Editieren im Schnitt: Cut-Polygone tragen die Quell-Element-Id
toSection loest je Cut-Band die Quell-Element-Id (Wand/Decke/Dach) ueber die Besitzer-Listen auf (cp.sourceId) und propagiert sie durch Schicht-Zerlegung, Terminierung und Boolean-Dominanz. generateSectionPlan schreibt sie je component.kind als wallId/ceilingId/roofId ans Plan-Polygon — ein Klick im Schnitt waehlt damit exakt jenes Bauteil im Modell (bestehende PlanView-Pick-/ Highlight-Logik greift), das Attribut-Panel editiert OK/UK/Hoehe, der Schnitt rechnet neu. Tests fuer Mapping + sourceId-Propagation.
This commit is contained in:
@@ -995,6 +995,12 @@ export function generateSectionPlan(output: SectionOutput, mono = false): Plan {
|
|||||||
const monoFill = solid ? HATCH_INK : HATCH_PAPER;
|
const monoFill = solid ? HATCH_INK : HATCH_PAPER;
|
||||||
const monoHatch =
|
const monoHatch =
|
||||||
solid ? hatch : hatch.pattern !== "none" ? { ...hatch, color: SECTION_INK } : hatch;
|
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({
|
primitives.push({
|
||||||
kind: "polygon",
|
kind: "polygon",
|
||||||
pts,
|
pts,
|
||||||
@@ -1002,6 +1008,9 @@ export function generateSectionPlan(output: SectionOutput, mono = false): Plan {
|
|||||||
stroke: SECTION_INK,
|
stroke: SECTION_INK,
|
||||||
strokeWidthMm: SECTION_CUT_OUTLINE_MM,
|
strokeWidthMm: SECTION_CUT_OUTLINE_MM,
|
||||||
hatch: mono ? monoHatch : hatch,
|
hatch: mono ? monoHatch : hatch,
|
||||||
|
wallId: kind === "wall" ? sourceId : undefined,
|
||||||
|
ceilingId: kind === "slab" ? sourceId : undefined,
|
||||||
|
roofId: kind === "roof" ? sourceId : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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<typeof p, { kind: "polygon" }> => 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<typeof p, { kind: "polygon" }> => p.kind === "polygon",
|
||||||
|
);
|
||||||
|
expect(poly?.wallId).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -112,6 +112,13 @@ describe("splitWallLayers: Aussenputz auf der Aussenseite, Wände spiegelverkehr
|
|||||||
expect(bands[3].width).toBeCloseTo(RENDER_EXT_T, 6);
|
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)", () => {
|
it("W2 und W4 liegen spiegelverkehrt (nicht identisch)", () => {
|
||||||
const b2 = bandsByU(splitWallLayers(cp, sampleProject, aw, wall("W2"), uWorld));
|
const b2 = bandsByU(splitWallLayers(cp, sampleProject, aw, wall("W2"), uWorld));
|
||||||
const b4 = bandsByU(splitWallLayers(cp, sampleProject, aw, wall("W4"), uWorld));
|
const b4 = bandsByU(splitWallLayers(cp, sampleProject, aw, wall("W4"), uWorld));
|
||||||
|
|||||||
@@ -76,6 +76,15 @@ export interface SectionCutPolygon {
|
|||||||
* Wert (unauflösbares Bauteil), nimmt das Band an keiner Verschmelzung teil.
|
* Wert (unauflösbares Bauteil), nimmt das Band an keiner Verschmelzung teil.
|
||||||
*/
|
*/
|
||||||
componentId?: string;
|
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. */
|
/** 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
|
// aufgelöst; keine Boolean-Dominanz (joinPriority undefined) — unverändert
|
||||||
// übernehmen. WICHTIG vor dem Slab-Zweig (sonst falscher owner-Lookup).
|
// übernehmen. WICHTIG vor dem Slab-Zweig (sonst falscher owner-Lookup).
|
||||||
if (ref.kind === "roof") {
|
if (ref.kind === "roof") {
|
||||||
|
cp.sourceId = (project.roofs ?? [])[ref.index]?.id;
|
||||||
bands.push(cp);
|
bands.push(cp);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (ref.kind === "wall") {
|
if (ref.kind === "wall") {
|
||||||
const owner = walls[ref.index];
|
const owner = walls[ref.index];
|
||||||
|
cp.sourceId = owner?.id;
|
||||||
// Wand: bei mehrschichtigem Wandtyp wird das EINE geschnittene Rechteck in
|
// Wand: bei mehrschichtigem Wandtyp wird das EINE geschnittene Rechteck in
|
||||||
// stehende Teil-Rechtecke je Schicht quer zur Dicke (u) aufgeteilt — das
|
// stehende Teil-Rechtecke je Schicht quer zur Dicke (u) aufgeteilt — das
|
||||||
// vertikale Gegenstück zur Decken-Zerlegung (dort quer zur Höhe v), siehe
|
// 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
|
// Schnitt sichtbar (im Grundriss bleibt die Decke bewusst flächig, siehe
|
||||||
// `addCeilingPoche`).
|
// `addCeilingPoche`).
|
||||||
const owner = slabs[ref.index];
|
const owner = slabs[ref.index];
|
||||||
|
cp.sourceId = owner?.id;
|
||||||
if (owner) {
|
if (owner) {
|
||||||
const wt = getCeilingType(project, owner);
|
const wt = getCeilingType(project, owner);
|
||||||
if (wt.layers.length > 1) {
|
if (wt.layers.length > 1) {
|
||||||
@@ -727,6 +739,7 @@ function bandFromRect(src: SectionCutPolygon, r: Rect): SectionCutPolygon {
|
|||||||
hatch: src.hatch,
|
hatch: src.hatch,
|
||||||
joinPriority: src.joinPriority,
|
joinPriority: src.joinPriority,
|
||||||
componentId: src.componentId,
|
componentId: src.componentId,
|
||||||
|
sourceId: src.sourceId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,6 +792,7 @@ function splitSlabLayers(
|
|||||||
hatch,
|
hatch,
|
||||||
joinPriority: comp.joinPriority,
|
joinPriority: comp.joinPriority,
|
||||||
componentId: comp.id,
|
componentId: comp.id,
|
||||||
|
sourceId: cp.sourceId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
@@ -865,6 +879,7 @@ export function splitWallLayers(
|
|||||||
hatch,
|
hatch,
|
||||||
joinPriority: comp.joinPriority,
|
joinPriority: comp.joinPriority,
|
||||||
componentId: comp.id,
|
componentId: comp.id,
|
||||||
|
sourceId: cp.sourceId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
Reference in New Issue
Block a user