Ansicht: Flächen-Generator einbinden + Schatten-Umschalter

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.
This commit is contained in:
2026-07-11 00:20:49 +02:00
parent ce728703f6
commit a3a6b51db4
4 changed files with 64 additions and 7 deletions
+32 -7
View File
@@ -41,6 +41,7 @@ import { generatePlan, generateSectionPlan } from "./plan/generatePlan";
import type { CategoryDisplayResolver } from "./plan/generatePlan"; import type { CategoryDisplayResolver } from "./plan/generatePlan";
import { computeSection } from "./plan/toSection"; import { computeSection } from "./plan/toSection";
import type { SectionOutput } from "./plan/toSection"; import type { SectionOutput } from "./plan/toSection";
import { generateElevationPlan } from "./plan/toElevation";
import { pushNativeScene, pushNativeWalls } from "./plan/nativeSync"; import { pushNativeScene, pushNativeWalls } from "./plan/nativeSync";
import { selectionHighlightLines } from "./plan/toWalls3d"; import { selectionHighlightLines } from "./plan/toWalls3d";
import { parseDxf } from "./io/dxfParser"; import { parseDxf } from "./io/dxfParser";
@@ -3296,6 +3297,16 @@ export default function App() {
renderModeEnabled={viewType === "perspektive"} renderModeEnabled={viewType === "perspektive"}
planColorMode={planColorMode} planColorMode={planColorMode}
onPlanColorMode={setPlanColorMode} 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={{ combos={{
// Ebenen-Kombinationen: Speichern = Snapshot des Layer-Baums → // Ebenen-Kombinationen: Speichern = Snapshot des Layer-Baums →
// localStorage; Laden = Map lesen → Sichtbarkeits-Flags anwenden. // localStorage; Laden = Map lesen → Sichtbarkeits-Flags anwenden.
@@ -4949,12 +4960,17 @@ function SectionPlanView({
selectedDrawingIds?: string[]; selectedDrawingIds?: string[];
gripHandlers: GripHandlers; 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<SectionOutput | null>(null); const [output, setOutput] = useState<SectionOutput | null>(null);
const [status, setStatus] = useState<"loading" | "ready" | "empty" | "error">( const [status, setStatus] = useState<"loading" | "ready" | "empty" | "error">(
"loading", "loading",
); );
useEffect(() => { useEffect(() => {
if (isElevation) return; // Ansicht: kein WASM-Schnitt (synchroner Generator).
let disposed = false; let disposed = false;
setStatus("loading"); setStatus("loading");
setOutput(null); setOutput(null);
@@ -4976,20 +4992,29 @@ function SectionPlanView({
return () => { return () => {
disposed = true; disposed = true;
}; };
}, [project, level]); }, [project, level, isElevation]);
const plan = useMemo( // Ansichts-Plan: gefüllte Fassadenansicht inkl. optionalem Schatten
() => (output ? generateSectionPlan(output, mono) : null), // (level.shadows). Liefert null, wenn die Ebene keine Ansichtslinie trägt.
[output, mono], 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"); const kindLabel = level.kind === "section" ? t("stub.section") : t("stub.elevation");
if (!plan || status !== "ready") { if (!plan || effStatus !== "ready") {
const note = const note =
status === "empty" effStatus === "empty"
? t("section.empty", { kind: kindLabel }) ? t("section.empty", { kind: kindLabel })
: status === "error" : effStatus === "error"
? t("section.error", { kind: kindLabel }) ? t("section.error", { kind: kindLabel })
: t("section.loading", { kind: kindLabel }); : t("section.loading", { kind: kindLabel });
return ( return (
+2
View File
@@ -462,6 +462,8 @@ export const de = {
"section.empty": "{kind} — keine Schnittlinie im Grundriss gesetzt", "section.empty": "{kind} — keine Schnittlinie im Grundriss gesetzt",
"section.error": "{kind} — Berechnung fehlgeschlagen (render3d-WASM neu bauen?)", "section.error": "{kind} — Berechnung fehlgeschlagen (render3d-WASM neu bauen?)",
"section.legend": "{kind} · abgeleitete Projektion (render3d)", "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.floorHeight": "Geschosshöhe",
"props.cutHeight": "Schnitthöhe", "props.cutHeight": "Schnitthöhe",
"props.okff": "OKFF", "props.okff": "OKFF",
+2
View File
@@ -457,6 +457,8 @@ export const en: Record<TranslationKey, string> = {
"section.empty": "{kind} — no section line set in the plan", "section.empty": "{kind} — no section line set in the plan",
"section.error": "{kind} — computation failed (rebuild render3d WASM?)", "section.error": "{kind} — computation failed (rebuild render3d WASM?)",
"section.legend": "{kind} · derived projection (render3d)", "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.floorHeight": "Floor height",
"props.cutHeight": "Cut height", "props.cutHeight": "Cut height",
"props.okff": "FFL", "props.okff": "FFL",
+28
View File
@@ -990,6 +990,12 @@ export interface ViewRibbonTabProps {
renderModeEnabled: boolean; renderModeEnabled: boolean;
planColorMode: PlanColorMode; planColorMode: PlanColorMode;
onPlanColorMode: (m: PlanColorMode) => void; 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; combos: ComboMenuHandlers;
/** Text-/Font-Formatierung auf DERSELBEN Leiste (Raumstempel-Ziel; `null` = /** Text-/Font-Formatierung auf DERSELBEN Leiste (Raumstempel-Ziel; `null` =
* nur Defaults). Liegt bewusst im Ansichten"-Tab (Nutzer-Wunsch). */ * nur Defaults). Liegt bewusst im Ansichten"-Tab (Nutzer-Wunsch). */
@@ -1027,6 +1033,9 @@ export function ViewRibbonTab({
renderModeEnabled, renderModeEnabled,
planColorMode, planColorMode,
onPlanColorMode, onPlanColorMode,
shadowsAvailable,
shadows,
onShadows,
combos, combos,
textTarget, textTarget,
}: ViewRibbonTabProps) { }: ViewRibbonTabProps) {
@@ -1254,6 +1263,25 @@ export function ViewRibbonTab({
</div> </div>
<Sep /> <Sep />
{/* 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 && (
<>
<div className="tb-group" role="group" aria-label={t("elevation.shadows")}>
<button
className={`tb-toggle${shadows ? " active" : ""}`}
onClick={() => onShadows(!shadows)}
title={t("elevation.shadows.hint")}
aria-pressed={shadows}
>
{t("elevation.shadows")}
</button>
</div>
<Sep />
</>
)}
{/* Text-/Font-Formatierung auf DERSELBEN Leiste (Nutzer-Wunsch: Text und {/* Text-/Font-Formatierung auf DERSELBEN Leiste (Nutzer-Wunsch: Text und
Ansichten zusammen) Stil/Schrift/Grösse + B/I/U · L/C/R + Zeilenhöhe, Ansichten zusammen) Stil/Schrift/Grösse + B/I/U · L/C/R + Zeilenhöhe,
formatiert das aktuelle Text-Ziel (Raumstempel) live. */} formatiert das aktuelle Text-Ziel (Raumstempel) live. */}