Merge: Schnittfunktion ausgebaut (Schnittlinie, Editieren im Schnitt, Tiefe)

- Befehl 'sectionline'/'viewline' (Aliase schnittlinie/ansichtslinie): zwei
  Klicks setzen die Schnitt-/Ansichtslinie, neue Ebene bei Bedarf
- Schnittführungs-Symbol im Grundriss (Strichpunkt, Endmarken, Richtungs-
  pfeile, Label) + Pick-Band, Auswahl + Attribut-Sektion (Name, Richtung
  umkehren, Endpunkte, Tiefe, Löschen)
- Editieren IM Schnitt: Cut-Polygone tragen die Quell-Element-Id (durch
  Schicht-Zerlegung/Terminierung/Dominanz propagiert) → Klick wählt das
  Bauteil, Panels editieren, Schnitt rechnet neu
- Schnitt-Tiefe (DrawingLevel.depth): Ansichtskanten ferner Bauteile werden
  ausgeblendet (Owner-Distanz-Näherung, dokumentiert)

# Conflicts:
#	src/model/types.ts
This commit is contained in:
2026-07-11 00:35:20 +02:00
19 changed files with 1135 additions and 2 deletions
+74 -1
View File
@@ -582,6 +582,9 @@ export default function App() {
const selectedRoofIds = useStore((s) => s.selectedRoofIds);
const setSelectedRoofIds = useStore((s) => s.setSelectedRoofIds);
const selectedRoofId = selectedRoofIds.length === 1 ? selectedRoofIds[0] : null;
// Schnitt-/Ansichtslinien-Auswahl (Einzel-DrawingLevel-Id oder null).
const selectedSectionLineId = useStore((s) => s.selectedSectionLineId);
const setSelectedSectionLineId = useStore((s) => s.setSelectedSectionLineId);
const updateRoof = useStore((s) => s.updateRoof);
const moveRoofGrip = useStore((s) => s.moveRoofGrip);
const moveRoofBy = useStore((s) => s.moveRoofBy);
@@ -635,6 +638,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
setMenu(null);
setEditor(null);
}, [activeLevelId]);
@@ -1853,6 +1857,13 @@ export default function App() {
}
if (e.key !== "Delete" && e.key !== "Backspace") return;
if (activeTransform) return; // während einer Transformation nicht löschen
// Schnittlinie: Entf löscht NUR ihre Linie (linePoints), die Ebene bleibt.
if (selectedSectionLineId) {
patchLevel(selectedSectionLineId, { linePoints: undefined });
setSelectedSectionLineId(null);
e.preventDefault();
return;
}
if (
selectedDrawingIds.length ||
selectedWallIds.length ||
@@ -1898,6 +1909,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
e.preventDefault();
}
};
@@ -1914,6 +1926,7 @@ export default function App() {
selectedExtrudedSolidIds,
selectedColumnIds,
selectedRoofIds,
selectedSectionLineId,
project.walls,
project.drawings2d,
project.ceilings,
@@ -2707,6 +2720,21 @@ export default function App() {
],
);
// Gewählte Schnitt-/Ansichtslinie (DrawingLevel) — separat von `selection`, da
// eine Schnittlinie KEIN Projekt-Bauteil ist. Speist die Schnittlinien-Sektion
// des Object-Info-Panels.
const sectionLine = useMemo(
() =>
selectedSectionLineId
? project.drawingLevels.find(
(z) =>
z.id === selectedSectionLineId &&
(z.kind === "section" || z.kind === "elevation"),
) ?? null
: null,
[project.drawingLevels, selectedSectionLineId],
);
// ── DXF/DWG-Import (Dialog) ───────────────────────────────────────────────
// Eine Datei (Button ODER Drop) öffnet den modalen Import-Dialog. DXF wird als
// Text geparst, DWG als ArrayBuffer über LibreDWG-WASM (lazy, asynchron); beide
@@ -3319,6 +3347,14 @@ export default function App() {
onSetRoofPatch: (patch) => {
if (selection?.kind === "roof") updateRoof(selection.id, patch);
},
// ── Schnitt-/Ansichtslinie (nur bei selektierter Linie) ────────────────
sectionLine,
onSetSectionLinePatch: (patch) => {
if (selectedSectionLineId) patchLevel(selectedSectionLineId, patch);
},
onDeleteSectionLine: () => {
if (selectedSectionLineId) patchLevel(selectedSectionLineId, { linePoints: undefined });
},
// ── Treppen-Attribute (nur bei selektierter Treppe) ────────────────────
onSetStairShape: (shape) => {
if (selection?.kind === "stair") updateStair(selection.id, { shape });
@@ -3475,6 +3511,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
switch (kind) {
case "Wand":
setSelectedWallIds([id]);
@@ -3848,6 +3885,8 @@ export default function App() {
detail,
scaleDenominator,
selection,
sectionLine,
selectedSectionLineId,
selectedViewSnapshotId,
]);
@@ -4334,6 +4373,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
setStampEditorRoomId(roomId);
};
// Doppelklick auf ein Text-2D-Element: selektieren + Inhalts-Editor öffnen.
@@ -4349,6 +4389,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
setEditTextId(drawingId);
};
// Bearbeiteten Text-Inhalt übernehmen (immutabel; nur bei Text-Geometrie).
@@ -4373,6 +4414,13 @@ export default function App() {
// Rest behalten (Wände und 2D-Zeichnungen unabhängig, gemischte Auswahl
// erlaubt). Leerraum-Klick mit Shift lässt die Auswahl unverändert.
if (sel.shift) {
// Schnittlinie ist Einzelauswahl — Shift+Klick togglet sie an/aus.
if (sel.sectionLineId) {
setSelectedSectionLineId(
selectedSectionLineId === sel.sectionLineId ? null : sel.sectionLineId,
);
return;
}
if (sel.wallId) {
const id = sel.wallId;
setSelectedWallIds(
@@ -4454,8 +4502,12 @@ export default function App() {
if (keep !== "extrudedSolid") setSelectedExtrudedSolidIds([]);
if (keep !== "column") setSelectedColumnIds([]);
if (keep !== "roof") setSelectedRoofIds([]);
if (keep !== "sectionLine") setSelectedSectionLineId(null);
};
if (sel.openingId) {
if (sel.sectionLineId) {
setSelectedSectionLineId(sel.sectionLineId);
clearOthers("sectionLine");
} else if (sel.openingId) {
setSelectedOpeningIds([sel.openingId]);
clearOthers("opening");
} else if (sel.wallId) {
@@ -4500,6 +4552,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
};
// Rechts-Klick im Grundriss: Wand-Kontextmenü. Trifft der Klick eine Wand, die
// NICHT bereits in der Auswahl ist, wird die Auswahl auf sie gesetzt (damit die
@@ -4526,6 +4579,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
}
};
// Links-Klick im 3D: per Raycast getroffene Treppe wählen (Einzelauswahl).
@@ -4540,6 +4594,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
}
};
// Links-Klick im 3D: per Raycast getroffene Decke wählen (Einzelauswahl).
@@ -4554,6 +4609,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
}
};
// Links-Klick im 3D: per Raycast getroffene Öffnung wählen (Einzelauswahl).
@@ -4568,6 +4624,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
}
};
// Links-KLICK im Nordstern-3D-Viewport (WASM, TS-Raycast): getroffenes Bauteil
@@ -4587,6 +4644,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
return;
}
// Öffnung (Fenster/Tür) und Treppe sind jetzt auch im WASM-Viewport pickbar
@@ -4602,6 +4660,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
return;
}
if (hit.kind === "stair") {
@@ -4614,6 +4673,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
return;
}
if (hit.kind === "roof") {
@@ -4646,6 +4706,7 @@ export default function App() {
setSelectedExtrudedSolidIds([]);
setSelectedColumnIds([]);
setSelectedRoofIds([]);
setSelectedSectionLineId(null);
};
// Rechts-Klick im 3D: Wand-Kontextmenü an der Cursorposition.
const onViewportContextMenu = (info: ViewportContextInfo) => {
@@ -4941,6 +5002,7 @@ export default function App() {
selectedColumnIds={selectedColumnIds}
selectedRoomIds={selectedRoomIds}
selectedRoofIds={selectedRoofIds}
selectedSectionLineId={selectedSectionLineId}
roomStamp={roomStamp}
onStampMove={moveRoomStamp}
onStampEdit={onOpenStampEditor}
@@ -6074,6 +6136,7 @@ function Content({
selectedColumnIds,
selectedRoomIds,
selectedRoofIds,
selectedSectionLineId,
roomStamp,
onStampMove,
onStampEdit,
@@ -6152,6 +6215,7 @@ function Content({
selectedColumnIds: string[];
selectedRoomIds: string[];
selectedRoofIds: string[];
selectedSectionLineId: string | null;
roomStamp: { roomId: string; anchor: Vec2 } | null;
onStampMove: (roomId: string, delta: Vec2) => void;
onStampEdit: (roomId: string) => void;
@@ -6269,6 +6333,7 @@ function Content({
selectedColumnIds={selectedColumnIds}
selectedRoomIds={selectedRoomIds}
selectedRoofIds={selectedRoofIds}
selectedSectionLineId={selectedSectionLineId}
roomStamp={roomStamp}
onStampMove={onStampMove}
onStampEdit={onStampEdit}
@@ -6324,6 +6389,7 @@ function Content({
selectedColumnIds={selectedColumnIds}
selectedRoomIds={selectedRoomIds}
selectedRoofIds={selectedRoofIds}
selectedSectionLineId={selectedSectionLineId}
roomStamp={roomStamp}
onStampMove={onStampMove}
onStampEdit={onStampEdit}
@@ -6419,6 +6485,7 @@ function Content({
selectedColumnIds={selectedColumnIds}
selectedRoomIds={selectedRoomIds}
selectedRoofIds={selectedRoofIds}
selectedSectionLineId={selectedSectionLineId}
roomStamp={roomStamp}
onStampMove={onStampMove}
onStampEdit={onStampEdit}
@@ -6464,6 +6531,7 @@ function LevelPlanView({
selectedColumnIds,
selectedRoomIds,
selectedRoofIds,
selectedSectionLineId,
roomStamp,
onStampMove,
onStampEdit,
@@ -6502,6 +6570,7 @@ function LevelPlanView({
selectedColumnIds: string[];
selectedRoomIds: string[];
selectedRoofIds: string[];
selectedSectionLineId: string | null;
roomStamp: { roomId: string; anchor: Vec2 } | null;
onStampMove: (roomId: string, delta: Vec2) => void;
onStampEdit: (roomId: string) => void;
@@ -6579,6 +6648,7 @@ function LevelPlanView({
selectedColumnIds={selectedColumnIds}
selectedRoomIds={selectedRoomIds}
selectedRoofIds={selectedRoofIds}
selectedSectionLineIds={selectedSectionLineId ? [selectedSectionLineId] : []}
roomStamp={roomStamp}
onStampMove={onStampMove}
onStampEdit={onStampEdit}
@@ -6632,6 +6702,7 @@ function SectionPlanView({
selectedColumnIds,
selectedRoomIds,
selectedRoofIds,
selectedSectionLineId,
roomStamp,
onStampMove,
onStampEdit,
@@ -6666,6 +6737,7 @@ function SectionPlanView({
selectedColumnIds: string[];
selectedRoomIds: string[];
selectedRoofIds: string[];
selectedSectionLineId: string | null;
roomStamp: { roomId: string; anchor: Vec2 } | null;
onStampMove: (roomId: string, delta: Vec2) => void;
onStampEdit: (roomId: string) => void;
@@ -6767,6 +6839,7 @@ function SectionPlanView({
selectedColumnIds={selectedColumnIds}
selectedRoomIds={selectedRoomIds}
selectedRoofIds={selectedRoofIds}
selectedSectionLineIds={selectedSectionLineId ? [selectedSectionLineId] : []}
roomStamp={roomStamp}
onStampMove={onStampMove}
onStampEdit={onStampEdit}