From a3a6b51db43fa2e1b84940e47087934356cf32b4 Mon Sep 17 00:00:00 2001 From: Karim Date: Sat, 11 Jul 2026 00:20:49 +0200 Subject: [PATCH] =?UTF-8?q?Ansicht:=20Fl=C3=A4chen-Generator=20einbinden?= =?UTF-8?q?=20+=20Schatten-Umschalter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit App: kind "elevation" nutzt generateElevationPlan (synchron, ohne WASM) statt der Schnitt-Pipeline; Fallback-Hinweis bleibt bei fehlender Linie. Oberleiste (ViewRibbonTab): Umschalter "Schatten", nur bei aktiver Ansicht sichtbar, schreibt DrawingLevel.shadows. i18n-Keys in de/en. --- src/App.tsx | 39 ++++++++++++++++++++++++++++++++------- src/i18n/de.ts | 2 ++ src/i18n/en.ts | 2 ++ src/ui/TopBar.tsx | 28 ++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 77ec77f..0ac675c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -41,6 +41,7 @@ import { generatePlan, generateSectionPlan } from "./plan/generatePlan"; import type { CategoryDisplayResolver } from "./plan/generatePlan"; import { computeSection } from "./plan/toSection"; import type { SectionOutput } from "./plan/toSection"; +import { generateElevationPlan } from "./plan/toElevation"; import { pushNativeScene, pushNativeWalls } from "./plan/nativeSync"; import { selectionHighlightLines } from "./plan/toWalls3d"; import { parseDxf } from "./io/dxfParser"; @@ -3296,6 +3297,16 @@ export default function App() { renderModeEnabled={viewType === "perspektive"} planColorMode={planColorMode} onPlanColorMode={setPlanColorMode} + shadowsAvailable={activeLevel.kind === "elevation"} + shadows={activeLevel.shadows ?? false} + onShadows={(v) => + setProject((p) => ({ + ...p, + drawingLevels: p.drawingLevels.map((z) => + z.id === activeLevel.id ? { ...z, shadows: v } : z, + ), + })) + } combos={{ // Ebenen-Kombinationen: Speichern = Snapshot des Layer-Baums → // localStorage; Laden = Map lesen → Sichtbarkeits-Flags anwenden. @@ -4949,12 +4960,17 @@ function SectionPlanView({ selectedDrawingIds?: string[]; gripHandlers: GripHandlers; }) { + // Die Ansicht (kind "elevation") läuft NICHT über den WASM-Schnitt, sondern + // über den rein TS-seitigen Flächen-Generator (Painter, toElevation.ts) — + // synchron, kein Lazy-Load. Nur der echte Schnitt braucht die WASM-Pipeline. + const isElevation = level.kind === "elevation"; const [output, setOutput] = useState(null); const [status, setStatus] = useState<"loading" | "ready" | "empty" | "error">( "loading", ); useEffect(() => { + if (isElevation) return; // Ansicht: kein WASM-Schnitt (synchroner Generator). let disposed = false; setStatus("loading"); setOutput(null); @@ -4976,20 +4992,29 @@ function SectionPlanView({ return () => { disposed = true; }; - }, [project, level]); + }, [project, level, isElevation]); - const plan = useMemo( - () => (output ? generateSectionPlan(output, mono) : null), - [output, mono], + // Ansichts-Plan: gefüllte Fassadenansicht inkl. optionalem Schatten + // (level.shadows). Liefert null, wenn die Ebene keine Ansichtslinie trägt. + const elevationPlan = useMemo( + () => (isElevation ? generateElevationPlan(project, level, { mono }) : null), + [isElevation, project, level, mono], ); + const plan = isElevation + ? elevationPlan + : output + ? generateSectionPlan(output, mono) + : null; + const effStatus = isElevation ? (elevationPlan ? "ready" : "empty") : status; + const kindLabel = level.kind === "section" ? t("stub.section") : t("stub.elevation"); - if (!plan || status !== "ready") { + if (!plan || effStatus !== "ready") { const note = - status === "empty" + effStatus === "empty" ? t("section.empty", { kind: kindLabel }) - : status === "error" + : effStatus === "error" ? t("section.error", { kind: kindLabel }) : t("section.loading", { kind: kindLabel }); return ( diff --git a/src/i18n/de.ts b/src/i18n/de.ts index a22f5f7..566eb7e 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -462,6 +462,8 @@ export const de = { "section.empty": "{kind} — keine Schnittlinie im Grundriss gesetzt", "section.error": "{kind} — Berechnung fehlgeschlagen (render3d-WASM neu bauen?)", "section.legend": "{kind} · abgeleitete Projektion (render3d)", + "elevation.shadows": "Schatten", + "elevation.shadows.hint": "Schlagschatten auskragender Bauteile (45°) in der Ansicht ein-/ausblenden", "props.floorHeight": "Geschosshöhe", "props.cutHeight": "Schnitthöhe", "props.okff": "OKFF", diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 9df258d..e8f513a 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -457,6 +457,8 @@ export const en: Record = { "section.empty": "{kind} — no section line set in the plan", "section.error": "{kind} — computation failed (rebuild render3d WASM?)", "section.legend": "{kind} · derived projection (render3d)", + "elevation.shadows": "Shadows", + "elevation.shadows.hint": "Toggle 45° cast shadows of overhanging elements in the elevation", "props.floorHeight": "Floor height", "props.cutHeight": "Cut height", "props.okff": "FFL", diff --git a/src/ui/TopBar.tsx b/src/ui/TopBar.tsx index 9be63af..2d51497 100644 --- a/src/ui/TopBar.tsx +++ b/src/ui/TopBar.tsx @@ -990,6 +990,12 @@ export interface ViewRibbonTabProps { renderModeEnabled: boolean; planColorMode: PlanColorMode; onPlanColorMode: (m: PlanColorMode) => void; + /** Ob die aktive Ebene eine Ansicht (kind "elevation") ist — nur dann ist der + * Schatten-Umschalter sichtbar/aktiv. */ + shadowsAvailable: boolean; + /** Schlagschatten der aktiven Ansicht ein/aus (DrawingLevel.shadows). */ + shadows: boolean; + onShadows: (v: boolean) => void; combos: ComboMenuHandlers; /** Text-/Font-Formatierung auf DERSELBEN Leiste (Raumstempel-Ziel; `null` = * nur Defaults). Liegt bewusst im „Ansichten"-Tab (Nutzer-Wunsch). */ @@ -1027,6 +1033,9 @@ export function ViewRibbonTab({ renderModeEnabled, planColorMode, onPlanColorMode, + shadowsAvailable, + shadows, + onShadows, combos, textTarget, }: ViewRibbonTabProps) { @@ -1254,6 +1263,25 @@ export function ViewRibbonTab({ + {/* Schatten ein/aus — NUR für eine aktive Ansicht (kind "elevation"). + Schreibt DrawingLevel.shadows; der Ansichts-Generator (toElevation) + zeichnet dann den 45°-Schlagschatten auskragender Bauteile. */} + {shadowsAvailable && ( + <> +
+ +
+ + + )} + {/* Text-/Font-Formatierung auf DERSELBEN Leiste (Nutzer-Wunsch: Text und Ansichten zusammen) — Stil/Schrift/Grösse + B/I/U · L/C/R + Zeilenhöhe, formatiert das aktuelle Text-Ziel (Raumstempel) live. */}