FP_POC / packages /shared /src /rules /scene.ts
github-actions[bot]
Space-Deploy aus GitHub main
9de795a
Raw History Blame Contribute Delete
3.78 kB
/**
* Szenen-Aufbau: Raummodell + Plan + Katalog → auswertbare Szene.
*
* Der Interpreter rechnet nie direkt auf den Artefakten, sondern auf einer
* aufgelösten Szene (Footprints aus Katalog-Massen bzw. Bestands-bboxen).
* Platzierungen referenzieren nur catalogItemId – Geometrie kommt IMMER aus
* dem Katalog (Box-Platzhalter mit Auto-Upgrade, Schema-Spezifikation).
*
* ⚠️ PARITÄT: 1:1 gespiegelt in fp_engines/rules/scene.py.
*/
import { footprint, type Quad, type Vec2 } from "./geometry.ts";
/** Minimale Sicht auf die Artefakte (strukturell kompatibel zu den generierten Typen). */
export interface RoomInput {
roomType: string;
shell: {
walls: {
id: string;
start: Vec2;
end: Vec2;
kind: string;
}[];
floor: { polygon: Vec2[] };
};
openings: {
id: string;
type: string;
hostWall: string;
offset: number;
width: number;
sill: number;
}[];
fixpoints: { id: string; type: string; position: Vec2 }[];
objects: {
id: string;
label: string;
geometry: { bbox: { w: number; d: number; h: number } };
pose: { pos: Vec2; yawDeg: number };
}[];
meta: { estimatedError_cm: number; geometryConfirmed: boolean };
}
export interface PlanInput {
placements: {
id: string;
catalogItemId: string;
pose: { pos: Vec2; yawDeg: number };
mountHeight?: number;
}[];
meta: { normProfile: string };
}
export interface CatalogItemInput {
id: string;
funktionsTyp: string;
masse: { w: number; d: number; h: number };
mount?: string;
anschluesse: string[];
}
/** Ein auswertbares Objekt der Szene (Platzierung ODER Bestandsobjekt). */
export interface SceneObject {
id: string;
funktionsTyp: string;
quad: Quad;
center: Vec2;
yawDeg: number;
w: number;
d: number;
h: number;
mount: string;
mountHeight: number | null;
anschluesse: string[];
isPlacement: boolean;
}
export interface Scene {
roomType: string;
floor: Vec2[];
walls: RoomInput["shell"]["walls"];
openings: RoomInput["openings"];
fixpoints: RoomInput["fixpoints"];
objects: SceneObject[];
normProfile: string;
/** Unsicherheits-Marge der Konfidenz-Ampel in cm (0, wenn Geometrie bestätigt). */
margeCm: number;
}
export function buildScene(room: RoomInput, plan: PlanInput, catalog: CatalogItemInput[]): Scene {
const byId = new Map(catalog.map((c) => [c.id, c]));
const objects: SceneObject[] = [];
for (const p of plan.placements) {
const item = byId.get(p.catalogItemId);
if (!item) throw new Error(`Placement ${p.id}: Katalog-Item ${p.catalogItemId} fehlt`);
objects.push({
id: p.id,
funktionsTyp: item.funktionsTyp,
quad: footprint(p.pose.pos, item.masse.w, item.masse.d, p.pose.yawDeg),
center: p.pose.pos,
yawDeg: p.pose.yawDeg,
w: item.masse.w,
d: item.masse.d,
h: item.masse.h,
mount: item.mount ?? "boden",
mountHeight: p.mountHeight ?? null,
anschluesse: item.anschluesse,
isPlacement: true,
});
}
for (const o of room.objects) {
objects.push({
id: o.id,
funktionsTyp: o.label,
quad: footprint(o.pose.pos, o.geometry.bbox.w, o.geometry.bbox.d, o.pose.yawDeg),
center: o.pose.pos,
yawDeg: o.pose.yawDeg,
w: o.geometry.bbox.w,
d: o.geometry.bbox.d,
h: o.geometry.bbox.h,
mount: "boden",
mountHeight: null,
anschluesse: [],
isPlacement: false,
});
}
return {
roomType: room.roomType,
floor: room.shell.floor.polygon,
walls: room.shell.walls,
openings: room.openings,
fixpoints: room.fixpoints,
objects,
normProfile: plan.meta.normProfile,
margeCm: room.meta.geometryConfirmed ? 0 : room.meta.estimatedError_cm,
};
}