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 (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,
|
||||
]);
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user