Schnittlinie editierbar: Objektinfo-Sektion + Entf-Taste

Gewaehlte Schnitt-/Ansichtslinie erscheint als SectionLineSection im Attribut-
Panel (ueber host.sectionLine, da eine Linie kein Projekt-Bauteil ist): Name,
Blickrichtung umkehren (directionSign-Flip), Endpunkt-Koordinaten und Schnitt-
Tiefe editierbar; Loeschen/Entf setzt linePoints zurueck (Ebene bleibt). Host-
Kontrakt um sectionLine/onSetSectionLinePatch/onDeleteSectionLine erweitert,
verdrahtet ueber patchLevel. i18n de/en.
This commit is contained in:
2026-07-11 00:26:49 +02:00
parent 39bceedc17
commit ef7eb295d9
6 changed files with 197 additions and 1 deletions
+33
View File
@@ -1856,6 +1856,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 ||
@@ -1918,6 +1925,7 @@ export default function App() {
selectedExtrudedSolidIds,
selectedColumnIds,
selectedRoofIds,
selectedSectionLineId,
project.walls,
project.drawings2d,
project.ceilings,
@@ -2711,6 +2719,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
@@ -3323,6 +3346,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 });
@@ -3853,6 +3884,8 @@ export default function App() {
detail,
scaleDenominator,
selection,
sectionLine,
selectedSectionLineId,
selectedViewSnapshotId,
]);
+12
View File
@@ -503,6 +503,18 @@ export const de = {
"objinfo.room.refFloor": "Referenzgeschoss",
"objinfo.room.editStamp": "Stempel bearbeiten …",
// ── Extrusions-Attribute (truck-Integration, Object-Info-Panel) ─────────
"objinfo.sectionLine.section": "Schnittlinie",
"objinfo.sectionLine.kind.section": "Schnitt",
"objinfo.sectionLine.kind.elevation": "Ansicht",
"objinfo.sectionLine.name": "Name",
"objinfo.sectionLine.flip": "Blickrichtung umkehren",
"objinfo.sectionLine.startX": "Start X",
"objinfo.sectionLine.startY": "Start Y",
"objinfo.sectionLine.endX": "Ende X",
"objinfo.sectionLine.endY": "Ende Y",
"objinfo.sectionLine.depth": "Schnitt-Tiefe (m)",
"objinfo.sectionLine.depthHint": "leer = unbegrenzt",
"objinfo.sectionLine.delete": "Schnittlinie löschen",
"objinfo.extrudedSolid.section": "Extrusion",
"objinfo.extrudedSolid.height": "Höhe",
"objinfo.extrudedSolid.taper": "Verjüngung",
+12
View File
@@ -500,6 +500,18 @@ export const en: Record<TranslationKey, string> = {
"objinfo.room.refFloor": "Reference floor",
"objinfo.room.editStamp": "Edit stamp …",
// ── Extruded solid attributes (truck integration, object info panel) ────
"objinfo.sectionLine.section": "Section line",
"objinfo.sectionLine.kind.section": "Section",
"objinfo.sectionLine.kind.elevation": "Elevation",
"objinfo.sectionLine.name": "Name",
"objinfo.sectionLine.flip": "Flip view direction",
"objinfo.sectionLine.startX": "Start X",
"objinfo.sectionLine.startY": "Start Y",
"objinfo.sectionLine.endX": "End X",
"objinfo.sectionLine.endY": "End Y",
"objinfo.sectionLine.depth": "Section depth (m)",
"objinfo.sectionLine.depthHint": "empty = unlimited",
"objinfo.sectionLine.delete": "Delete section line",
"objinfo.extrudedSolid.section": "Extrusion",
"objinfo.extrudedSolid.height": "Height",
"objinfo.extrudedSolid.taper": "Taper",
+7 -1
View File
@@ -42,6 +42,7 @@ import {
RoomSection,
ExtrudedSolidSection,
ColumnSection,
SectionLineSection,
} from "./ObjectInfoPanel";
/** UI-Zustand des 3-Optionen-Quellen-Dropdowns (nicht 1:1 das Modell-Feld:
@@ -92,10 +93,15 @@ export function AttributesPanel() {
// Leerzustand: der Typ-Picker (falls ein Bauteil-Werkzeug aktiv ist), sonst ein
// kompakter Hinweis.
if (sel === null) {
// Schnitt-/Ansichtslinie ist kein Projekt-Bauteil (kein `selection`) — sie
// kommt über host.sectionLine und wird hier im „Leerzustand" bearbeitet.
return (
<div className="attr-panel">
{typeSection}
{!typeSection && <div className="attr-empty">{t("attr.empty")}</div>}
{host.sectionLine && <SectionLineSection level={host.sectionLine} host={host} />}
{!typeSection && !host.sectionLine && (
<div className="attr-empty">{t("attr.empty")}</div>
)}
</div>
);
}
+114
View File
@@ -19,10 +19,12 @@ import { t } from "../i18n";
import type { TranslationKey } from "../i18n";
import { flattenCategories, formatM } from "../model/types";
import type {
DrawingLevel,
RoofShape,
SiaCategory,
SliceTermination,
StairShape,
Vec2,
VerticalAnchor,
WallReferenceLine,
} from "../model/types";
@@ -314,6 +316,118 @@ export function RoomSection({
);
}
// ── Schnitt-/Ansichtslinien-Abschnitt (Object-Info; nur die gewählte Linie) ──
// Name (editierbar), Blickrichtung umkehren (directionSign-Flip), Endpunkt-
// Koordinaten (editierbar) + Schnitt-Tiefe; Löschen setzt linePoints zurück
// (die Ebene bleibt bestehen). Eine Schnittlinie ist KEIN Projekt-Bauteil,
// darum kommt sie über `host.sectionLine` (nicht über `selection`).
export function SectionLineSection({
level,
host,
}: {
level: DrawingLevel;
host: ReturnType<typeof usePanelHost>;
}) {
const line = level.linePoints;
const kindLabel =
level.kind === "elevation"
? t("objinfo.sectionLine.kind.elevation")
: t("objinfo.sectionLine.kind.section");
const patchPoint = (which: 0 | 1, axis: "x" | "y", value: number) => {
if (!line) return;
const next: [Vec2, Vec2] = [{ ...line[0] }, { ...line[1] }];
next[which] = { ...next[which], [axis]: value };
host.onSetSectionLinePatch({ linePoints: next });
};
const coordField = (labelKey: TranslationKey, value: number, on: (v: number) => void) => (
<div className="objinfo-field">
<span className="objinfo-flabel">{t(labelKey)}</span>
<input
type="number"
className="objinfo-text"
step={0.1}
value={value}
onChange={(e) => {
const v = Number(e.target.value);
if (Number.isFinite(v)) on(v);
}}
title={t(labelKey)}
/>
</div>
);
return (
<>
<div className="objinfo-sep" />
<div className="objinfo-section-label">
{t("objinfo.sectionLine.section")} · {kindLabel}
</div>
{/* Name (editierbar). */}
<div className="objinfo-field">
<span className="objinfo-flabel">{t("objinfo.sectionLine.name")}</span>
<input
type="text"
className="objinfo-text"
value={level.name}
onChange={(e) => host.onSetSectionLinePatch({ name: e.target.value })}
title={t("objinfo.sectionLine.name")}
/>
</div>
{/* Blickrichtung umkehren (directionSign-Flip). */}
<button
type="button"
className="objinfo-btn"
onClick={() =>
host.onSetSectionLinePatch({
directionSign: (level.directionSign ?? 1) === 1 ? -1 : 1,
})
}
>
{t("objinfo.sectionLine.flip")}
</button>
{/* Endpunkt-Koordinaten (nur wenn eine Linie gesetzt ist). */}
{line && (
<>
{coordField("objinfo.sectionLine.startX", line[0].x, (v) => patchPoint(0, "x", v))}
{coordField("objinfo.sectionLine.startY", line[0].y, (v) => patchPoint(0, "y", v))}
{coordField("objinfo.sectionLine.endX", line[1].x, (v) => patchPoint(1, "x", v))}
{coordField("objinfo.sectionLine.endY", line[1].y, (v) => patchPoint(1, "y", v))}
</>
)}
{/* Schnitt-Tiefe (leer = unbegrenzt). */}
<div className="objinfo-field">
<span className="objinfo-flabel">{t("objinfo.sectionLine.depth")}</span>
<input
type="number"
className="objinfo-text"
step={0.5}
min={0}
value={level.depth ?? ""}
placeholder={t("objinfo.sectionLine.depthHint")}
onChange={(e) => {
const raw = e.target.value.trim();
if (raw === "") {
host.onSetSectionLinePatch({ depth: undefined });
return;
}
const v = Number(raw);
if (Number.isFinite(v) && v > 0) host.onSetSectionLinePatch({ depth: v });
}}
title={t("objinfo.sectionLine.depth")}
/>
</div>
{/* Löschen: linePoints zurücksetzen (die Ebene bleibt bestehen). */}
<button type="button" className="objinfo-btn" onClick={() => host.onDeleteSectionLine()}>
{t("objinfo.sectionLine.delete")}
</button>
</>
);
}
// ── Extrusions-Attribut-Abschnitt (truck-Integration) ───────────────────────
// Höhe editierbar (löst Re-Extrusion aus); Grundfläche + Geschoss read-only.
export function ExtrudedSolidSection({
+19
View File
@@ -330,6 +330,25 @@ export interface PanelHostValue {
*/
onSetRoofPatch: (patch: Partial<import("../model/types").Roof>) => void;
// ── Schnitt-/Ansichtslinie (Object-Info-Panel; nur die selektierte Linie) ──
/**
* Die aktuell gewählte Schnitt-/Ansichtsebene (DrawingLevel, kind
* "section"/"elevation"), oder `null`. Das Object-Info-Panel zeigt ihre
* Attribute (Name, Blickrichtung, Endpunkte, Tiefe), auch wenn `selection`
* null ist (eine Schnittlinie ist KEIN Projekt-Bauteil).
*/
sectionLine: DrawingLevel | null;
/**
* Immutable Änderung einer Schnitt-/Ansichtslinie (Name/directionSign/
* linePoints/depth) wirkt NUR auf die selektierte Linie.
*/
onSetSectionLinePatch: (patch: Partial<DrawingLevel>) => void;
/**
* Löscht die Schnittlinie der gewählten Ebene (`linePoints` = undefined); die
* Ebene selbst bleibt bestehen (der Schnitt zeigt dann wieder den Hinweis).
*/
onDeleteSectionLine: () => void;
// ── Treppen-Attribute (Object-Info-Panel; nur die selektierte Treppe) ───
/** Setzt die Grundform (gerade/L/Wendel). */
onSetStairShape: (shape: StairShape) => void;