Objektinfo: Klasse (Kategorie) + Zeichnungsebene editierbar im Kopf
VW-Muster ('Klasse'/'Ebene'): der Objektinfo-Kopf zeigt statt des read-only
Kategorie-Codes zwei Dropdowns — Grafik-Kategorie und Zeichnungsebene
(Geschoss), beide direkt änderbar. Wirkt auf Wand/Decke/Dach/Treppe/Raum/
2D-Zeichnung/Stütze/Extrusion; Öffnungen ohne Ebenen-Dropdown (Ebene kommt
aus der Wirts-Wand), Extrusionen ohne Kategorie (kein categoryCode am Element).
- Selection.floorId (alle Builder), host.onSetSelectionCategory/-Level,
App-Routing je Elementart (Store-Actions bzw. setProject für Stütze/Extrusion)
- 2D-Zeichnungen auf Nicht-Geschoss-Ebenen behalten ihre Ebene in der Liste
673/673 grün.
This commit is contained in:
+53
@@ -3076,6 +3076,59 @@ export default function App() {
|
|||||||
if (!sel2) return;
|
if (!sel2) return;
|
||||||
commitTransformOn(sel2, "move", [{ x: 0, y: 0 }, { x: dx, y: dy }], "move", 1);
|
commitTransformOn(sel2, "move", [{ x: 0, y: 0 }, { x: dx, y: dy }], "move", 1);
|
||||||
},
|
},
|
||||||
|
// Objektinfo-Kopf: Klasse (Grafik-Kategorie) + Zeichnungsebene ändern
|
||||||
|
// (VW-Muster „Klasse"/„Ebene"). Je Elementart der passende Store-Weg.
|
||||||
|
onSetSelectionCategory: (code) => {
|
||||||
|
if (!selection || !code) return;
|
||||||
|
const id = selection.id;
|
||||||
|
switch (selection.kind) {
|
||||||
|
case "wall": updateWall(id, { categoryCode: code }); break;
|
||||||
|
case "ceiling": updateCeiling(id, { categoryCode: code }); break;
|
||||||
|
case "roof": updateRoof(id, { categoryCode: code }); break;
|
||||||
|
case "opening": updateOpening(id, { categoryCode: code }); break;
|
||||||
|
case "stair": updateStair(id, { categoryCode: code }); break;
|
||||||
|
case "room": updateRoom(id, { categoryCode: code }); break;
|
||||||
|
case "drawing2d": updateDrawing2D(id, { categoryCode: code }); break;
|
||||||
|
case "column":
|
||||||
|
setProject((p) => ({
|
||||||
|
...p,
|
||||||
|
columns: (p.columns ?? []).map((c) =>
|
||||||
|
c.id === id ? { ...c, categoryCode: code } : c,
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
default: break; // extrudedSolid/door: keine Kategorie am Element
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSetSelectionLevel: (levelId) => {
|
||||||
|
if (!selection || !levelId) return;
|
||||||
|
const id = selection.id;
|
||||||
|
switch (selection.kind) {
|
||||||
|
case "wall": updateWall(id, { floorId: levelId }); break;
|
||||||
|
case "ceiling": updateCeiling(id, { floorId: levelId }); break;
|
||||||
|
case "roof": updateRoof(id, { floorId: levelId }); break;
|
||||||
|
case "stair": updateStair(id, { floorId: levelId }); break;
|
||||||
|
case "room": updateRoom(id, { floorId: levelId }); break;
|
||||||
|
case "drawing2d": updateDrawing2D(id, { levelId }); break;
|
||||||
|
case "extrudedSolid":
|
||||||
|
setProject((p) => ({
|
||||||
|
...p,
|
||||||
|
extrudedSolids: (p.extrudedSolids ?? []).map((s) =>
|
||||||
|
s.id === id ? { ...s, levelId } : s,
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
case "column":
|
||||||
|
setProject((p) => ({
|
||||||
|
...p,
|
||||||
|
columns: (p.columns ?? []).map((c) =>
|
||||||
|
c.id === id ? { ...c, floorId: levelId } : c,
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
default: break; // opening: Ebene kommt aus der Wirts-Wand
|
||||||
|
}
|
||||||
|
},
|
||||||
onRotateSelectionAround: (cx, cy, deg) => {
|
onRotateSelectionAround: (cx, cy, deg) => {
|
||||||
if (!selection) return;
|
if (!selection) return;
|
||||||
if (!isFinite(deg) || deg === 0) return;
|
if (!isFinite(deg) || deg === 0) return;
|
||||||
|
|||||||
@@ -266,6 +266,8 @@ export const de = {
|
|||||||
// Objekt-Info-Palette (ObjectInfoPanel)
|
// Objekt-Info-Palette (ObjectInfoPanel)
|
||||||
"objinfo.title": "Objekt-Info",
|
"objinfo.title": "Objekt-Info",
|
||||||
"objinfo.empty": "Kein Objekt gewählt",
|
"objinfo.empty": "Kein Objekt gewählt",
|
||||||
|
"objinfo.category": "Klasse",
|
||||||
|
"objinfo.level": "Ebene",
|
||||||
"objinfo.refpoint": "Bezugspunkt",
|
"objinfo.refpoint": "Bezugspunkt",
|
||||||
"objinfo.setRefpoint": "Bezugspunkt wählen",
|
"objinfo.setRefpoint": "Bezugspunkt wählen",
|
||||||
"objinfo.x": "X",
|
"objinfo.x": "X",
|
||||||
|
|||||||
@@ -265,6 +265,8 @@ export const en: Record<TranslationKey, string> = {
|
|||||||
// Objekt-Info-Palette (ObjectInfoPanel)
|
// Objekt-Info-Palette (ObjectInfoPanel)
|
||||||
"objinfo.title": "Object Info",
|
"objinfo.title": "Object Info",
|
||||||
"objinfo.empty": "No object selected",
|
"objinfo.empty": "No object selected",
|
||||||
|
"objinfo.category": "Class",
|
||||||
|
"objinfo.level": "Layer",
|
||||||
"objinfo.refpoint": "Reference point",
|
"objinfo.refpoint": "Reference point",
|
||||||
"objinfo.setRefpoint": "Set reference point",
|
"objinfo.setRefpoint": "Set reference point",
|
||||||
"objinfo.x": "X",
|
"objinfo.x": "X",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
import type { TranslationKey } from "../i18n";
|
import type { TranslationKey } from "../i18n";
|
||||||
import { formatM } from "../model/types";
|
import { flattenCategories, formatM } from "../model/types";
|
||||||
import type {
|
import type {
|
||||||
RoofShape,
|
RoofShape,
|
||||||
SiaCategory,
|
SiaCategory,
|
||||||
@@ -110,13 +110,43 @@ export function ObjectInfoPanel() {
|
|||||||
return (
|
return (
|
||||||
<div className="objinfo-panel">
|
<div className="objinfo-panel">
|
||||||
{m && <MeasurementSection m={m} />}
|
{m && <MeasurementSection m={m} />}
|
||||||
{/* Kopfzeile: Typ + Kategorie. Die element-spezifischen Attribut-Abschnitte
|
{/* Kopfzeile: Typ + editierbare Klasse (Grafik-Kategorie) + Zeichnungsebene
|
||||||
(Wand/Decke/Öffnung/Treppe/Raum) liegen jetzt im Attribute-Panel;
|
(VW-Muster „Klasse"/„Ebene"). Die element-spezifischen Attribut-
|
||||||
ObjectInfo trägt nur noch Bezugspunkt + Masse. */}
|
Abschnitte (Wand/Decke/Öffnung/Treppe/Raum) liegen im Attribute-Panel. */}
|
||||||
<div className="objinfo-head">
|
<div className="objinfo-head">
|
||||||
<span className="objinfo-kind">{t(`objinfo.kind.${sel.kind}`)}</span>
|
<span className="objinfo-kind">{t(`objinfo.kind.${sel.kind}`)}</span>
|
||||||
<span className="objinfo-cat">{sel.categoryCode}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
{sel.categoryCode !== "" && (
|
||||||
|
<div className="objinfo-field">
|
||||||
|
<span className="objinfo-flabel">{t("objinfo.category")}</span>
|
||||||
|
<Dropdown
|
||||||
|
value={sel.categoryCode}
|
||||||
|
onChange={(v) => host.onSetSelectionCategory(v)}
|
||||||
|
options={flattenCategories(host.project.layers).map((c) => ({
|
||||||
|
value: c.code,
|
||||||
|
label: `${c.code} ${c.name}`,
|
||||||
|
}))}
|
||||||
|
title={t("objinfo.category")}
|
||||||
|
width={150}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{sel.floorId != null && (
|
||||||
|
<div className="objinfo-field">
|
||||||
|
<span className="objinfo-flabel">{t("objinfo.level")}</span>
|
||||||
|
<Dropdown
|
||||||
|
value={sel.floorId}
|
||||||
|
onChange={(v) => host.onSetSelectionLevel(v)}
|
||||||
|
options={host.project.drawingLevels
|
||||||
|
// Geschosse + die aktuelle Ebene (2D-Zeichnungen können auch auf
|
||||||
|
// Schnitt-/Zeichnungs-Ebenen liegen — die darf nicht verschwinden).
|
||||||
|
.filter((z) => z.kind === "floor" || z.id === sel.floorId)
|
||||||
|
.map((z) => ({ value: z.id, label: z.name }))}
|
||||||
|
title={t("objinfo.level")}
|
||||||
|
width={150}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="objinfo-sep" />
|
<div className="objinfo-sep" />
|
||||||
|
|
||||||
|
|||||||
@@ -214,6 +214,18 @@ export interface PanelHostValue {
|
|||||||
* diese Elementarten nicht) ein No-op.
|
* diese Elementarten nicht) ein No-op.
|
||||||
*/
|
*/
|
||||||
onMoveSelectionBy: (dx: number, dy: number) => void;
|
onMoveSelectionBy: (dx: number, dy: number) => void;
|
||||||
|
/**
|
||||||
|
* Setzt die Grafik-Kategorie (Klasse/Ebene) des selektierten Elements —
|
||||||
|
* editierbar im Objektinfo-Kopf (VW: „Klasse"). Wirkt auf alle Elementarten
|
||||||
|
* mit `categoryCode`.
|
||||||
|
*/
|
||||||
|
onSetSelectionCategory: (code: string) => void;
|
||||||
|
/**
|
||||||
|
* Verschiebt das selektierte Element auf eine andere Zeichnungsebene
|
||||||
|
* (Geschoss) — editierbar im Objektinfo-Kopf (VW: „Ebene"). No-op bei
|
||||||
|
* Öffnungen (deren Ebene kommt aus der Wirts-Wand).
|
||||||
|
*/
|
||||||
|
onSetSelectionLevel: (levelId: string) => void;
|
||||||
/**
|
/**
|
||||||
* Dreht die Selektion um `deg` Grad um den Weltpunkt (cx,cy) — generischer
|
* Dreht die Selektion um `deg` Grad um den Weltpunkt (cx,cy) — generischer
|
||||||
* Rotate über das Transform-System; deckt dieselben Selektionsarten ab wie
|
* Rotate über das Transform-System; deckt dieselben Selektionsarten ab wie
|
||||||
|
|||||||
@@ -345,6 +345,12 @@ export interface Selection {
|
|||||||
id: string;
|
id: string;
|
||||||
/** Grafik-Kategorie (Ebene) des Elements. */
|
/** Grafik-Kategorie (Ebene) des Elements. */
|
||||||
categoryCode: string;
|
categoryCode: string;
|
||||||
|
/**
|
||||||
|
* Zeichnungsebene (Geschoss) des Elements — editierbar im Objektinfo-Kopf
|
||||||
|
* (VW: „Ebene"). Bei Öffnungen `undefined` (die Ebene kommt aus der
|
||||||
|
* Wirts-Wand, ein Verschieben auf eine andere Ebene ergäbe keinen Sinn).
|
||||||
|
*/
|
||||||
|
floorId?: string;
|
||||||
/** Effektiv aufgelöste Strich-/Umrandungsfarbe (hex). */
|
/** Effektiv aufgelöste Strich-/Umrandungsfarbe (hex). */
|
||||||
color: string;
|
color: string;
|
||||||
/** Effektiv aufgelöste Strichstärke in Millimetern. */
|
/** Effektiv aufgelöste Strichstärke in Millimetern. */
|
||||||
@@ -550,6 +556,7 @@ function wallSelection(project: Project, wall: Wall): Selection {
|
|||||||
kind: "wall",
|
kind: "wall",
|
||||||
id: wall.id,
|
id: wall.id,
|
||||||
categoryCode: wall.categoryCode,
|
categoryCode: wall.categoryCode,
|
||||||
|
floorId: wall.floorId,
|
||||||
color,
|
color,
|
||||||
weightMm,
|
weightMm,
|
||||||
fillHatchId: wall.hatchId,
|
fillHatchId: wall.hatchId,
|
||||||
@@ -604,6 +611,7 @@ function ceilingSelection(project: Project, ceiling: Ceiling): Selection {
|
|||||||
kind: "ceiling",
|
kind: "ceiling",
|
||||||
id: ceiling.id,
|
id: ceiling.id,
|
||||||
categoryCode: ceiling.categoryCode,
|
categoryCode: ceiling.categoryCode,
|
||||||
|
floorId: ceiling.floorId,
|
||||||
color,
|
color,
|
||||||
weightMm,
|
weightMm,
|
||||||
fillHatchId: ceiling.hatchId,
|
fillHatchId: ceiling.hatchId,
|
||||||
@@ -653,6 +661,7 @@ function roofSelection(project: Project, roof: Roof): Selection {
|
|||||||
kind: "roof",
|
kind: "roof",
|
||||||
id: roof.id,
|
id: roof.id,
|
||||||
categoryCode: roof.categoryCode,
|
categoryCode: roof.categoryCode,
|
||||||
|
floorId: roof.floorId,
|
||||||
color,
|
color,
|
||||||
weightMm,
|
weightMm,
|
||||||
closed: true,
|
closed: true,
|
||||||
@@ -751,6 +760,7 @@ function stairSelection(project: Project, s: Stair): Selection {
|
|||||||
kind: "stair",
|
kind: "stair",
|
||||||
id: s.id,
|
id: s.id,
|
||||||
categoryCode: s.categoryCode,
|
categoryCode: s.categoryCode,
|
||||||
|
floorId: s.floorId,
|
||||||
color,
|
color,
|
||||||
weightMm,
|
weightMm,
|
||||||
fillHatchId: undefined,
|
fillHatchId: undefined,
|
||||||
@@ -791,6 +801,7 @@ function roomSelection(project: Project, r: Room): Selection {
|
|||||||
kind: "room",
|
kind: "room",
|
||||||
id: r.id,
|
id: r.id,
|
||||||
categoryCode: r.categoryCode,
|
categoryCode: r.categoryCode,
|
||||||
|
floorId: r.floorId,
|
||||||
color,
|
color,
|
||||||
weightMm,
|
weightMm,
|
||||||
fillHatchId: undefined,
|
fillHatchId: undefined,
|
||||||
@@ -829,6 +840,7 @@ function extrudedSolidSelection(project: Project, s: ExtrudedSolid): Selection {
|
|||||||
kind: "extrudedSolid",
|
kind: "extrudedSolid",
|
||||||
id: s.id,
|
id: s.id,
|
||||||
categoryCode: "",
|
categoryCode: "",
|
||||||
|
floorId: s.levelId,
|
||||||
color: EXTRUSION_COLOR,
|
color: EXTRUSION_COLOR,
|
||||||
weightMm: WALL_FALLBACK_MM,
|
weightMm: WALL_FALLBACK_MM,
|
||||||
fillHatchId: null,
|
fillHatchId: null,
|
||||||
@@ -872,6 +884,7 @@ function columnSelection(project: Project, col: Column): Selection {
|
|||||||
kind: "column",
|
kind: "column",
|
||||||
id: col.id,
|
id: col.id,
|
||||||
categoryCode: col.categoryCode,
|
categoryCode: col.categoryCode,
|
||||||
|
floorId: col.floorId,
|
||||||
color: col.color ?? category?.color ?? COLUMN_COLOR,
|
color: col.color ?? category?.color ?? COLUMN_COLOR,
|
||||||
weightMm: WALL_FALLBACK_MM,
|
weightMm: WALL_FALLBACK_MM,
|
||||||
bbox: { minX, minY, maxX, maxY },
|
bbox: { minX, minY, maxX, maxY },
|
||||||
@@ -900,6 +913,7 @@ function drawingSelection(project: Project, d: Drawing2D): Selection {
|
|||||||
kind: "drawing2d",
|
kind: "drawing2d",
|
||||||
id: d.id,
|
id: d.id,
|
||||||
categoryCode: d.categoryCode,
|
categoryCode: d.categoryCode,
|
||||||
|
floorId: d.levelId,
|
||||||
color,
|
color,
|
||||||
weightMm,
|
weightMm,
|
||||||
fillHatchId: closed ? d.hatchId ?? null : null,
|
fillHatchId: closed ? d.hatchId ?? null : null,
|
||||||
|
|||||||
Reference in New Issue
Block a user