diff --git a/src/plan/generatePlan.ts b/src/plan/generatePlan.ts index 8247e13..0839430 100644 --- a/src/plan/generatePlan.ts +++ b/src/plan/generatePlan.ts @@ -995,6 +995,12 @@ export function generateSectionPlan(output: SectionOutput, mono = false): Plan { const monoFill = solid ? HATCH_INK : HATCH_PAPER; const monoHatch = solid ? hatch : hatch.pattern !== "none" ? { ...hatch, color: SECTION_INK } : hatch; + // Auswahl im Schnitt: das QUELL-Element (via cp.sourceId + component.kind) als + // wallId/ceilingId/roofId ans Polygon hängen — so wählt ein Klick auf das + // geschnittene Bauteil im Schnitt exakt jene Wand/Decke/Dach im Modell, und + // die bestehende PlanView-Pick-/Highlight-Logik greift ohne Sonderfall. + const kind = cp.component.kind; + const sourceId = cp.sourceId; primitives.push({ kind: "polygon", pts, @@ -1002,6 +1008,9 @@ export function generateSectionPlan(output: SectionOutput, mono = false): Plan { stroke: SECTION_INK, strokeWidthMm: SECTION_CUT_OUTLINE_MM, hatch: mono ? monoHatch : hatch, + wallId: kind === "wall" ? sourceId : undefined, + ceilingId: kind === "slab" ? sourceId : undefined, + roofId: kind === "roof" ? sourceId : undefined, }); } diff --git a/src/plan/generateSectionPlan.select.test.ts b/src/plan/generateSectionPlan.select.test.ts new file mode 100644 index 0000000..5077813 --- /dev/null +++ b/src/plan/generateSectionPlan.select.test.ts @@ -0,0 +1,58 @@ +// Auswahl im Schnitt-View: generateSectionPlan hängt jedem Cut-Polygon die +// Quell-Element-Id (cp.sourceId) je nach component.kind als wallId/ceilingId/ +// roofId ans Plan-Polygon — damit ein Klick im Schnitt das Bauteil im Modell +// wählt (die bestehende PlanView-Pick-Logik greift dann ohne Sonderfall). + +import { describe, it, expect } from "vitest"; +import { generateSectionPlan } from "./generatePlan"; +import type { SectionCutPolygon, SectionOutput } from "./toSection"; + +function cut(kind: "wall" | "slab" | "roof", sourceId: string): SectionCutPolygon { + return { + component: { kind, index: 0 }, + color: [0.5, 0.5, 0.5], + pts: [ + [0, 0], + [1, 0], + [1, 2], + [0, 2], + ], + sourceId, + }; +} + +const output: SectionOutput = { + cutPolygons: [cut("wall", "W1"), cut("slab", "C1"), cut("roof", "R1")], + visibleEdges: [], + hiddenEdges: [], +}; + +describe("generateSectionPlan — Auswahl-Ids am Cut-Polygon", () => { + it("schreibt sourceId je component.kind als wallId/ceilingId/roofId", () => { + const polys = generateSectionPlan(output).primitives.filter( + (p): p is Extract => p.kind === "polygon", + ); + const wall = polys.find((p) => p.wallId === "W1"); + const ceiling = polys.find((p) => p.ceilingId === "C1"); + const roof = polys.find((p) => p.roofId === "R1"); + expect(wall).toBeTruthy(); + expect(ceiling).toBeTruthy(); + expect(roof).toBeTruthy(); + // Kanäle schließen sich je Polygon aus (eine Wand trägt keine ceilingId). + expect(wall?.ceilingId).toBeUndefined(); + expect(wall?.roofId).toBeUndefined(); + expect(ceiling?.wallId).toBeUndefined(); + }); + + it("lässt die Ids weg, wenn cp.sourceId fehlt (unauflösbares Bauteil)", () => { + const noId: SectionOutput = { + cutPolygons: [{ ...cut("wall", "W1"), sourceId: undefined }], + visibleEdges: [], + hiddenEdges: [], + }; + const poly = generateSectionPlan(noId).primitives.find( + (p): p is Extract => p.kind === "polygon", + ); + expect(poly?.wallId).toBeUndefined(); + }); +}); diff --git a/src/plan/toSection.orientation.test.ts b/src/plan/toSection.orientation.test.ts index 9b74ed0..f5948d9 100644 --- a/src/plan/toSection.orientation.test.ts +++ b/src/plan/toSection.orientation.test.ts @@ -112,6 +112,13 @@ describe("splitWallLayers: Aussenputz auf der Aussenseite, Wände spiegelverkehr expect(bands[3].width).toBeCloseTo(RENDER_EXT_T, 6); }); + it("propagiert sourceId (Quell-Element) auf ALLE Schicht-Bänder", () => { + const cpWithId: SectionCutPolygon = { ...cp, sourceId: "W2" }; + const bands = splitWallLayers(cpWithId, sampleProject, aw, wall("W2"), uWorld); + expect(bands.length).toBeGreaterThan(1); + expect(bands.every((b) => b.sourceId === "W2")).toBe(true); + }); + it("W2 und W4 liegen spiegelverkehrt (nicht identisch)", () => { const b2 = bandsByU(splitWallLayers(cp, sampleProject, aw, wall("W2"), uWorld)); const b4 = bandsByU(splitWallLayers(cp, sampleProject, aw, wall("W4"), uWorld)); diff --git a/src/plan/toSection.ts b/src/plan/toSection.ts index 82fb48d..6e8c56d 100644 --- a/src/plan/toSection.ts +++ b/src/plan/toSection.ts @@ -76,6 +76,15 @@ export interface SectionCutPolygon { * Wert (unauflösbares Bauteil), nimmt das Band an keiner Verschmelzung teil. */ componentId?: string; + /** + * ID des QUELL-Elements dieses Cut-Bands im Dokumentmodell (Wand/Decke/Dach) — + * aufgelöst in `attachCutStyles` über die Besitzer-Listen. Trägt die Auswahl im + * Schnitt-View: `generateSectionPlan` schreibt sie je nach `component.kind` als + * `wallId`/`ceilingId`/`roofId` ins Plan-Polygon, sodass ein Klick auf das + * geschnittene Bauteil im Schnitt exakt jenes Element im Grundriss-Modell wählt. + * Fehlt (unauflösbares Bauteil), bleibt das Band ohne Auswahlbezug. + */ + sourceId?: string; } /** Ein projiziertes Liniensegment in (u, v)-Metern. */ @@ -294,11 +303,13 @@ function attachCutStyles(output: SectionOutput, project: Project, plane: Section // aufgelöst; keine Boolean-Dominanz (joinPriority undefined) — unverändert // übernehmen. WICHTIG vor dem Slab-Zweig (sonst falscher owner-Lookup). if (ref.kind === "roof") { + cp.sourceId = (project.roofs ?? [])[ref.index]?.id; bands.push(cp); continue; } if (ref.kind === "wall") { const owner = walls[ref.index]; + cp.sourceId = owner?.id; // Wand: bei mehrschichtigem Wandtyp wird das EINE geschnittene Rechteck in // stehende Teil-Rechtecke je Schicht quer zur Dicke (u) aufgeteilt — das // vertikale Gegenstück zur Decken-Zerlegung (dort quer zur Höhe v), siehe @@ -327,6 +338,7 @@ function attachCutStyles(output: SectionOutput, project: Project, plane: Section // Schnitt sichtbar (im Grundriss bleibt die Decke bewusst flächig, siehe // `addCeilingPoche`). const owner = slabs[ref.index]; + cp.sourceId = owner?.id; if (owner) { const wt = getCeilingType(project, owner); if (wt.layers.length > 1) { @@ -727,6 +739,7 @@ function bandFromRect(src: SectionCutPolygon, r: Rect): SectionCutPolygon { hatch: src.hatch, joinPriority: src.joinPriority, componentId: src.componentId, + sourceId: src.sourceId, }; } @@ -779,6 +792,7 @@ function splitSlabLayers( hatch, joinPriority: comp.joinPriority, componentId: comp.id, + sourceId: cp.sourceId, }); } return out; @@ -865,6 +879,7 @@ export function splitWallLayers( hatch, joinPriority: comp.joinPriority, componentId: comp.id, + sourceId: cp.sourceId, }); } return out;