Browser-BIM (cad): semantisches Modell, abgeleitete 2D/3D-Sichten, Zeichenwerkzeuge

Standalone-Browser-Port von DOSSIER. Enthaelt das semantische Modell mit
Plan-/3D-Ableitung, Zeichen- und Editierwerkzeuge, Rhino-artiges Befehlssystem,
dockbares Panel-System, Resource-Manager, DXF/.lin/.pat-Import, i18n (de/en)
sowie Projektdokumentation und Probe-Harness.
This commit is contained in:
2026-06-30 20:52:27 +02:00
commit ca859c4aa4
157 changed files with 37921 additions and 0 deletions
+81
View File
@@ -0,0 +1,81 @@
// Site-/Kontext-Slice: verwaltet die Kontext-Schicht `project.context`
// (importierte Meshes, Konturen-Sätze, abgeleitetes Gelände-TIN). Diese
// Geometrie ist „dumm"/nicht-semantisch — sie liegt bewusst NEBEN dem BIM-Modell.
//
// Entwurfsentscheidung: `context` ist Teil von `project` (ein einziger
// Wahrheits-Baum, konsistent mit projectSlice). Diese Slice MUTIERT daher
// `project.context` IMMUTABEL über die vorhandene `setProject`-Aktion
// (Cross-Slice), genau wie projectSlice ein neues Project-Objekt liefert. So
// bleiben Persistenz/Undo später einheitlich am Project hängen.
//
// Bezeichner englisch, Kommentare deutsch (CONVENTIONS.md).
import { generateTerrainFromContours } from "../model/terrain";
import type { ContextObject, Project } from "../model/types";
import type { StoreApi } from "./store";
/** Felder/Actions, die diese Slice in den RootState beisteuert. */
export interface SiteSlice {
/** Fügt ein Kontext-Objekt (Mesh/Konturen/Gelände) hinzu (immutabel). */
addContextObject: (obj: ContextObject) => void;
/** Fügt mehrere Kontext-Objekte in einem Schritt hinzu (z. B. DXF-Import). */
addContextObjects: (objs: ContextObject[]) => void;
/** Entfernt ein Kontext-Objekt per ID (No-op bei unbekannter ID). */
removeContextObject: (id: string) => void;
/**
* Erzeugt aus einem ContourSet (per ID) ein Gelände-TIN und fügt es als
* weiteres Kontext-Objekt hinzu. Liefert die ID des neuen TerrainMesh, oder
* null, wenn der ContourSet nicht existiert / kein Mesh entsteht.
*/
generateTerrain: (contourSetId: string) => string | null;
/** Leert die gesamte Kontext-Schicht. */
clearContext: () => void;
}
/**
* Cross-Slice-Abhängigkeit: diese Slice liest `project` und schreibt es über
* `setProject` (beide aus projectSlice). Der Store komponiert SiteSlice mit
* dieser Sicht, sodass `get()`/Actions typsicher auf das Projekt zugreifen.
*/
type SiteSliceDeps = {
project: Project;
setProject: (next: Project | ((p: Project) => Project)) => void;
};
export function createSiteSlice(
api: StoreApi<SiteSlice & SiteSliceDeps>,
): SiteSlice {
const { get } = api;
// Immutabler Kontext-Updater: ersetzt `project.context` über die vorhandene
// setProject-Aktion (konsistent mit projectSlice-Stil).
const setContext = (
update: (ctx: ContextObject[]) => ContextObject[],
): void => {
get().setProject((p) => ({ ...p, context: update(p.context ?? []) }));
};
return {
addContextObject: (obj) => setContext((ctx) => [...ctx, obj]),
addContextObjects: (objs) =>
setContext((ctx) => (objs.length ? [...ctx, ...objs] : ctx)),
removeContextObject: (id) =>
setContext((ctx) => ctx.filter((o) => o.id !== id)),
generateTerrain: (contourSetId) => {
const ctx = get().project.context ?? [];
const set = ctx.find(
(o) => o.id === contourSetId && o.type === "contourSet",
);
if (!set || set.type !== "contourSet") return null;
const terrain = generateTerrainFromContours(set.contours);
if (terrain.indices.length === 0) return null; // kein sinnvolles TIN
setContext((c) => [...c, terrain]);
return terrain.id;
},
clearContext: () => setContext(() => []),
};
}