Wandtyp-Kürzel: Component.abbrev + wallTypeLabel() + Kürzel-Feld im ResourceManager; Geschoss-Dropdown schmal
This commit is contained in:
@@ -545,6 +545,9 @@ export const de = {
|
||||
"resources.components.placeholder": "Bauteil",
|
||||
"resources.col.name": "Name",
|
||||
"resources.col.color": "Farbe",
|
||||
"resources.col.abbrev": "Kürzel",
|
||||
"resources.col.abbrev.hint": "Kurz-Code für Wandtyp-Dropdowns (z. B. «BET», «HLZ»)",
|
||||
"resources.col.abbrev.placeholder": "z. B. BET",
|
||||
"resources.col.hatch": "Schnittschraffur",
|
||||
"resources.col.viewHatch": "Ansichtsschraffur",
|
||||
"resources.viewHatch.none": "(keine)",
|
||||
|
||||
@@ -539,6 +539,9 @@ export const en: Record<TranslationKey, string> = {
|
||||
"resources.components.placeholder": "Component",
|
||||
"resources.col.name": "Name",
|
||||
"resources.col.color": "Color",
|
||||
"resources.col.abbrev": "Code",
|
||||
"resources.col.abbrev.hint": "Short code for wall-type dropdowns (e.g. «CON», «BRK»)",
|
||||
"resources.col.abbrev.placeholder": "e.g. CON",
|
||||
"resources.col.hatch": "Section hatch",
|
||||
"resources.col.viewHatch": "View hatch",
|
||||
"resources.viewHatch.none": "(none)",
|
||||
|
||||
@@ -227,6 +227,12 @@ export interface Component {
|
||||
* Render-Modus „textured".
|
||||
*/
|
||||
material?: ComponentMaterial;
|
||||
/**
|
||||
* Optionales Kürzel für Wandtyp-Labels (z. B. „BET", „HLZ", „GKB", „DAE").
|
||||
* Wird von `wallTypeLabel()` genutzt, um Wandtyp-Dropdowns kompakt darzustellen
|
||||
* (z. B. „BET 24" statt dem vollen Namen). Fehlt es, greift der Name.
|
||||
*/
|
||||
abbrev?: string;
|
||||
/** Verschneidungs-Rang: höher läuft am Stoß durch (Backbone). */
|
||||
joinPriority: number;
|
||||
}
|
||||
@@ -1374,6 +1380,35 @@ export const collectVisibleCodes = (layers: LayerCategory[]): Set<string> => {
|
||||
export const wallTypeThickness = (wt: WallType): number =>
|
||||
wt.layers.reduce((sum, l) => sum + l.thickness, 0);
|
||||
|
||||
/**
|
||||
* Kompakter Anzeigetext für einen Wandtyp im Dropdown (VW-Stil).
|
||||
* - Einschichtig mit Kürzel: „BET 24" (Kürzel + Dicke in cm)
|
||||
* - Mehrschichtig mit Kürzeln: „GKB·BET·DAE" + Gesamtdicke „(25.5)"
|
||||
* - Ohne Kürzel: Name bleibt — kein Rückfall auf rohen Namen, nur keine Kurzform.
|
||||
*
|
||||
* `project.components` wird genutzt, um `Component.abbrev` aufzulösen.
|
||||
* Ist kein abbrev gesetzt, erscheint der volle Name unverändert.
|
||||
*/
|
||||
export function wallTypeLabel(
|
||||
wt: WallType | CeilingType,
|
||||
components: Component[],
|
||||
): string {
|
||||
const resolve = (id: string) => components.find((c) => c.id === id);
|
||||
const totalCm = +(wallTypeThickness(wt) * 100).toFixed(1);
|
||||
// cm-Zahl: "24" statt "24.0", "17.5" bleibt "17.5"
|
||||
const cmStr = totalCm % 1 === 0 ? String(totalCm | 0) : String(totalCm);
|
||||
|
||||
// Alle Schichten haben ein Kürzel → Kurzform bauen
|
||||
const abbrevs = wt.layers.map((l) => resolve(l.componentId)?.abbrev ?? "");
|
||||
const allHaveAbbrev = abbrevs.every((a) => a.length > 0);
|
||||
if (allHaveAbbrev) {
|
||||
if (wt.layers.length === 1) return `${abbrevs[0]} ${cmStr}`;
|
||||
return `${abbrevs.join("·")} (${cmStr})`;
|
||||
}
|
||||
// Fallback: voller Name
|
||||
return wt.name;
|
||||
}
|
||||
|
||||
/** Formatiert Meter mit zwei Nachkommastellen, z. B. "0.35 m". */
|
||||
export const formatM = (meters: number): string => meters.toFixed(2) + " m";
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { t } from "../i18n";
|
||||
import { formatM } from "../model/types";
|
||||
import { formatM, wallTypeLabel } from "../model/types";
|
||||
import { PEN_WEIGHTS } from "../model/types";
|
||||
import type { AttributeSource } from "../model/types";
|
||||
import { usePanelHost } from "./host";
|
||||
@@ -75,7 +75,11 @@ export function AttributesPanel() {
|
||||
value={host.activeWallTypeId}
|
||||
disabled={!host.toolsEnabled}
|
||||
onChange={(v) => host.onActiveWallTypeId(v)}
|
||||
options={project.wallTypes.map((wt) => ({ value: wt.id, label: wt.name }))}
|
||||
options={project.wallTypes.map((wt) => ({
|
||||
value: wt.id,
|
||||
label: wallTypeLabel(wt, project.components),
|
||||
title: wt.name,
|
||||
}))}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -581,10 +581,8 @@ export function CeilingSection({
|
||||
onChange={(id) => host.onSetCeilingType(id)}
|
||||
options={ceiling.ceilingTypes.map((ct) => ({
|
||||
value: ct.id,
|
||||
label: t("objinfo.wall.presetLabel", {
|
||||
name: ct.name,
|
||||
thickness: ct.thickness.toFixed(2),
|
||||
}),
|
||||
label: ct.label,
|
||||
title: ct.name,
|
||||
}))}
|
||||
title={t("objinfo.ceiling.preset")}
|
||||
width={150}
|
||||
@@ -717,10 +715,8 @@ export function WallSection({
|
||||
onChange={(id) => host.onSetWallType(id)}
|
||||
options={wall.wallTypes.map((wt) => ({
|
||||
value: wt.id,
|
||||
label: t("objinfo.wall.presetLabel", {
|
||||
name: wt.name,
|
||||
thickness: wt.thickness.toFixed(2),
|
||||
}),
|
||||
label: wt.label,
|
||||
title: wt.name,
|
||||
}))}
|
||||
title={t("objinfo.wall.preset")}
|
||||
width={150}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
flattenCategories,
|
||||
getCeilingType,
|
||||
getWallType,
|
||||
wallTypeLabel,
|
||||
wallTypeThickness,
|
||||
} from "../model/types";
|
||||
import type {
|
||||
@@ -48,6 +49,8 @@ import { stairGeometry, stairBBox } from "../geometry/stair";
|
||||
export interface WallTypeChoice {
|
||||
id: string;
|
||||
name: string;
|
||||
/** Kurz-Label für das Dropdown (aus wallTypeLabel: Kürzel+Dicke oder Name). */
|
||||
label: string;
|
||||
/** Gesamtdicke (Meter) — informativ im Dropdown-Label. */
|
||||
thickness: number;
|
||||
/** Anzahl Schichten (1 = einschichtig). */
|
||||
@@ -359,6 +362,7 @@ function wallSelection(project: Project, wall: Wall): Selection {
|
||||
wallTypes: project.wallTypes.map((t) => ({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
label: wallTypeLabel(t, project.components),
|
||||
thickness: wallTypeThickness(t),
|
||||
layerCount: t.layers.length,
|
||||
})),
|
||||
@@ -414,6 +418,7 @@ function ceilingSelection(project: Project, ceiling: Ceiling): Selection {
|
||||
ceilingTypes: (project.ceilingTypes ?? []).map((t) => ({
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
label: wallTypeLabel(t, project.components),
|
||||
thickness: wallTypeThickness(t),
|
||||
layerCount: t.layers.length,
|
||||
})),
|
||||
|
||||
+2
-1
@@ -4040,7 +4040,8 @@ body {
|
||||
padding-left: 56px;
|
||||
}
|
||||
.objinfo-subfield .tb-dd-trigger {
|
||||
width: 100%;
|
||||
width: auto;
|
||||
max-width: 140px;
|
||||
}
|
||||
|
||||
/* ── Attribute-Palette (AttributesPanel) ─────────────────────────────── */
|
||||
|
||||
@@ -763,6 +763,13 @@ function ComponentDetail({
|
||||
</div>
|
||||
|
||||
<div className="res-fields">
|
||||
<FieldRow label={t("resources.col.abbrev")} hint={t("resources.col.abbrev.hint")}>
|
||||
<TextField
|
||||
value={component.abbrev ?? ""}
|
||||
onChange={(v) => onPatch({ abbrev: v || undefined })}
|
||||
placeholder={t("resources.col.abbrev.placeholder")}
|
||||
/>
|
||||
</FieldRow>
|
||||
<FieldRow
|
||||
label={t("resources.col.foreground")}
|
||||
hint={t("resources.col.foreground.hint")}
|
||||
|
||||
Reference in New Issue
Block a user