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:
+33
@@ -1856,6 +1856,13 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
if (e.key !== "Delete" && e.key !== "Backspace") return;
|
if (e.key !== "Delete" && e.key !== "Backspace") return;
|
||||||
if (activeTransform) return; // während einer Transformation nicht löschen
|
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 (
|
if (
|
||||||
selectedDrawingIds.length ||
|
selectedDrawingIds.length ||
|
||||||
selectedWallIds.length ||
|
selectedWallIds.length ||
|
||||||
@@ -1918,6 +1925,7 @@ export default function App() {
|
|||||||
selectedExtrudedSolidIds,
|
selectedExtrudedSolidIds,
|
||||||
selectedColumnIds,
|
selectedColumnIds,
|
||||||
selectedRoofIds,
|
selectedRoofIds,
|
||||||
|
selectedSectionLineId,
|
||||||
project.walls,
|
project.walls,
|
||||||
project.drawings2d,
|
project.drawings2d,
|
||||||
project.ceilings,
|
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) ───────────────────────────────────────────────
|
// ── DXF/DWG-Import (Dialog) ───────────────────────────────────────────────
|
||||||
// Eine Datei (Button ODER Drop) öffnet den modalen Import-Dialog. DXF wird als
|
// Eine Datei (Button ODER Drop) öffnet den modalen Import-Dialog. DXF wird als
|
||||||
// Text geparst, DWG als ArrayBuffer über LibreDWG-WASM (lazy, asynchron); beide
|
// Text geparst, DWG als ArrayBuffer über LibreDWG-WASM (lazy, asynchron); beide
|
||||||
@@ -3323,6 +3346,14 @@ export default function App() {
|
|||||||
onSetRoofPatch: (patch) => {
|
onSetRoofPatch: (patch) => {
|
||||||
if (selection?.kind === "roof") updateRoof(selection.id, 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) ────────────────────
|
// ── Treppen-Attribute (nur bei selektierter Treppe) ────────────────────
|
||||||
onSetStairShape: (shape) => {
|
onSetStairShape: (shape) => {
|
||||||
if (selection?.kind === "stair") updateStair(selection.id, { shape });
|
if (selection?.kind === "stair") updateStair(selection.id, { shape });
|
||||||
@@ -3853,6 +3884,8 @@ export default function App() {
|
|||||||
detail,
|
detail,
|
||||||
scaleDenominator,
|
scaleDenominator,
|
||||||
selection,
|
selection,
|
||||||
|
sectionLine,
|
||||||
|
selectedSectionLineId,
|
||||||
selectedViewSnapshotId,
|
selectedViewSnapshotId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -503,6 +503,18 @@ export const de = {
|
|||||||
"objinfo.room.refFloor": "Referenzgeschoss",
|
"objinfo.room.refFloor": "Referenzgeschoss",
|
||||||
"objinfo.room.editStamp": "Stempel bearbeiten …",
|
"objinfo.room.editStamp": "Stempel bearbeiten …",
|
||||||
// ── Extrusions-Attribute (truck-Integration, Object-Info-Panel) ─────────
|
// ── 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.section": "Extrusion",
|
||||||
"objinfo.extrudedSolid.height": "Höhe",
|
"objinfo.extrudedSolid.height": "Höhe",
|
||||||
"objinfo.extrudedSolid.taper": "Verjüngung",
|
"objinfo.extrudedSolid.taper": "Verjüngung",
|
||||||
|
|||||||
@@ -500,6 +500,18 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
"objinfo.room.refFloor": "Reference floor",
|
"objinfo.room.refFloor": "Reference floor",
|
||||||
"objinfo.room.editStamp": "Edit stamp …",
|
"objinfo.room.editStamp": "Edit stamp …",
|
||||||
// ── Extruded solid attributes (truck integration, object info panel) ────
|
// ── 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.section": "Extrusion",
|
||||||
"objinfo.extrudedSolid.height": "Height",
|
"objinfo.extrudedSolid.height": "Height",
|
||||||
"objinfo.extrudedSolid.taper": "Taper",
|
"objinfo.extrudedSolid.taper": "Taper",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import {
|
|||||||
RoomSection,
|
RoomSection,
|
||||||
ExtrudedSolidSection,
|
ExtrudedSolidSection,
|
||||||
ColumnSection,
|
ColumnSection,
|
||||||
|
SectionLineSection,
|
||||||
} from "./ObjectInfoPanel";
|
} from "./ObjectInfoPanel";
|
||||||
|
|
||||||
/** UI-Zustand des 3-Optionen-Quellen-Dropdowns (nicht 1:1 das Modell-Feld:
|
/** 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
|
// Leerzustand: der Typ-Picker (falls ein Bauteil-Werkzeug aktiv ist), sonst ein
|
||||||
// kompakter Hinweis.
|
// kompakter Hinweis.
|
||||||
if (sel === null) {
|
if (sel === null) {
|
||||||
|
// Schnitt-/Ansichtslinie ist kein Projekt-Bauteil (kein `selection`) — sie
|
||||||
|
// kommt über host.sectionLine und wird hier im „Leerzustand" bearbeitet.
|
||||||
return (
|
return (
|
||||||
<div className="attr-panel">
|
<div className="attr-panel">
|
||||||
{typeSection}
|
{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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ import { t } from "../i18n";
|
|||||||
import type { TranslationKey } from "../i18n";
|
import type { TranslationKey } from "../i18n";
|
||||||
import { flattenCategories, formatM } from "../model/types";
|
import { flattenCategories, formatM } from "../model/types";
|
||||||
import type {
|
import type {
|
||||||
|
DrawingLevel,
|
||||||
RoofShape,
|
RoofShape,
|
||||||
SiaCategory,
|
SiaCategory,
|
||||||
SliceTermination,
|
SliceTermination,
|
||||||
StairShape,
|
StairShape,
|
||||||
|
Vec2,
|
||||||
VerticalAnchor,
|
VerticalAnchor,
|
||||||
WallReferenceLine,
|
WallReferenceLine,
|
||||||
} from "../model/types";
|
} 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) ───────────────────────
|
// ── Extrusions-Attribut-Abschnitt (truck-Integration) ───────────────────────
|
||||||
// Höhe editierbar (löst Re-Extrusion aus); Grundfläche + Geschoss read-only.
|
// Höhe editierbar (löst Re-Extrusion aus); Grundfläche + Geschoss read-only.
|
||||||
export function ExtrudedSolidSection({
|
export function ExtrudedSolidSection({
|
||||||
|
|||||||
@@ -330,6 +330,25 @@ export interface PanelHostValue {
|
|||||||
*/
|
*/
|
||||||
onSetRoofPatch: (patch: Partial<import("../model/types").Roof>) => void;
|
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) ───
|
// ── Treppen-Attribute (Object-Info-Panel; nur die selektierte Treppe) ───
|
||||||
/** Setzt die Grundform (gerade/L/Wendel). */
|
/** Setzt die Grundform (gerade/L/Wendel). */
|
||||||
onSetStairShape: (shape: StairShape) => void;
|
onSetStairShape: (shape: StairShape) => void;
|
||||||
|
|||||||
Reference in New Issue
Block a user