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:
+32
-7
@@ -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 (
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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. */}
|
||||||
|
|||||||
Reference in New Issue
Block a user