Joins Phase 1b: T-Stoss-Kern sticht nur durch den Nah-Putz (keine Achs-Ueberlappung)

Der verschmelzende Abzweig-Kern (Backstein) lief seit Phase 1 bis zur
ACHSE der Durchgangswand und ueberlappte deren Koerper. Jetzt stoppt er
an der Nahflaeche des Durchgangs-Rueckgrats: mergeCut bei
offT + sign*(tT/2 - nearPlaster), wobei nearPlaster die Summe der
Durchgangs-Schichten zwischen Nahseite und Rueckgrat-Kern ist. Die
L-Seitenlinien der Putzschichten liegen ebenfalls auf dieser Tiefe.
Naht zwischen den beiden Kernen entfaellt ueber die bestehenden
Mechanismen (stroke:none am Fuellband, noStrokeEdges an den Stirnkanten,
Zeichenreihenfolge bricht die Nahflaechenlinie auf) - kein
generatePlan-Umbau noetig. Rust-Paritaet in geometry/lib.rs analog
(Aggregat-Cuts unveraendert, parity.test gruen). 216 Tests + cargo 7/7.
This commit is contained in:
2026-07-04 14:55:51 +02:00
parent cf8b638f33
commit 9acce9d7cd
4 changed files with 132 additions and 49 deletions
+57 -17
View File
@@ -117,8 +117,12 @@ fn resolve_join_priority(
/// von `applyLayerCuts` aus `model/joins.ts`. Vergleicht jede Schicht der /// von `applyLayerCuts` aus `model/joins.ts`. Vergleicht jede Schicht der
/// Abzweig-Wand mit dem Rueckgrat (hoechste `joinPriority`) der Durchgangswand; /// Abzweig-Wand mit dem Rueckgrat (hoechste `joinPriority`) der Durchgangswand;
/// verschmilzt sie (gleiches Rueckgrat-Material, oder die Abzweig-Schicht ist /// verschmilzt sie (gleiches Rueckgrat-Material, oder die Abzweig-Schicht ist
/// sogar prioritaerer), bekommt sie keinen Cut. Getrimmte Schichten neben einer /// sogar prioritaerer), bekommt sie einen Cut an der NAHFLAECHE DES RUECKGRAT-
/// verschmelzenden Nachbarschicht bekommen zusaetzlich die seitliche L-Linie. /// KERNS der Durchgangswand -- der verschmelzende Kern sticht nur durch den
/// Nah-Putz und stoppt am Durchgangs-Kern, statt bis zur Achse durchzulaufen.
/// Getrimmte Schichten neben einer verschmelzenden Nachbarschicht bekommen
/// zusaetzlich die seitliche L-Linie an eben dieser Rueckgrat-Nahflaeche.
/// `sign`/`off_t` = Nahseite bzw. Referenzversatz der Durchgangswand.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn apply_layer_cuts( fn apply_layer_cuts(
result: &mut [WallCuts], result: &mut [WallCuts],
@@ -129,16 +133,21 @@ fn apply_layer_cuts(
face_cut: Line, face_cut: Line,
axis_point: Vec2, axis_point: Vec2,
through_dir: Vec2, through_dir: Vec2,
sign: f64,
off_t: f64,
) { ) {
if branch_layers.is_empty() || through_layers.is_empty() { if branch_layers.is_empty() || through_layers.is_empty() {
return; return;
} }
// Rueckgrat der Durchgangswand: die Schicht mit der hoechsten joinPriority. // Rueckgrat der Durchgangswand: die Schicht mit der hoechsten joinPriority
// (Index mitgefuehrt fuer die Nah-Putz-Dicke davor).
let mut backbone = &through_layers[0]; let mut backbone = &through_layers[0];
for l in through_layers { let mut backbone_idx = 0usize;
for (i, l) in through_layers.iter().enumerate() {
if l.join_priority > backbone.join_priority { if l.join_priority > backbone.join_priority {
backbone = l; backbone = l;
backbone_idx = i;
} }
} }
@@ -171,13 +180,33 @@ fn apply_layer_cuts(
return; // Schicht-Anzahl passt nicht zusammen (sollte nicht vorkommen) return; // Schicht-Anzahl passt nicht zusammen (sollte nicht vorkommen)
} }
let side_line = Line { point: axis_point, dir: through_dir }; // Nahflaechen-Cut des verschmelzenden Kerns: von der Durchgangswand-Nah-
// flaeche (off_t + sign*t_t/2) um die Nah-Putz-Dicke nach innen versetzt =
// die dem Abzweig zugewandte Flaeche des Rueckgrat-Kerns. Nah-Putz = Summe
// der Durchgangswand-Schichten zwischen Nahseite (sign) und Rueckgrat: auf
// der +nT-Nahseite (sign>0) die Schichten hinter dem Rueckgrat
// (Index > backbone_idx), auf der -nT-Nahseite die davor.
let t_t: f64 = through_layers.iter().map(|l| l.thickness).sum();
let mut near_plaster = 0.0;
for (i, l) in through_layers.iter().enumerate() {
let near = if sign > 0.0 { i > backbone_idx } else { i < backbone_idx };
if near {
near_plaster += l.thickness;
}
}
let n_t = left_normal(through_dir);
let merge_point = add(axis_point, scale(n_t, off_t + sign * (t_t / 2.0 - near_plaster)));
let merge_cut = Line { point: merge_point, dir: through_dir };
// Getrimmte Nachbarschichten schliessen mit ihrer L-Seitenlinie an dieser
// Rueckgrat-Nahflaeche ab (nicht an der Wandachse).
let side_line = merge_cut;
let (face_arr, side_arr) = match branch_end { let (face_arr, side_arr) = match branch_end {
WallEndKind::Start => (&mut lc.start, &mut lc.start_side), WallEndKind::Start => (&mut lc.start, &mut lc.start_side),
WallEndKind::End => (&mut lc.end, &mut lc.end_side), WallEndKind::End => (&mut lc.end, &mut lc.end_side),
}; };
for i in 0..n { for i in 0..n {
face_arr[i] = if merged[i] { None } else { Some(face_cut) }; face_arr[i] = if merged[i] { Some(merge_cut) } else { Some(face_cut) };
let adj_merged = let adj_merged =
!merged[i] && ((i > 0 && merged[i - 1]) || (i + 1 < n && merged[i + 1])); !merged[i] && ((i > 0 && merged[i - 1]) || (i + 1 < n && merged[i + 1]));
side_arr[i] = if adj_merged { Some(side_line) } else { None }; side_arr[i] = if adj_merged { Some(side_line) } else { None };
@@ -454,6 +483,8 @@ fn apply_t_junction(
face_cut, face_cut,
j, j,
u_t, u_t,
sign,
off_t,
); );
} }
@@ -542,6 +573,8 @@ fn apply_mid_span_tee(
face_cut, face_cut,
axis_point, axis_point,
u_w, u_w,
sign,
off_w,
); );
} }
@@ -825,9 +858,10 @@ mod tests {
#[test] #[test]
fn t_junction_layer_cuts_merge_and_trim() { fn t_junction_layer_cuts_merge_and_trim() {
// Wie t_junction_branch_gets_face_cut, aber alle drei Waende vom // Wie t_junction_branch_gets_face_cut, aber alle drei Waende vom
// W9-artigen Wandtyp: der Backstein-Kern (Index 1) verschmilzt (kein // W9-artigen Wandtyp: der Backstein-Kern (Index 1) sticht durch den
// Cut), die beiden Innenputz-Schichten (Index 0/2) werden getrimmt und // Nah-Putz und stoppt an der Kern-Nahflaeche (y=0.06), die beiden
// bekommen zusaetzlich die seitliche L-Linie. // Innenputz-Schichten (Index 0/2) werden getrimmt und bekommen
// zusaetzlich die seitliche L-Linie an derselben Flaeche.
let input = JoinInput { let input = JoinInput {
walls: vec![ walls: vec![
w_iw("A", 0.0, 0.0, 5.0, 0.0), w_iw("A", 0.0, 0.0, 5.0, 0.0),
@@ -846,8 +880,15 @@ mod tests {
let lc = cb.layer_cuts.as_ref().expect("layerCuts gesetzt (Backstein verschmilzt)"); let lc = cb.layer_cuts.as_ref().expect("layerCuts gesetzt (Backstein verschmilzt)");
assert_eq!(lc.start.len(), 3); assert_eq!(lc.start.len(), 3);
// Schicht 1 (Backstein-Kern): kein Cut, keine Seitenlinie. let dist = |cut: Line, q: Vec2| cross(cut.dir, sub(q, cut.point)).abs() / len(cut.dir);
assert!(lc.start[1].is_none());
// Schicht 1 (Backstein-Kern): Cut an der Rueckgrat-Nahflaeche y=0.06
// (Halbdicke 0.075 minus Nah-Putz 0.015), NICHT null/Achse; keine
// Seitenlinie (verschmilzt).
let l1 = lc.start[1].expect("Schicht 1 (Kern) hat Merge-Cut");
assert!(dist(l1, Vec2 { x: 5.0, y: 0.06 }) < 1e-9);
assert!(dist(l1, Vec2 { x: 0.0, y: 0.06 }) < 1e-9);
assert!(dist(l1, Vec2 { x: 5.0, y: 0.0 }) > 1e-6); // nicht bis zur Achse
assert!(lc.start_side[1].is_none()); assert!(lc.start_side[1].is_none());
// Schichten 0/2 (Innenputz): derselbe Nahflaechen-Cut wie das Aggregat... // Schichten 0/2 (Innenputz): derselbe Nahflaechen-Cut wie das Aggregat...
@@ -855,14 +896,13 @@ mod tests {
let l2 = lc.start[2].expect("Schicht 2 getrimmt"); let l2 = lc.start[2].expect("Schicht 2 getrimmt");
assert!((l0.point.y - 0.075).abs() < 1e-9); assert!((l0.point.y - 0.075).abs() < 1e-9);
assert!((l2.point.y - 0.075).abs() < 1e-9); assert!((l2.point.y - 0.075).abs() < 1e-9);
// ... UND die seitliche L-Linie entlang der Durchgangsachse (durch den // ... UND die seitliche L-Linie an der Rueckgrat-Nahflaeche (y=0.06,
// Knoten (5,0), parallel zur x-Achse). // parallel zur x-Achse).
let s0 = lc.start_side[0].expect("Schicht 0 hat L-Seitenlinie"); let s0 = lc.start_side[0].expect("Schicht 0 hat L-Seitenlinie");
let s2 = lc.start_side[2].expect("Schicht 2 hat L-Seitenlinie"); let s2 = lc.start_side[2].expect("Schicht 2 hat L-Seitenlinie");
let dist = |cut: Line, q: Vec2| cross(cut.dir, sub(q, cut.point)).abs() / len(cut.dir); assert!(dist(s0, Vec2 { x: 5.0, y: 0.06 }) < 1e-9);
assert!(dist(s0, Vec2 { x: 5.0, y: 0.0 }) < 1e-9); assert!(dist(s0, Vec2 { x: 0.0, y: 0.06 }) < 1e-9);
assert!(dist(s0, Vec2 { x: 0.0, y: 0.0 }) < 1e-9); assert!(dist(s2, Vec2 { x: 5.0, y: 0.06 }) < 1e-9);
assert!(dist(s2, Vec2 { x: 5.0, y: 0.0 }) < 1e-9);
// Die kollineare Durchgangswand bleibt unangetastet (kein layerCuts). // Die kollineare Durchgangswand bleibt unangetastet (kein layerCuts).
let ca = find(&out, "A"); let ca = find(&out, "A");
+14 -7
View File
@@ -267,7 +267,7 @@ function iwWall(id: string, start: Vec2, end: Vec2): Wall {
} }
describe("computeJoins — materialbewusster T-Stoss (layerCuts)", () => { describe("computeJoins — materialbewusster T-Stoss (layerCuts)", () => {
it("W9-artiger T-Knoten: Backstein-Kern des Abzweigs verschmilzt (kein Cut), Putz wird getrimmt + L-Seitenlinie", () => { it("W9-artiger T-Knoten: Backstein-Kern des Abzweigs sticht bis zur Rückgrat-Nahfläche durch, Putz wird getrimmt + L-Seitenlinie an derselben Fläche", () => {
// Durchgangswand WA1 (0,0)→(5,0) / WA2 (5,0)→(10,0), Abzweig WB (5,0)→(5,3), // Durchgangswand WA1 (0,0)→(5,0) / WA2 (5,0)→(10,0), Abzweig WB (5,0)→(5,3),
// alle vom selben Wandtyp "iw" (Innenputz/Backstein/Innenputz, T=0.15). // alle vom selben Wandtyp "iw" (Innenputz/Backstein/Innenputz, T=0.15).
const proj = layeredWallProject( const proj = layeredWallProject(
@@ -287,8 +287,14 @@ describe("computeJoins — materialbewusster T-Stoss (layerCuts)", () => {
const lc = cuts.layerCuts!; const lc = cuts.layerCuts!;
expect(lc.start.length).toBe(3); expect(lc.start.length).toBe(3);
// Schicht 1 (Backstein-Kern, Index 1) verschmilzt: KEIN Cut. // Schicht 1 (Backstein-Kern, Index 1) verschmilzt: Cut an der Nahfläche des
expect(lc.start[1]).toBeNull(); // Durchgangs-Rückgrats — NICHT null (Achse) und NICHT die Aggregat-Nahfläche
// (0.075). Durchgangswand-Halbdicke 0.075 minus Nah-Putz 0.015 → y = 0.06.
expect(lc.start[1]).not.toBeNull();
expect(distToLine(lc.start[1]!, { x: 5, y: 0.06 })).toBeCloseTo(0, 6);
expect(distToLine(lc.start[1]!, { x: 0, y: 0.06 })).toBeCloseTo(0, 6);
// Der Kern läuft NICHT bis zur Achse (y=0) durch.
expect(distToLine(lc.start[1]!, { x: 5, y: 0 })).not.toBeCloseTo(0, 6);
expect(lc.startSide[1]).toBeNull(); expect(lc.startSide[1]).toBeNull();
// Schichten 0 und 2 (Innenputz) werden getrimmt: derselbe Nahflächen-Cut // Schichten 0 und 2 (Innenputz) werden getrimmt: derselbe Nahflächen-Cut
@@ -297,12 +303,13 @@ describe("computeJoins — materialbewusster T-Stoss (layerCuts)", () => {
expect(lc.start[2]).not.toBeNull(); expect(lc.start[2]).not.toBeNull();
expect(distToLine(lc.start[0]!, { x: 5, y: 0.075 })).toBeCloseTo(0, 6); expect(distToLine(lc.start[0]!, { x: 5, y: 0.075 })).toBeCloseTo(0, 6);
expect(distToLine(lc.start[2]!, { x: 5, y: 0.075 })).toBeCloseTo(0, 6); expect(distToLine(lc.start[2]!, { x: 5, y: 0.075 })).toBeCloseTo(0, 6);
// ... UND bekommen die zusätzliche L-Seitenlinie entlang der // ... UND bekommen die zusätzliche L-Seitenlinie an der Rückgrat-Nahfläche
// Durchgangsachse (durch den Knoten (5,0), Richtung parallel zur x-Achse). // (y = 0.06, parallel zur Durchgangsachse) — dort endet der durchstechende
// Kern.
expect(lc.startSide[0]).not.toBeNull(); expect(lc.startSide[0]).not.toBeNull();
expect(lc.startSide[2]).not.toBeNull(); expect(lc.startSide[2]).not.toBeNull();
expect(distToLine(lc.startSide[0]!, { x: 5, y: 0 })).toBeCloseTo(0, 6); expect(distToLine(lc.startSide[0]!, { x: 5, y: 0.06 })).toBeCloseTo(0, 6);
expect(distToLine(lc.startSide[0]!, { x: 0, y: 0 })).toBeCloseTo(0, 6); expect(distToLine(lc.startSide[0]!, { x: 0, y: 0.06 })).toBeCloseTo(0, 6);
// Die kollinearen Hälften der Durchgangswand bleiben unangetastet // Die kollinearen Hälften der Durchgangswand bleiben unangetastet
// (unverändertes Verhalten, keine layerCuts für sie). // (unverändertes Verhalten, keine layerCuts für sie).
+50 -16
View File
@@ -285,7 +285,7 @@ function applyTJunction(
const faceCut: Line = { point: facePoint, dir: uT }; const faceCut: Line = { point: facePoint, dir: uT };
setCut(result, branchEnd, faceCut); setCut(result, branchEnd, faceCut);
applyLayerCuts(project, result, branchEnd, branchWall, throughWall, faceCut, j, uT); applyLayerCuts(project, result, branchEnd, branchWall, throughWall, faceCut, j, uT, sign);
} }
/** /**
@@ -293,16 +293,22 @@ function applyTJunction(
* {@link resolveJoinPriority}): vergleicht jede Schicht der ABZWEIG-Wand mit * {@link resolveJoinPriority}): vergleicht jede Schicht der ABZWEIG-Wand mit
* dem Rückgrat (höchste `joinPriority`) der DURCHGANGSWAND. Verschmilzt eine * dem Rückgrat (höchste `joinPriority`) der DURCHGANGSWAND. Verschmilzt eine
* Schicht (gleiche Komponente + Priorität wie das Rückgrat, oder die * Schicht (gleiche Komponente + Priorität wie das Rückgrat, oder die
* Abzweig-Schicht ist sogar prioritärer), bekommt sie KEINEN Cut — das Band * Abzweig-Schicht ist sogar prioritärer), bekommt sie einen Cut an der
* läuft bis zum Wandachsenpunkt durch (die Durchgangswand selbst ist an einem * NAHFLÄCHE DES RÜCKGRAT-KERNS der Durchgangswand: der verschmelzende Abzweig-
* T-Stoss unzerschnitten und deckt den Rest ab, s. `applyTJunction`-Doc). * Kern (z. B. Backstein) sticht nur durch den Nah-Putz der Durchgangswand und
* stoppt am Durchgangs-Kern — er läuft NICHT bis zur Achse durch und überlappt
* die Durchgangswand nicht (dort verschmelzen die beiden Kerne nahtlos).
* Getrimmte Schichten, die direkt an eine verschmelzende Nachbarschicht * Getrimmte Schichten, die direkt an eine verschmelzende Nachbarschicht
* grenzen, bekommen zusätzlich eine seitliche Cut-Linie entlang der * grenzen, bekommen zusätzlich eine seitliche Cut-Linie an eben dieser
* Durchgangsachse (L-Anschluss, z. B. Putz neben durchlaufendem Kern). * Rückgrat-Nahfläche (L-Anschluss, z. B. Putz neben durchstechendem Kern).
* *
* Setzt `WallCuts.layerCuts` NUR, wenn mindestens eine Schicht verschmilzt — * Setzt `WallCuts.layerCuts` NUR, wenn mindestens eine Schicht verschmilzt —
* sonst bleibt das Feld `undefined` (additiv, bit-identisches Default- * sonst bleibt das Feld `undefined` (additiv, bit-identisches Default-
* Verhalten für den ganz überwiegenden Fall ohne Materialübereinstimmung). * Verhalten für den ganz überwiegenden Fall ohne Materialübereinstimmung).
*
* `sign` ist die Nahseite der Durchgangswand (der Abzweig-zugewandte `nT`-
* Vorzeichen, aus {@link applyTJunction}/{@link applyMidSpanTee}); er bestimmt,
* welche Durchgangswand-Schichten als Nah-Putz vor dem Rückgrat liegen.
*/ */
function applyLayerCuts( function applyLayerCuts(
project: Project, project: Project,
@@ -313,16 +319,22 @@ function applyLayerCuts(
faceCut: Line, faceCut: Line,
axisPoint: Vec2, axisPoint: Vec2,
throughDir: Vec2, throughDir: Vec2,
sign: number,
): void { ): void {
const branchWt = getWallType(project, branchWall); const branchWt = getWallType(project, branchWall);
const throughWt = getWallType(project, throughWall); const throughWt = getWallType(project, throughWall);
if (branchWt.layers.length === 0 || throughWt.layers.length === 0) return; if (branchWt.layers.length === 0 || throughWt.layers.length === 0) return;
// Rückgrat der Durchgangswand: die Schicht mit der höchsten joinPriority. // Rückgrat der Durchgangswand: die Schicht mit der höchsten joinPriority
// (Index mitgeführt, um die Nah-Putz-Dicke davor zu bestimmen).
let backbone = getComponent(project, throughWt.layers[0].componentId); let backbone = getComponent(project, throughWt.layers[0].componentId);
for (const l of throughWt.layers) { let backboneIdx = 0;
const c = getComponent(project, l.componentId); for (let i = 0; i < throughWt.layers.length; i++) {
if (c.joinPriority > backbone.joinPriority) backbone = c; const c = getComponent(project, throughWt.layers[i].componentId);
if (c.joinPriority > backbone.joinPriority) {
backbone = c;
backboneIdx = i;
}
} }
const n = branchWt.layers.length; const n = branchWt.layers.length;
@@ -352,13 +364,35 @@ function applyLayerCuts(
const faceArr = branchEnd.end === "start" ? lc.start : lc.end; const faceArr = branchEnd.end === "start" ? lc.start : lc.end;
const sideArr = branchEnd.end === "start" ? lc.startSide : lc.endSide; const sideArr = branchEnd.end === "start" ? lc.startSide : lc.endSide;
// Seitliche Cut-Linie entlang der Durchgangsachse, auf Höhe des Wand-
// Achsenpunkts (= die Tiefe, bis zu der eine verschmelzende Nachbarschicht // Nahflächen-Cut des verschmelzenden Kerns: von der Durchgangswand-Nahfläche
// ungekappt durchläuft). // (`offT + sign·tT/2`) um die Nah-Putz-Dicke nach innen versetzt — also genau
const sideLine: Line = { point: axisPoint, dir: throughDir }; // die dem Abzweig zugewandte Fläche des Rückgrat-Kerns. Nah-Putz = Summe der
// Durchgangswand-Schichten, die zwischen der Nahseite (sign) und dem Rückgrat
// liegen. Die Schichten stapeln entlang +nT (Index 0 = tT/2); auf der
// +nT-Nahseite (sign>0) sind das die Schichten HINTER dem Rückgrat
// (Index > backboneIdx), auf der nT-Nahseite die davor (Index < backboneIdx).
const tT = wallTypeThickness(throughWt);
const offT = wallReferenceOffset(throughWall, tT);
let nearPlaster = 0;
for (let i = 0; i < throughWt.layers.length; i++) {
if (sign > 0 ? i > backboneIdx : i < backboneIdx)
nearPlaster += throughWt.layers[i].thickness;
}
const nT = leftNormal(throughDir);
const mergePoint = add(
axisPoint,
scale(nT, offT + sign * (tT / 2 - nearPlaster)),
);
const mergeCut: Line = { point: mergePoint, dir: throughDir };
// Getrimmte Nachbarschichten schließen mit ihrer L-Seitenlinie an eben dieser
// Rückgrat-Nahfläche (nicht an der Wandachse) — dort endet der Kern und die
// Nachbarschicht kappt ihre Breite.
const sideLine: Line = mergeCut;
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
faceArr[i] = merged[i] ? null : faceCut; faceArr[i] = merged[i] ? mergeCut : faceCut;
const adjMerged = const adjMerged =
!merged[i] && ((i > 0 && merged[i - 1]) || (i < n - 1 && merged[i + 1])); !merged[i] && ((i > 0 && merged[i - 1]) || (i < n - 1 && merged[i + 1]));
sideArr[i] = adjMerged ? sideLine : null; sideArr[i] = adjMerged ? sideLine : null;
@@ -420,5 +454,5 @@ function applyMidSpanTee(
// Endes (nicht zwingend w.start) — Anker für die materialbewusste // Endes (nicht zwingend w.start) — Anker für die materialbewusste
// Pro-Schicht-Erweiterung (analog zum T-Knoten-Fall). // Pro-Schicht-Erweiterung (analog zum T-Knoten-Fall).
const axisPoint = add(w.start, scale(uW, best.distAlong)); const axisPoint = add(w.start, scale(uW, best.distAlong));
applyLayerCuts(project, result, freeEnd, freeWall, w, faceCut, axisPoint, uW); applyLayerCuts(project, result, freeEnd, freeWall, w, faceCut, axisPoint, uW, sign);
} }
+11 -9
View File
@@ -3,8 +3,9 @@
* an einem T-Stoss zweier verputzter Mauerwerkswände (W9-artig: Innenputz/ * an einem T-Stoss zweier verputzter Mauerwerkswände (W9-artig: Innenputz/
* Backstein-Kern/Innenputz) darf der Abzweig nicht mehr naiv über die volle * Backstein-Kern/Innenputz) darf der Abzweig nicht mehr naiv über die volle
* Dicke an der Durchgangswand-Fläche gekappt werden. * Dicke an der Durchgangswand-Fläche gekappt werden.
* • Der Backstein-Kern des Abzweigs läuft UNGEKAPPT bis zur Durchgangsachse * • Der Backstein-Kern des Abzweigs sticht durch den Nah-Putz der Durchgangs-
* durch (kein Cut an der Nahfläche) — "durchgehender Kern". * wand und stoppt an deren Kern-Nahfläche (kein Durchlaufen bis zur Achse,
* kein Überlappen) — "durchstechender Kern".
* • Die Innenputz-Schichten des Abzweigs enden weiterhin an der Nahfläche * • Die Innenputz-Schichten des Abzweigs enden weiterhin an der Nahfläche
* UND bekommen eine zusätzliche seitliche L-Linie (cls "layer-joint-l"). * UND bekommen eine zusätzliche seitliche L-Linie (cls "layer-joint-l").
*/ */
@@ -88,10 +89,11 @@ describe("generatePlan — materialbewusster T-Stoss (Poché-Bänder)", () => {
expect(render2.pts[0].y).toBeCloseTo(0.075, 6); expect(render2.pts[0].y).toBeCloseTo(0.075, 6);
expect(render2.pts[3].y).toBeCloseTo(0.075, 6); expect(render2.pts[3].y).toBeCloseTo(0.075, 6);
// Der Backstein-Kern verschmilzt: KEIN Cut an der Nahfläche → die // Der Backstein-Kern verschmilzt, sticht aber nur durch den Nah-Putz der
// Randpunkte liegen auf der Durchgangsachse (y=0), nicht auf y=0.075. // Durchgangswand bis zur Nahfläche des Durchgangs-Kerns (y = 0.075 0.015 =
expect(brick.pts[0].y).toBeCloseTo(0, 6); // 0.06) — nicht bis zur Nahfläche (0.075) und NICHT bis zur Achse (0).
expect(brick.pts[3].y).toBeCloseTo(0, 6); expect(brick.pts[0].y).toBeCloseTo(0.06, 6);
expect(brick.pts[3].y).toBeCloseTo(0.06, 6);
}); });
it("zeichnet die L-Seitenlinie nur für die getrimmten Putzschichten, nicht für den durchlaufenden Kern", () => { it("zeichnet die L-Seitenlinie nur für die getrimmten Putzschichten, nicht für den durchlaufenden Kern", () => {
@@ -103,11 +105,11 @@ describe("generatePlan — materialbewusster T-Stoss (Poché-Bänder)", () => {
// bekommt keine (er verschmilzt, kein Sprung im Material). // bekommt keine (er verschmilzt, kein Sprung im Material).
expect(lLines.length).toBe(2); expect(lLines.length).toBe(2);
// Jede L-Linie verläuft entlang der Durchgangsachse (y=0, parallel zur // Jede L-Linie verläuft an der Rückgrat-Nahfläche (y=0.06, parallel zur
// x-Achse) und spannt genau die Breite EINER Putzschicht (0.015 m) auf. // x-Achse) und spannt genau die Breite EINER Putzschicht (0.015 m) auf.
for (const l of lLines) { for (const l of lLines) {
expect(l.a.y).toBeCloseTo(0, 6); expect(l.a.y).toBeCloseTo(0.06, 6);
expect(l.b.y).toBeCloseTo(0, 6); expect(l.b.y).toBeCloseTo(0.06, 6);
expect(Math.abs(l.a.x - l.b.x)).toBeCloseTo(0.015, 6); expect(Math.abs(l.a.x - l.b.x)).toBeCloseTo(0.015, 6);
} }
}); });