@kerros/editor 0.0.0-stage → 0.1.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +35 -2
- package/dist/SitePlanner.js +1189 -0
- package/dist/SitePlanner.js.map +1 -0
- package/dist/adapters/basemap.js +11 -0
- package/dist/adapters/basemap.js.map +1 -0
- package/dist/adapters/persistence.js +121 -0
- package/dist/adapters/persistence.js.map +1 -0
- package/dist/adapters/status.js +11 -0
- package/dist/adapters/status.js.map +1 -0
- package/dist/adapters/useProjectPersistence.js +56 -0
- package/dist/adapters/useProjectPersistence.js.map +1 -0
- package/dist/components/AiImportPanel.js +635 -0
- package/dist/components/AiImportPanel.js.map +1 -0
- package/dist/components/AnalysisPreview.js +179 -0
- package/dist/components/AnalysisPreview.js.map +1 -0
- package/dist/components/ElevatorPanel.js +120 -0
- package/dist/components/ElevatorPanel.js.map +1 -0
- package/dist/components/Icons.js +57 -0
- package/dist/components/Icons.js.map +1 -0
- package/dist/components/ImportDialog.js +467 -0
- package/dist/components/ImportDialog.js.map +1 -0
- package/dist/components/Inspector.js +1184 -0
- package/dist/components/Inspector.js.map +1 -0
- package/dist/components/NavigatePanel.js +301 -0
- package/dist/components/NavigatePanel.js.map +1 -0
- package/dist/components/NavigationGraph.js +234 -0
- package/dist/components/NavigationGraph.js.map +1 -0
- package/dist/components/StructureExplorer.js +510 -0
- package/dist/components/StructureExplorer.js.map +1 -0
- package/dist/components/StructureView.js +286 -0
- package/dist/components/StructureView.js.map +1 -0
- package/dist/components/controls.js +209 -0
- package/dist/components/controls.js.map +1 -0
- package/dist/components/graphLayout.js +27 -0
- package/dist/components/graphLayout.js.map +1 -0
- package/dist/host.js +175 -0
- package/dist/host.js.map +1 -0
- package/dist/i18n.js +88 -0
- package/dist/i18n.js.map +1 -0
- package/dist/import/analysisPreview.js +41 -0
- package/dist/import/analysisPreview.js.map +1 -0
- package/dist/import/brief.js +29 -0
- package/dist/import/brief.js.map +1 -0
- package/dist/import/checkpoint.js +50 -0
- package/dist/import/checkpoint.js.map +1 -0
- package/dist/import/detect.js +120 -0
- package/dist/import/detect.js.map +1 -0
- package/dist/import/documentSvg.js +49 -0
- package/dist/import/documentSvg.js.map +1 -0
- package/dist/import/instructions.js +20 -0
- package/dist/import/instructions.js.map +1 -0
- package/dist/import/openings.js +61 -0
- package/dist/import/openings.js.map +1 -0
- package/dist/import/planImport.js +233 -0
- package/dist/import/planImport.js.map +1 -0
- package/dist/import/register.js +47 -0
- package/dist/import/register.js.map +1 -0
- package/dist/import/runs.js +67 -0
- package/dist/import/runs.js.map +1 -0
- package/dist/import/session.js +23 -0
- package/dist/import/session.js.map +1 -0
- package/dist/import/types.js +13 -0
- package/dist/import/types.js.map +1 -0
- package/dist/import/usage.js +29 -0
- package/dist/import/usage.js.map +1 -0
- package/dist/import/walls.js +112 -0
- package/dist/import/walls.js.map +1 -0
- package/dist/index.js +178 -0
- package/dist/index.js.map +1 -0
- package/dist/map/CabinMaterials.js +81 -0
- package/dist/map/CabinMaterials.js.map +1 -0
- package/dist/map/CabinMirror.js +43 -0
- package/dist/map/CabinMirror.js.map +1 -0
- package/dist/map/Doors.js +80 -0
- package/dist/map/Doors.js.map +1 -0
- package/dist/map/ElevatorCabin.js +93 -0
- package/dist/map/ElevatorCabin.js.map +1 -0
- package/dist/map/FixtureLights.js +117 -0
- package/dist/map/FixtureLights.js.map +1 -0
- package/dist/map/MapCanvas.js +2161 -0
- package/dist/map/MapCanvas.js.map +1 -0
- package/dist/map/Passenger.js +69 -0
- package/dist/map/Passenger.js.map +1 -0
- package/dist/map/SceneLayer.js +1534 -0
- package/dist/map/SceneLayer.js.map +1 -0
- package/dist/map/UndergroundContext.js +153 -0
- package/dist/map/UndergroundContext.js.map +1 -0
- package/dist/map/ambience.js +194 -0
- package/dist/map/ambience.js.map +1 -0
- package/dist/map/architecture.js +305 -0
- package/dist/map/architecture.js.map +1 -0
- package/dist/map/bathMusic.js +92 -0
- package/dist/map/bathMusic.js.map +1 -0
- package/dist/map/cameraAim.js +33 -0
- package/dist/map/cameraAim.js.map +1 -0
- package/dist/map/elevators.js +56 -0
- package/dist/map/elevators.js.map +1 -0
- package/dist/map/excavation.js +52 -0
- package/dist/map/excavation.js.map +1 -0
- package/dist/map/exteriors.js +25 -0
- package/dist/map/exteriors.js.map +1 -0
- package/dist/map/features.js +218 -0
- package/dist/map/features.js.map +1 -0
- package/dist/map/fences.js +29 -0
- package/dist/map/fences.js.map +1 -0
- package/dist/map/journey.js +112 -0
- package/dist/map/journey.js.map +1 -0
- package/dist/map/levels.js +6 -0
- package/dist/map/levels.js.map +1 -0
- package/dist/map/lighting.js +143 -0
- package/dist/map/lighting.js.map +1 -0
- package/dist/map/loadBasemap.js +26 -0
- package/dist/map/loadBasemap.js.map +1 -0
- package/dist/map/materials.js +183 -0
- package/dist/map/materials.js.map +1 -0
- package/dist/map/poolLighting.js +39 -0
- package/dist/map/poolLighting.js.map +1 -0
- package/dist/map/projection.js +24 -0
- package/dist/map/projection.js.map +1 -0
- package/dist/map/rampGeometry.js +38 -0
- package/dist/map/rampGeometry.js.map +1 -0
- package/dist/map/route.js +107 -0
- package/dist/map/route.js.map +1 -0
- package/dist/map/stairSurfaces.js +115 -0
- package/dist/map/stairSurfaces.js.map +1 -0
- package/dist/map/surfaces.js +136 -0
- package/dist/map/surfaces.js.map +1 -0
- package/dist/map/textures.js +95 -0
- package/dist/map/textures.js.map +1 -0
- package/dist/map/underground.js +34 -0
- package/dist/map/underground.js.map +1 -0
- package/dist/map/walk.js +433 -0
- package/dist/map/walk.js.map +1 -0
- package/dist/map/walkContext.js +46 -0
- package/dist/map/walkContext.js.map +1 -0
- package/dist/map/walkDimensions.js +7 -0
- package/dist/map/walkDimensions.js.map +1 -0
- package/dist/map/walkJourney.js +85 -0
- package/dist/map/walkJourney.js.map +1 -0
- package/dist/map/walkSurfaces.js +106 -0
- package/dist/map/walkSurfaces.js.map +1 -0
- package/dist/map/wallJoins.js +70 -0
- package/dist/map/wallJoins.js.map +1 -0
- package/dist/map/water.js +274 -0
- package/dist/map/water.js.map +1 -0
- package/dist/model/authoring.js +99 -0
- package/dist/model/authoring.js.map +1 -0
- package/dist/model/boundaries.js +548 -0
- package/dist/model/boundaries.js.map +1 -0
- package/dist/model/coverage.js +30 -0
- package/dist/model/coverage.js.map +1 -0
- package/dist/model/document.js +52 -0
- package/dist/model/document.js.map +1 -0
- package/dist/model/doors.js +47 -0
- package/dist/model/doors.js.map +1 -0
- package/dist/model/factory.js +56 -0
- package/dist/model/factory.js.map +1 -0
- package/dist/model/geometry.js +454 -0
- package/dist/model/geometry.js.map +1 -0
- package/dist/model/history.js +29 -0
- package/dist/model/history.js.map +1 -0
- package/dist/model/imports.js +45 -0
- package/dist/model/imports.js.map +1 -0
- package/dist/model/inference.js +182 -0
- package/dist/model/inference.js.map +1 -0
- package/dist/model/materials.js +11 -0
- package/dist/model/materials.js.map +1 -0
- package/dist/model/mutations.js +157 -0
- package/dist/model/mutations.js.map +1 -0
- package/dist/model/navigation.js +275 -0
- package/dist/model/navigation.js.map +1 -0
- package/dist/model/ontology.js +109 -0
- package/dist/model/ontology.js.map +1 -0
- package/dist/model/passages.js +5 -0
- package/dist/model/passages.js.map +1 -0
- package/dist/model/portals.js +42 -0
- package/dist/model/portals.js.map +1 -0
- package/dist/model/precision.js +11 -0
- package/dist/model/precision.js.map +1 -0
- package/dist/model/project.js +31 -0
- package/dist/model/project.js.map +1 -0
- package/dist/model/ramps.js +10 -0
- package/dist/model/ramps.js.map +1 -0
- package/dist/model/roomNavigation.js +87 -0
- package/dist/model/roomNavigation.js.map +1 -0
- package/dist/model/routeClearance.js +72 -0
- package/dist/model/routeClearance.js.map +1 -0
- package/dist/model/spaces.js +104 -0
- package/dist/model/spaces.js.map +1 -0
- package/dist/model/stairGeometry.js +40 -0
- package/dist/model/stairGeometry.js.map +1 -0
- package/dist/model/stairTraversal.js +48 -0
- package/dist/model/stairTraversal.js.map +1 -0
- package/dist/model/topology.js +63 -0
- package/dist/model/topology.js.map +1 -0
- package/dist/model/types.js +35 -0
- package/dist/model/types.js.map +1 -0
- package/dist/model/validate.js +240 -0
- package/dist/model/validate.js.map +1 -0
- package/dist/model/vertical.js +129 -0
- package/dist/model/vertical.js.map +1 -0
- package/dist/model/walls.js +261 -0
- package/dist/model/walls.js.map +1 -0
- package/dist/planner/DrawingDock.js +231 -0
- package/dist/planner/DrawingDock.js.map +1 -0
- package/dist/planner/FloorControls.js +93 -0
- package/dist/planner/FloorControls.js.map +1 -0
- package/dist/planner/MapSettings.js +143 -0
- package/dist/planner/MapSettings.js.map +1 -0
- package/dist/planner/PlannerHelp.js +57 -0
- package/dist/planner/PlannerHelp.js.map +1 -0
- package/dist/planner/ProjectSidebar.js +246 -0
- package/dist/planner/ProjectSidebar.js.map +1 -0
- package/dist/planner/floors.js +18 -0
- package/dist/planner/floors.js.map +1 -0
- package/dist/planner/useDrawingTools.js +341 -0
- package/dist/planner/useDrawingTools.js.map +1 -0
- package/dist/planner/useMapDisplay.js +51 -0
- package/dist/planner/useMapDisplay.js.map +1 -0
- package/dist/planner/usePlannerKeyboard.js +138 -0
- package/dist/planner/usePlannerKeyboard.js.map +1 -0
- package/dist/planner/useReferenceAlignment.js +50 -0
- package/dist/planner/useReferenceAlignment.js.map +1 -0
- package/dist/styles.css +4931 -0
- package/dist/theme/index.js +34 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/types/SitePlanner.d.ts +5 -0
- package/dist/types/adapters/basemap.d.ts +2 -0
- package/dist/types/adapters/persistence.d.ts +29 -0
- package/dist/types/adapters/status.d.ts +4 -0
- package/dist/types/adapters/useProjectPersistence.d.ts +13 -0
- package/dist/types/components/AiImportPanel.d.ts +9 -0
- package/dist/types/components/AnalysisPreview.d.ts +4 -0
- package/dist/types/components/ElevatorPanel.d.ts +18 -0
- package/dist/types/components/Icons.d.ts +8 -0
- package/dist/types/components/ImportDialog.d.ts +42 -0
- package/dist/types/components/Inspector.d.ts +38 -0
- package/dist/types/components/NavigatePanel.d.ts +27 -0
- package/dist/types/components/NavigationGraph.d.ts +10 -0
- package/dist/types/components/StructureExplorer.d.ts +8 -0
- package/dist/types/components/StructureView.d.ts +22 -0
- package/dist/types/components/controls.d.ts +52 -0
- package/dist/types/components/graphLayout.d.ts +9 -0
- package/dist/types/editor/host.d.ts +9 -0
- package/dist/types/editor/index.d.ts +2 -0
- package/dist/types/i18n.d.ts +85 -0
- package/dist/types/import/analysisPreview.d.ts +27 -0
- package/dist/types/import/brief.d.ts +22 -0
- package/dist/types/import/checkpoint.d.ts +49 -0
- package/dist/types/import/detect.d.ts +34 -0
- package/dist/types/import/documentSvg.d.ts +2 -0
- package/dist/types/import/index.d.ts +10 -0
- package/dist/types/import/instructions.d.ts +7 -0
- package/dist/types/import/openings.d.ts +22 -0
- package/dist/types/import/planImport.d.ts +22 -0
- package/dist/types/import/register.d.ts +25 -0
- package/dist/types/import/runs.d.ts +31 -0
- package/dist/types/import/session.d.ts +33 -0
- package/dist/types/import/types.d.ts +56 -0
- package/dist/types/import/usage.d.ts +10 -0
- package/dist/types/import/walls.d.ts +37 -0
- package/dist/types/map/CabinMaterials.d.ts +18 -0
- package/dist/types/map/CabinMirror.d.ts +13 -0
- package/dist/types/map/Doors.d.ts +11 -0
- package/dist/types/map/ElevatorCabin.d.ts +20 -0
- package/dist/types/map/FixtureLights.d.ts +35 -0
- package/dist/types/map/MapCanvas.d.ts +87 -0
- package/dist/types/map/Passenger.d.ts +18 -0
- package/dist/types/map/SceneLayer.d.ts +306 -0
- package/dist/types/map/UndergroundContext.d.ts +18 -0
- package/dist/types/map/ambience.d.ts +29 -0
- package/dist/types/map/architecture.d.ts +8 -0
- package/dist/types/map/bathMusic.d.ts +14 -0
- package/dist/types/map/cameraAim.d.ts +20 -0
- package/dist/types/map/elevators.d.ts +16 -0
- package/dist/types/map/excavation.d.ts +9 -0
- package/dist/types/map/exteriors.d.ts +3 -0
- package/dist/types/map/features.d.ts +33 -0
- package/dist/types/map/fences.d.ts +4 -0
- package/dist/types/map/journey.d.ts +36 -0
- package/dist/types/map/levels.d.ts +4 -0
- package/dist/types/map/lighting.d.ts +103 -0
- package/dist/types/map/loadBasemap.d.ts +4 -0
- package/dist/types/map/materials.d.ts +38 -0
- package/dist/types/map/poolLighting.d.ts +11 -0
- package/dist/types/map/projection.d.ts +9 -0
- package/dist/types/map/rampGeometry.d.ts +8 -0
- package/dist/types/map/route.d.ts +59 -0
- package/dist/types/map/stairSurfaces.d.ts +29 -0
- package/dist/types/map/surfaces.d.ts +36 -0
- package/dist/types/map/textures.d.ts +8 -0
- package/dist/types/map/underground.d.ts +30 -0
- package/dist/types/map/walk.d.ts +232 -0
- package/dist/types/map/walkContext.d.ts +11 -0
- package/dist/types/map/walkDimensions.d.ts +5 -0
- package/dist/types/map/walkJourney.d.ts +33 -0
- package/dist/types/map/walkSurfaces.d.ts +27 -0
- package/dist/types/map/wallJoins.d.ts +6 -0
- package/dist/types/map/water.d.ts +12 -0
- package/dist/types/model/authoring.d.ts +38 -0
- package/dist/types/model/boundaries.d.ts +41 -0
- package/dist/types/model/coverage.d.ts +8 -0
- package/dist/types/model/document.d.ts +9 -0
- package/dist/types/model/doors.d.ts +19 -0
- package/dist/types/model/factory.d.ts +5 -0
- package/dist/types/model/geometry.d.ts +99 -0
- package/dist/types/model/history.d.ts +10 -0
- package/dist/types/model/host.d.ts +154 -0
- package/dist/types/model/imports.d.ts +7 -0
- package/dist/types/model/inference.d.ts +46 -0
- package/dist/types/model/live.d.ts +60 -0
- package/dist/types/model/materials.d.ts +28 -0
- package/dist/types/model/mutations.d.ts +178 -0
- package/dist/types/model/navigation.d.ts +70 -0
- package/dist/types/model/ontology.d.ts +43 -0
- package/dist/types/model/passages.d.ts +2 -0
- package/dist/types/model/portals.d.ts +9 -0
- package/dist/types/model/precision.d.ts +11 -0
- package/dist/types/model/project.d.ts +8 -0
- package/dist/types/model/ramps.d.ts +7 -0
- package/dist/types/model/roomNavigation.d.ts +15 -0
- package/dist/types/model/routeClearance.d.ts +11 -0
- package/dist/types/model/spaces.d.ts +46 -0
- package/dist/types/model/stairGeometry.d.ts +27 -0
- package/dist/types/model/stairTraversal.d.ts +8 -0
- package/dist/types/model/topology.d.ts +14 -0
- package/dist/types/model/types.d.ts +369 -0
- package/dist/types/model/validate.d.ts +38 -0
- package/dist/types/model/vertical.d.ts +145 -0
- package/dist/types/model/walls.d.ts +71 -0
- package/dist/types/planner/DrawingDock.d.ts +16 -0
- package/dist/types/planner/FloorControls.d.ts +17 -0
- package/dist/types/planner/MapSettings.d.ts +16 -0
- package/dist/types/planner/PlannerHelp.d.ts +3 -0
- package/dist/types/planner/ProjectSidebar.d.ts +32 -0
- package/dist/types/planner/floors.d.ts +3 -0
- package/dist/types/planner/types.d.ts +3 -0
- package/dist/types/planner/useDrawingTools.d.ts +52 -0
- package/dist/types/planner/useMapDisplay.d.ts +29 -0
- package/dist/types/planner/usePlannerKeyboard.d.ts +18 -0
- package/dist/types/planner/useReferenceAlignment.d.ts +30 -0
- package/dist/types/schema/index.d.ts +25 -0
- package/dist/types/theme/index.d.ts +55 -0
- package/package.json +58 -4
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { closeRing as w, toLocal as M, centroid as A } from "./geometry.js";
|
|
2
|
+
import { validateRings as E } from "./validate.js";
|
|
3
|
+
import { createObject as P } from "./factory.js";
|
|
4
|
+
function O(i, u, p, g) {
|
|
5
|
+
var m;
|
|
6
|
+
if (!i || typeof i != "object")
|
|
7
|
+
throw new Error("Choose a GeoJSON FeatureCollection, Feature, Polygon, or MultiPolygon.");
|
|
8
|
+
const t = i, s = t.type === "FeatureCollection" ? t.features : t.type === "Feature" ? [{ geometry: t.geometry, properties: t.properties }] : [{ geometry: i, properties: {} }];
|
|
9
|
+
if (!Array.isArray(s) || !s.length) throw new Error("The GeoJSON contains no features.");
|
|
10
|
+
const l = [];
|
|
11
|
+
for (const n of s) {
|
|
12
|
+
if (!n || !n.geometry || !["Polygon", "MultiPolygon"].includes(n.geometry.type))
|
|
13
|
+
throw new Error("Only Polygon and MultiPolygon footprint features are supported.");
|
|
14
|
+
const y = n.geometry, d = y.type === "Polygon" ? [y.coordinates] : y.coordinates;
|
|
15
|
+
for (const f of d) {
|
|
16
|
+
if (!Array.isArray(f)) throw new Error("Invalid polygon coordinates.");
|
|
17
|
+
const o = f.map((r) => {
|
|
18
|
+
if (!Array.isArray(r)) throw new Error("Invalid polygon ring.");
|
|
19
|
+
return w(
|
|
20
|
+
r.map((e) => {
|
|
21
|
+
if (!Array.isArray(e) || e.length < 2 || !e.slice(0, 2).every(Number.isFinite))
|
|
22
|
+
throw new Error("Invalid coordinates.");
|
|
23
|
+
const c = g ? g([e[0], e[1]]) : [e[0], e[1]];
|
|
24
|
+
if (Math.abs(c[0]) > 180 || Math.abs(c[1]) > 85)
|
|
25
|
+
throw new Error("Coordinates are outside WGS84 bounds — pick the projection the file uses.");
|
|
26
|
+
return M(c, u.origin);
|
|
27
|
+
})
|
|
28
|
+
);
|
|
29
|
+
}), h = E(o);
|
|
30
|
+
if (h) throw new Error(h);
|
|
31
|
+
const a = P(
|
|
32
|
+
p,
|
|
33
|
+
A(o[0]),
|
|
34
|
+
null,
|
|
35
|
+
String(((m = n.properties) == null ? void 0 : m.name) ?? `${p === "building" ? "Building" : "Parcel"} ${l.length + 1}`)
|
|
36
|
+
);
|
|
37
|
+
a.width = Math.max(...o[0].map((r) => r[0])) - Math.min(...o[0].map((r) => r[0])), a.depth = Math.max(...o[0].map((r) => r[1])) - Math.min(...o[0].map((r) => r[1])), a.rings = o, l.push(a);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return l;
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
O as importFootprints
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=imports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.js","sources":["../../../../src/model/imports.ts"],"sourcesContent":["import type { FeatureCollection, Geometry } from 'geojson';\nimport type { Point, ProjectDocument, Ring, SiteObject } from './types';\nimport { centroid, closeRing, toLocal } from './geometry';\nimport { validateRings } from './validate';\nimport { createObject } from './factory';\n\n/**\n * Import building/parcel footprints from GeoJSON. Coordinates are lng/lat (WGS84) by default; pass\n * `toLngLat` to convert from another projection first (the host supplies the converter — the core\n * carries no CRS knowledge).\n */\nexport function importFootprints(\n value: unknown,\n project: ProjectDocument,\n kind: 'building' | 'parcel',\n toLngLat?: (p: Point) => Point,\n): SiteObject[] {\n if (!value || typeof value !== 'object')\n throw new Error('Choose a GeoJSON FeatureCollection, Feature, Polygon, or MultiPolygon.');\n const v = value as {\n type?: string;\n geometry?: Geometry;\n features?: FeatureCollection['features'];\n properties?: Record<string, unknown>;\n };\n const features =\n v.type === 'FeatureCollection'\n ? v.features\n : v.type === 'Feature'\n ? [{ geometry: v.geometry, properties: v.properties }]\n : [{ geometry: value as Geometry, properties: {} }];\n if (!Array.isArray(features) || !features.length) throw new Error('The GeoJSON contains no features.');\n const result: SiteObject[] = [];\n for (const f of features) {\n if (!f || !f.geometry || !['Polygon', 'MultiPolygon'].includes(f.geometry.type))\n throw new Error('Only Polygon and MultiPolygon footprint features are supported.');\n const geometry = f.geometry as GeoJSON.Polygon | GeoJSON.MultiPolygon;\n const polys = geometry.type === 'Polygon' ? [geometry.coordinates] : geometry.coordinates;\n for (const polygon of polys) {\n if (!Array.isArray(polygon)) throw new Error('Invalid polygon coordinates.');\n const rings: Ring[] = polygon.map(ring => {\n if (!Array.isArray(ring)) throw new Error('Invalid polygon ring.');\n return closeRing(\n ring.map(p => {\n if (!Array.isArray(p) || p.length < 2 || !p.slice(0, 2).every(Number.isFinite))\n throw new Error('Invalid coordinates.');\n const lngLat = toLngLat ? toLngLat([p[0], p[1]]) : ([p[0], p[1]] as Point);\n if (Math.abs(lngLat[0]) > 180 || Math.abs(lngLat[1]) > 85)\n throw new Error('Coordinates are outside WGS84 bounds — pick the projection the file uses.');\n return toLocal(lngLat, project.origin);\n }),\n );\n });\n const error = validateRings(rings);\n if (error) throw new Error(error);\n const o = createObject(\n kind,\n centroid(rings[0]),\n null,\n String(f.properties?.name ?? `${kind === 'building' ? 'Building' : 'Parcel'} ${result.length + 1}`),\n );\n o.width = Math.max(...rings[0].map(p => p[0])) - Math.min(...rings[0].map(p => p[0]));\n o.depth = Math.max(...rings[0].map(p => p[1])) - Math.min(...rings[0].map(p => p[1]));\n o.rings = rings;\n result.push(o);\n }\n }\n return result;\n}\n"],"names":["importFootprints","value","project","kind","toLngLat","v","features","result","f","geometry","polys","polygon","rings","ring","closeRing","p","lngLat","toLocal","error","validateRings","o","createObject","centroid","_a"],"mappings":";;;AAWO,SAASA,EACdC,GACAC,GACAC,GACAC,GACc;;AACd,MAAI,CAACH,KAAS,OAAOA,KAAU;AAC7B,UAAM,IAAI,MAAM,wEAAwE;AAC1F,QAAMI,IAAIJ,GAMJK,IACJD,EAAE,SAAS,sBACPA,EAAE,WACFA,EAAE,SAAS,YACT,CAAC,EAAE,UAAUA,EAAE,UAAU,YAAYA,EAAE,WAAA,CAAY,IACnD,CAAC,EAAE,UAAUJ,GAAmB,YAAY,CAAA,EAAC,CAAG;AACxD,MAAI,CAAC,MAAM,QAAQK,CAAQ,KAAK,CAACA,EAAS,OAAQ,OAAM,IAAI,MAAM,mCAAmC;AACrG,QAAMC,IAAuB,CAAA;AAC7B,aAAWC,KAAKF,GAAU;AACxB,QAAI,CAACE,KAAK,CAACA,EAAE,YAAY,CAAC,CAAC,WAAW,cAAc,EAAE,SAASA,EAAE,SAAS,IAAI;AAC5E,YAAM,IAAI,MAAM,iEAAiE;AACnF,UAAMC,IAAWD,EAAE,UACbE,IAAQD,EAAS,SAAS,YAAY,CAACA,EAAS,WAAW,IAAIA,EAAS;AAC9E,eAAWE,KAAWD,GAAO;AAC3B,UAAI,CAAC,MAAM,QAAQC,CAAO,EAAG,OAAM,IAAI,MAAM,8BAA8B;AAC3E,YAAMC,IAAgBD,EAAQ,IAAI,CAAAE,MAAQ;AACxC,YAAI,CAAC,MAAM,QAAQA,CAAI,EAAG,OAAM,IAAI,MAAM,uBAAuB;AACjE,eAAOC;AAAA,UACLD,EAAK,IAAI,CAAAE,MAAK;AACZ,gBAAI,CAAC,MAAM,QAAQA,CAAC,KAAKA,EAAE,SAAS,KAAK,CAACA,EAAE,MAAM,GAAG,CAAC,EAAE,MAAM,OAAO,QAAQ;AAC3E,oBAAM,IAAI,MAAM,sBAAsB;AACxC,kBAAMC,IAASZ,IAAWA,EAAS,CAACW,EAAE,CAAC,GAAGA,EAAE,CAAC,CAAC,CAAC,IAAK,CAACA,EAAE,CAAC,GAAGA,EAAE,CAAC,CAAC;AAC/D,gBAAI,KAAK,IAAIC,EAAO,CAAC,CAAC,IAAI,OAAO,KAAK,IAAIA,EAAO,CAAC,CAAC,IAAI;AACrD,oBAAM,IAAI,MAAM,2EAA2E;AAC7F,mBAAOC,EAAQD,GAAQd,EAAQ,MAAM;AAAA,UACvC,CAAC;AAAA,QAAA;AAAA,MAEL,CAAC,GACKgB,IAAQC,EAAcP,CAAK;AACjC,UAAIM,EAAO,OAAM,IAAI,MAAMA,CAAK;AAChC,YAAME,IAAIC;AAAA,QACRlB;AAAA,QACAmB,EAASV,EAAM,CAAC,CAAC;AAAA,QACjB;AAAA,QACA,SAAOW,IAAAf,EAAE,eAAF,gBAAAe,EAAc,SAAQ,GAAGpB,MAAS,aAAa,aAAa,QAAQ,IAAII,EAAO,SAAS,CAAC,EAAE;AAAA,MAAA;AAEpG,MAAAa,EAAE,QAAQ,KAAK,IAAI,GAAGR,EAAM,CAAC,EAAE,IAAI,CAAAG,MAAKA,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,GAAGH,EAAM,CAAC,EAAE,IAAI,CAAAG,MAAKA,EAAE,CAAC,CAAC,CAAC,GACpFK,EAAE,QAAQ,KAAK,IAAI,GAAGR,EAAM,CAAC,EAAE,IAAI,CAAAG,MAAKA,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,GAAGH,EAAM,CAAC,EAAE,IAAI,CAAAG,MAAKA,EAAE,CAAC,CAAC,CAAC,GACpFK,EAAE,QAAQR,GACVL,EAAO,KAAKa,CAAC;AAAA,IACf;AAAA,EACF;AACA,SAAOb;AACT;"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import nn from "polygon-clipping";
|
|
2
|
+
import { mergeBoundaryGeometry as tn } from "./boundaries.js";
|
|
3
|
+
import { splitRoom as on, footprint as v, barrierEnds as Q, objectArea as J, openRing as U, distance as F, segmentProjection as X, objectPosition as sn, closeRing as A, pointInRing as L } from "./geometry.js";
|
|
4
|
+
import { isSpace as Y } from "./types.js";
|
|
5
|
+
import { inSpace as V, spaceAt as H } from "./spaces.js";
|
|
6
|
+
import { isVertical as T, shaftKey as _, servedFloors as en } from "./vertical.js";
|
|
7
|
+
import { pruneOntology as rn } from "./ontology.js";
|
|
8
|
+
import { sharedBoundaryPortals as an } from "./portals.js";
|
|
9
|
+
import { TRAVERSABLE as fn } from "./passages.js";
|
|
10
|
+
const cn = 0.35;
|
|
11
|
+
function dn(o) {
|
|
12
|
+
const d = new Map(o.barriers.map((i) => [i.id, i])), f = [];
|
|
13
|
+
for (const i of o.objects) {
|
|
14
|
+
if (!fn.has(i.kind)) continue;
|
|
15
|
+
const l = sn(o, i);
|
|
16
|
+
let a, c, n = cn;
|
|
17
|
+
const r = i.barrierId ? d.get(i.barrierId) : void 0;
|
|
18
|
+
if (r) {
|
|
19
|
+
const [u, t] = Q(o, r), s = F(u, t) || 1;
|
|
20
|
+
a = (t[1] - u[1]) / s, c = -(t[0] - u[0]) / s, n += r.thickness / 2;
|
|
21
|
+
} else {
|
|
22
|
+
const u = (i.rotation ?? 0) * Math.PI / 180;
|
|
23
|
+
a = -Math.sin(u), c = Math.cos(u), n += i.depth / 2;
|
|
24
|
+
}
|
|
25
|
+
const p = (u) => {
|
|
26
|
+
const t = [l[0] + a * n * u, l[1] + c * n * u];
|
|
27
|
+
return H(o, i.floorId, t) ?? (i.floorId === null ? null : H(o, null, t));
|
|
28
|
+
}, b = p(1), m = p(-1);
|
|
29
|
+
!b || !m || b.id === m.id || f.push({ id: `inferred:${i.id}`, openingId: i.id, a: b.id, b: m.id, attests: "assumed" });
|
|
30
|
+
}
|
|
31
|
+
return f;
|
|
32
|
+
}
|
|
33
|
+
const ln = 1, B = 0.45, un = 1.2;
|
|
34
|
+
function N(o) {
|
|
35
|
+
var m, u;
|
|
36
|
+
const d = /* @__PURE__ */ new Map(), f = (t, s) => {
|
|
37
|
+
const e = d.get(s);
|
|
38
|
+
e ? e.push(t) : d.set(s, [t]);
|
|
39
|
+
}, i = /* @__PURE__ */ new Map();
|
|
40
|
+
for (const t of o.objects) {
|
|
41
|
+
if (!T(t.kind) || !t.floorId) continue;
|
|
42
|
+
const s = _(t), e = i.get(s);
|
|
43
|
+
e ? e.add(t.floorId) : i.set(s, /* @__PURE__ */ new Set([t.floorId]));
|
|
44
|
+
}
|
|
45
|
+
for (const t of o.objects) {
|
|
46
|
+
if (!Y(t.kind) || (f(t, t.floorId ?? ""), !T(t.kind))) continue;
|
|
47
|
+
const s = i.get(_(t));
|
|
48
|
+
for (const e of en(o, t))
|
|
49
|
+
e.id !== t.floorId && !(s != null && s.has(e.id)) && f(t, e.id);
|
|
50
|
+
}
|
|
51
|
+
const l = (t, s = 0) => {
|
|
52
|
+
const e = [1 / 0, 1 / 0, -1 / 0, -1 / 0];
|
|
53
|
+
for (const [g, h] of t)
|
|
54
|
+
e[0] = Math.min(e[0], g - s), e[1] = Math.min(e[1], h - s), e[2] = Math.max(e[2], g + s), e[3] = Math.max(e[3], h + s);
|
|
55
|
+
return e;
|
|
56
|
+
}, a = (t, s, e) => s >= t[0] && e >= t[1] && s <= t[2] && e <= t[3], c = new Map(
|
|
57
|
+
[...d.values()].flat().map((t) => {
|
|
58
|
+
const s = v(t);
|
|
59
|
+
return [t.id, { rings: s, bounds: l(s[0] ?? []) }];
|
|
60
|
+
})
|
|
61
|
+
), n = (t, s) => {
|
|
62
|
+
const e = c.get(t.id);
|
|
63
|
+
return a(e.bounds, ...s) && !!e.rings[0] && L(s, e.rings[0]) && !e.rings.slice(1).some((g) => L(s, g));
|
|
64
|
+
}, r = /* @__PURE__ */ new Map();
|
|
65
|
+
for (const t of o.barriers) {
|
|
66
|
+
const s = t.floorId ?? "", e = r.get(s), g = Q(o, t), h = { ends: g, bounds: l(g, B) };
|
|
67
|
+
e ? e.push(h) : r.set(s, [h]);
|
|
68
|
+
}
|
|
69
|
+
const p = /* @__PURE__ */ new Map();
|
|
70
|
+
for (const [t, s] of d) {
|
|
71
|
+
const e = r.get(t) ?? [], g = [...s].sort((h, y) => J(h) - J(y));
|
|
72
|
+
for (const h of s)
|
|
73
|
+
for (const y of c.get(h.id).rings) {
|
|
74
|
+
if (!(y != null && y.length)) continue;
|
|
75
|
+
const R = U(y);
|
|
76
|
+
for (let P = 0; P < R.length; P++) {
|
|
77
|
+
const I = R[P], E = R[(P + 1) % R.length], M = F(I, E);
|
|
78
|
+
if (M < 0.05) continue;
|
|
79
|
+
const W = (E[0] - I[0]) / M, K = (E[1] - I[1]) / M, Z = K, j = -W, x = Math.max(1, Math.round(M / ln));
|
|
80
|
+
for (let O = 0; O < x; O++) {
|
|
81
|
+
const z = (O + 0.5) / x * M, $ = I[0] + W * z, k = I[1] + K * z;
|
|
82
|
+
if (!e.some((w) => a(w.bounds, $, k) && X([$, k], ...w.ends).distance < B))
|
|
83
|
+
for (const w of [1, -1]) {
|
|
84
|
+
const C = [$ + Z * B * w, k + j * B * w];
|
|
85
|
+
if (n(h, C)) continue;
|
|
86
|
+
const S = g.find((G) => G.id !== h.id && n(G, C));
|
|
87
|
+
if (!S || ((m = h.geometry) == null ? void 0 : m.mode) === "boundaries" && ((u = S.geometry) == null ? void 0 : u.mode) === "boundaries") continue;
|
|
88
|
+
const D = h.id < S.id ? `${h.id}|${S.id}` : `${S.id}|${h.id}`;
|
|
89
|
+
p.set(D, (p.get(D) ?? 0) + M / x);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const b = an(o);
|
|
96
|
+
for (const [t, s] of p) {
|
|
97
|
+
if (s < un) continue;
|
|
98
|
+
const [e, g] = t.split("|");
|
|
99
|
+
b.push({ id: `open:${e}:${g}`, a: e, b: g, attests: "none" });
|
|
100
|
+
}
|
|
101
|
+
return b;
|
|
102
|
+
}
|
|
103
|
+
const q = (o) => o.startsWith("inferred:") || o.startsWith("open:");
|
|
104
|
+
function vn(o) {
|
|
105
|
+
const d = o.portals ?? [], f = d.filter((n) => !q(n.id)), i = new Set(f.flatMap((n) => n.openingId ? [n.openingId] : [])), l = new Set(f.map((n) => JSON.stringify([n.a, n.b].sort()))), a = new Map(d.filter((n) => q(n.id)).map((n) => [n.id, n])), c = [...dn(o), ...N(o)].filter((n) => !n.openingId || !i.has(n.openingId)).filter((n) => !n.id.startsWith("open:") || !l.has(JSON.stringify([n.a, n.b].sort()))).map((n) => {
|
|
106
|
+
const r = a.get(n.id);
|
|
107
|
+
return r && (r.passage !== void 0 && (n.passage = r.passage), r.attests !== void 0 && (n.attests = r.attests), r.name !== void 0 && (n.name = r.name), r.metadata !== void 0 && (n.metadata = r.metadata)), n;
|
|
108
|
+
});
|
|
109
|
+
return o.portals = [...f, ...c], c.length;
|
|
110
|
+
}
|
|
111
|
+
function mn(o, d, f, i) {
|
|
112
|
+
var a;
|
|
113
|
+
const l = [];
|
|
114
|
+
for (const c of o.objects) {
|
|
115
|
+
if (c.floorId !== d || !Y(c.kind) || ((a = c.geometry) == null ? void 0 : a.mode) === "boundaries" || o.objects.some((m) => m.parentId === c.id)) continue;
|
|
116
|
+
const n = v(c)[0];
|
|
117
|
+
if (!(n != null && n.length)) continue;
|
|
118
|
+
const r = (m) => V(c, m) && !v(c).some((u) => {
|
|
119
|
+
const t = U(u);
|
|
120
|
+
return t.some((s, e) => X(m, s, t[(e + 1) % t.length]).distance < 1e-6);
|
|
121
|
+
});
|
|
122
|
+
if (r(f) || r(i)) continue;
|
|
123
|
+
const p = Math.max(8, Math.min(200, Math.round(F(f, i) / 0.25)));
|
|
124
|
+
let b = !1;
|
|
125
|
+
for (let m = 1; m < p && !b; m++) {
|
|
126
|
+
const u = m / p;
|
|
127
|
+
b = V(c, [f[0] + (i[0] - f[0]) * u, f[1] + (i[1] - f[1]) * u]);
|
|
128
|
+
}
|
|
129
|
+
b && l.push(c);
|
|
130
|
+
}
|
|
131
|
+
return l;
|
|
132
|
+
}
|
|
133
|
+
function Rn(o, d, f, i) {
|
|
134
|
+
const l = [];
|
|
135
|
+
for (const a of mn(o, d, f, i))
|
|
136
|
+
try {
|
|
137
|
+
l.push(on(o, a.id, f, i, !1));
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
return l;
|
|
141
|
+
}
|
|
142
|
+
function Pn(o, d) {
|
|
143
|
+
if (!o.barriers.find((a) => a.id === d)) return null;
|
|
144
|
+
const i = { ...o, barriers: o.barriers.filter((a) => a.id !== d) }, l = new Set(N(o).map((a) => [a.a, a.b].sort().join("|")));
|
|
145
|
+
for (const a of N(i)) {
|
|
146
|
+
if (l.has([a.a, a.b].sort().join("|"))) continue;
|
|
147
|
+
const c = o.objects.find((r) => r.id === a.a), n = o.objects.find((r) => r.id === a.b);
|
|
148
|
+
if (c && n) return [c, n];
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
function Bn(o, d, f) {
|
|
153
|
+
var a, c;
|
|
154
|
+
const i = o.objects.find((n) => n.id === d), l = o.objects.find((n) => n.id === f);
|
|
155
|
+
if (!i || !l || i.id === l.id) return !1;
|
|
156
|
+
if (((a = i.geometry) == null ? void 0 : a.mode) === "boundaries" || ((c = l.geometry) == null ? void 0 : c.mode) === "boundaries") {
|
|
157
|
+
if (!tn(o, i, l)) return !1;
|
|
158
|
+
} else {
|
|
159
|
+
const n = nn.union(v(i).map(A), v(l).map(A));
|
|
160
|
+
if (n.length !== 1) return !1;
|
|
161
|
+
i.rings = n[0].map((r) => A(r));
|
|
162
|
+
}
|
|
163
|
+
o.objects = o.objects.filter((n) => n.id !== f);
|
|
164
|
+
for (const n of o.objects) n.parentId === f && (n.parentId = d);
|
|
165
|
+
for (const n of o.portals ?? [])
|
|
166
|
+
n.a === f && (n.a = d), n.b === f && (n.b = d);
|
|
167
|
+
o.portals = (o.portals ?? []).filter((n) => n.a !== n.b);
|
|
168
|
+
for (const n of o.zones ?? [])
|
|
169
|
+
n.spaceIds = [...new Set(n.spaceIds.map((r) => r === f ? d : r))];
|
|
170
|
+
return rn(o), !0;
|
|
171
|
+
}
|
|
172
|
+
export {
|
|
173
|
+
fn as TRAVERSABLE,
|
|
174
|
+
Rn as divideSpaces,
|
|
175
|
+
N as inferOpenBoundaries,
|
|
176
|
+
dn as inferPortals,
|
|
177
|
+
Bn as mergeSpaces,
|
|
178
|
+
vn as refreshPortals,
|
|
179
|
+
mn as spacesDividedBy,
|
|
180
|
+
Pn as spacesRejoinedBy
|
|
181
|
+
};
|
|
182
|
+
//# sourceMappingURL=inference.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inference.js","sources":["../../../../src/model/inference.ts"],"sourcesContent":["// Reading the plan: what the geometry already says about connection and separation. Portals are\n// inferred from doors and from open boundaries, and a wall drawn or removed changes which spaces\n// exist at all. Nothing here decides anything — decisions (zones, hand-set passage) live in\n// ontology.ts; this module only reports what is drawn, which is why it may be re-run at any time.\nimport polygonClipping from 'polygon-clipping';\nimport { mergeBoundaryGeometry } from './boundaries';\nimport {\n barrierEnds,\n closeRing,\n distance,\n footprint,\n objectArea,\n objectPosition,\n openRing,\n pointInRing,\n segmentProjection,\n splitRoom,\n} from './geometry';\nimport { isSpace, type Point, type Portal, type ProjectDocument, type SiteObject } from './types';\nimport { inSpace, spaceAt } from './spaces';\nimport { isVertical, servedFloors, shaftKey } from './vertical';\nimport { pruneOntology } from './ontology';\nimport { sharedBoundaryPortals } from './portals';\nimport { TRAVERSABLE } from './passages';\nexport { TRAVERSABLE } from './passages';\n\n/** How far past a wall face to look for the room on that side — enough to clear the leaf and any\n * threshold, small enough to stay in the room rather than reaching into the next one. */\nconst PROBE = 0.35;\n/** Infer the portals a plan already describes: for each opening, the space on either side of the wall\n * it sits in. This is the move software without a plan cannot make — it has to be told which\n * doors bound an area, because none of them has a floor plan to ask.\n *\n * Openings whose sides cannot be resolved are skipped rather than guessed at: an unbounded or\n * open-plan side is a real modelling gap, and inventing a portal there would hide it. */\nexport function inferPortals(project: ProjectDocument): Portal[] {\n const barriers = new Map(project.barriers.map(b => [b.id, b]));\n const out: Portal[] = [];\n for (const opening of project.objects) {\n if (!TRAVERSABLE.has(opening.kind)) continue;\n const at = objectPosition(project, opening);\n // The direction to probe: across the wall it is set into, or across its own facing if it is free.\n let nx: number,\n ny: number,\n clearance = PROBE;\n const barrier = opening.barrierId ? barriers.get(opening.barrierId) : undefined;\n if (barrier) {\n const [a, b] = barrierEnds(project, barrier);\n const len = distance(a, b) || 1;\n nx = (b[1] - a[1]) / len;\n ny = -(b[0] - a[0]) / len;\n clearance += barrier.thickness / 2;\n } else {\n const rad = ((opening.rotation ?? 0) * Math.PI) / 180;\n nx = -Math.sin(rad);\n ny = Math.cos(rad);\n clearance += opening.depth / 2;\n }\n // Look on the opening's own floor first, then outdoors. A front door has a room on one side and\n // the street on the other, and the street is not a floor — it is floorId null. Probing only the\n // door's own level makes every entrance in the building unresolvable, which quietly severs the\n // inside from the outside and leaves no route to a muster point.\n const side = (sign: number): SiteObject | null => {\n const probe: Point = [at[0] + nx * clearance * sign, at[1] + ny * clearance * sign];\n return (\n spaceAt(project, opening.floorId, probe) ?? (opening.floorId === null ? null : spaceAt(project, null, probe))\n );\n };\n const a = side(1),\n b = side(-1);\n if (!a || !b || a.id === b.id) continue;\n out.push({ id: `inferred:${opening.id}`, openingId: opening.id, a: a.id, b: b.id, attests: 'assumed' });\n }\n return out;\n}\n\n/** Sampling step along a space's outline, and how far past it to look for a neighbour. */\nconst WALK = 1,\n REACH = 0.45;\n/** A shared run shorter than this is two rooms brushing at a corner, not a way through. */\nconst MIN_OPENING = 1.2;\n\n/** Infer the portals that have no door in them: two spaces that share a boundary with nothing built\n * across it. Open-plan floors are made of these — a department opening onto a gallery, a lift car\n * onto its lobby — and a model that only knows about door objects cannot see any of them.\n *\n * Shared virtual edges give exact connectivity. For independent outlines, walk the outline and\n * accumulate stretches that can step across into a neighbour without crossing a wall. */\nexport function inferOpenBoundaries(project: ProjectDocument): Portal[] {\n const byFloor = new Map<string, SiteObject[]>();\n const standOn = (space: SiteObject, key: string) => {\n const list = byFloor.get(key);\n if (list) list.push(space);\n else byFloor.set(key, [space]);\n };\n // Which floors already have a landing of each shaft drawn on them. A plan drawn sheet by sheet\n // gives one landing per storey and every one of them meets its own lobby without help.\n const landed = new Map<string, Set<string>>();\n for (const o of project.objects) {\n if (!isVertical(o.kind) || !o.floorId) continue;\n const key = shaftKey(o);\n const floors = landed.get(key);\n if (floors) floors.add(o.floorId);\n else landed.set(key, new Set([o.floorId]));\n }\n for (const space of project.objects) {\n if (!isSpace(space.kind)) continue;\n standOn(space, space.floorId ?? '');\n // A shaft is a column, and it stands on every floor it serves. A plan that draws the core once\n // and lists the floors it serves makes the same claim as a hundred landings do, in one object —\n // and walking only the floor that object happens to be filed under found a ten-storey lift\n // exactly one way in, which reads as a cupboard in the basement rather than as a lift.\n //\n // Only where nothing of this shaft already stands, though. servedFloors unions what every twin\n // declares, so a tower drawn landing by landing has each of its hundred landings claiming all\n // hundred floors: lifting them all onto all of them is the same wall walked ten thousand times\n // for the hundred placements that meant anything, and it took the Silo's inference from under a\n // second to eight.\n if (!isVertical(space.kind)) continue;\n const here = landed.get(shaftKey(space));\n for (const floor of servedFloors(project, space))\n if (floor.id !== space.floorId && !here?.has(floor.id)) standOn(space, floor.id);\n }\n // This index belongs to this inference pass: mutable editor drafts must never reuse stale\n // footprints. Bounds reject distant candidates before polygon tests and wall projections.\n type Bounds = [number, number, number, number];\n const bounds = (points: Point[], pad = 0): Bounds => {\n const box: Bounds = [Infinity, Infinity, -Infinity, -Infinity];\n for (const [x, y] of points) {\n box[0] = Math.min(box[0], x - pad);\n box[1] = Math.min(box[1], y - pad);\n box[2] = Math.max(box[2], x + pad);\n box[3] = Math.max(box[3], y + pad);\n }\n return box;\n };\n const within = (box: Bounds, x: number, y: number) => x >= box[0] && y >= box[1] && x <= box[2] && y <= box[3];\n const shapes = new Map(\n [...byFloor.values()].flat().map(o => {\n const rings = footprint(o);\n return [o.id, { rings, bounds: bounds(rings[0] ?? []) }] as const;\n }),\n );\n const inside = (space: SiteObject, point: Point) => {\n const shape = shapes.get(space.id)!;\n return (\n within(shape.bounds, ...point) &&\n !!shape.rings[0] &&\n pointInRing(point, shape.rings[0]) &&\n !shape.rings.slice(1).some(h => pointInRing(point, h))\n );\n };\n const wallsByFloor = new Map<string, { ends: [Point, Point]; bounds: Bounds }[]>();\n for (const barrier of project.barriers) {\n const key = barrier.floorId ?? '';\n const list = wallsByFloor.get(key);\n const ends = barrierEnds(project, barrier);\n const wall = { ends, bounds: bounds(ends, REACH) };\n if (list) list.push(wall);\n else wallsByFloor.set(key, [wall]);\n }\n const shared = new Map<string, number>();\n for (const [floorKey, onFloor] of byFloor) {\n const walls = wallsByFloor.get(floorKey) ?? [];\n // Smallest-first, so a probe landing in a nested room reports the room and not the plate.\n const ordered = [...onFloor].sort((a, b) => objectArea(a) - objectArea(b));\n for (const space of onFloor) {\n // Every ring, holes included. A courtyard's edge is as much a boundary as the outer wall, and\n // a pavilion standing in that courtyard connects to nothing if only the outline is walked.\n // Probing back into the space itself is skipped below, so a hole over open void costs nothing.\n for (const ringRaw of shapes.get(space.id)!.rings) {\n if (!ringRaw?.length) continue;\n const ring = openRing(ringRaw);\n for (let i = 0; i < ring.length; i++) {\n const a = ring[i],\n b = ring[(i + 1) % ring.length];\n const len = distance(a, b);\n if (len < 0.05) continue;\n const ux = (b[0] - a[0]) / len,\n uy = (b[1] - a[1]) / len,\n nx = uy,\n ny = -ux; // outward-ish normal; both signs are tried below\n const steps = Math.max(1, Math.round(len / WALK));\n for (let s = 0; s < steps; s++) {\n const t = ((s + 0.5) / steps) * len;\n const px = a[0] + ux * t,\n py = a[1] + uy * t;\n // Built across this stretch? Then whatever is beyond it is behind a wall.\n if (walls.some(w => within(w.bounds, px, py) && segmentProjection([px, py], ...w.ends).distance < REACH))\n continue;\n for (const sign of [1, -1]) {\n const probe: Point = [px + nx * REACH * sign, py + ny * REACH * sign];\n if (inside(space, probe)) continue; // still inside ourselves\n const other = ordered.find(o => o.id !== space.id && inside(o, probe));\n if (!other) continue;\n // Shared geometry already says whether these two faces have an open edge. A coarse\n // probe must neither miss a narrow opening nor invent a shortcut at a wall junction.\n if (space.geometry?.mode === 'boundaries' && other.geometry?.mode === 'boundaries') continue;\n const key = space.id < other.id ? `${space.id}|${other.id}` : `${other.id}|${space.id}`;\n shared.set(key, (shared.get(key) ?? 0) + len / steps);\n }\n }\n }\n }\n }\n }\n const out = sharedBoundaryPortals(project);\n for (const [key, run] of shared) {\n if (run < MIN_OPENING) continue;\n const [a, b] = key.split('|');\n out.push({ id: `open:${a}:${b}`, a, b, attests: 'none' });\n }\n return out;\n}\n\n/** An inferred portal's id is deterministic — the opening or the pair of spaces it joins — so a\n * re-inference can recognise the portal it replaces and carry a person's edits across. */\nconst INFERRED = (id: string) => id.startsWith('inferred:') || id.startsWith('open:');\n\n/** Re-read the portals from the plan, keeping what a person has decided.\n *\n * Hand-authored portals (any id inference would not mint) survive wholesale. On re-inferred ones the\n * geometry is the plan's to change — which spaces, through which opening — but the semantic fields a\n * person may have set (`passage`, `attests`, `name`, `metadata`) are carried onto the replacement:\n * sealing a doorway or marking it monitored must survive the plan being redrawn around it. A portal\n * the plan no longer describes goes, and its edits go with it — they were about a way through that\n * no longer exists. */\nexport function refreshPortals(project: ProjectDocument): number {\n const previous = project.portals ?? [];\n const authored = previous.filter(p => !INFERRED(p.id));\n const held = new Set(authored.flatMap(p => (p.openingId ? [p.openingId] : [])));\n const heldPairs = new Set(authored.map(p => JSON.stringify([p.a, p.b].sort())));\n const edited = new Map(previous.filter(p => INFERRED(p.id)).map(p => [p.id, p]));\n const fresh = [...inferPortals(project), ...inferOpenBoundaries(project)]\n .filter(p => !p.openingId || !held.has(p.openingId))\n .filter(p => !p.id.startsWith('open:') || !heldPairs.has(JSON.stringify([p.a, p.b].sort())))\n .map(p => {\n const old = edited.get(p.id);\n if (!old) return p;\n if (old.passage !== undefined) p.passage = old.passage;\n if (old.attests !== undefined) p.attests = old.attests;\n if (old.name !== undefined) p.name = old.name;\n if (old.metadata !== undefined) p.metadata = old.metadata;\n return p;\n });\n project.portals = [...authored, ...fresh];\n return fresh.length;\n}\n\n/** Spaces the segment a→b cuts clean across: it enters and leaves, so the space really is in two\n * pieces. Touching a corner or stopping halfway inside does not divide anything. */\nexport function spacesDividedBy(project: ProjectDocument, floorId: string | null, a: Point, b: Point): SiteObject[] {\n const out: SiteObject[] = [];\n for (const space of project.objects) {\n if (space.floorId !== floorId || !isSpace(space.kind)) continue;\n if (space.geometry?.mode === 'boundaries') continue; // the shared graph handles the subdivision\n // A containing area names the whole floor/department. Split its rooms, keeping their\n // parent intact; cutting the parent first strands children outside their parent polygon.\n if (project.objects.some(o => o.parentId === space.id)) continue;\n const ring = footprint(space)[0];\n if (!ring?.length) continue;\n // Both ends outside, and some part of the run inside. Testing the midpoint alone is not enough:\n // a wall crossing two rooms has its midpoint in the gap between them and would divide neither.\n const interior = (point: Point) =>\n inSpace(space, point) &&\n !footprint(space).some(closed => {\n const r = openRing(closed);\n return r.some((q, i) => segmentProjection(point, q, r[(i + 1) % r.length]).distance < 1e-6);\n });\n if (interior(a) || interior(b)) continue;\n const steps = Math.max(8, Math.min(200, Math.round(distance(a, b) / 0.25)));\n let entered = false;\n for (let i = 1; i < steps && !entered; i++) {\n const t = i / steps;\n entered = inSpace(space, [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t]);\n }\n if (entered) out.push(space);\n }\n return out;\n}\n\n/** Split every space a new wall cuts across, so the plan cannot claim two walled-apart halves are one\n * place. Returns the ids of the pieces created.\n *\n * The wall is assumed to exist already — this is called *because* one was drawn — so no partition is\n * built. splitRoom would otherwise lay a second wall along the first.\n *\n * Splitting is safe in a way that joining is not: one space becomes two and both inherit everything,\n * where merging two spaces would have to destroy one identity — its name, its feed binding, whichever\n * zones it had joined. That asymmetry is why this happens automatically and merging never will. */\nexport function divideSpaces(project: ProjectDocument, floorId: string | null, a: Point, b: Point): string[] {\n const made: string[] = [];\n for (const space of spacesDividedBy(project, floorId, a, b)) {\n try {\n made.push(splitRoom(project, space.id, a, b, false));\n } catch {\n // A cut that produces a sliver, or a shape polygon-clipping will not divide, leaves the space\n // whole. Better one honest space than two broken ones.\n }\n }\n return made;\n}\n\n/** Spaces that a wall was the only thing keeping apart: remove it and they are one room again.\n *\n * Answered before the wall goes, because afterwards the evidence is gone. Two spaces qualify when\n * they share a boundary that this barrier runs along, and nothing else is built across it. */\nexport function spacesRejoinedBy(project: ProjectDocument, barrierId: string): [SiteObject, SiteObject] | null {\n const barrier = project.barriers.find(b => b.id === barrierId);\n if (!barrier) return null;\n const without: ProjectDocument = { ...project, barriers: project.barriers.filter(x => x.id !== barrierId) };\n // The rule is simply: which pair becomes open to each other once this wall is gone? Ask the same\n // inference the rest of the model uses rather than inventing a second notion of adjacency, and\n // compare against what was already open so an unrelated doorway is not mistaken for this wall.\n const already = new Set(inferOpenBoundaries(project).map(p => [p.a, p.b].sort().join('|')));\n for (const portal of inferOpenBoundaries(without)) {\n if (already.has([portal.a, portal.b].sort().join('|'))) continue;\n const one = project.objects.find(o => o.id === portal.a),\n two = project.objects.find(o => o.id === portal.b);\n if (one && two) return [one, two];\n }\n return null;\n}\n/** Merge `absorbed` into `keep`, uniting their footprints. The survivor keeps its own name, colour and\n * bindings; that is the whole reason this is never automatic — something is always discarded. */\nexport function mergeSpaces(project: ProjectDocument, keepId: string, absorbedId: string): boolean {\n const keep = project.objects.find(o => o.id === keepId),\n absorbed = project.objects.find(o => o.id === absorbedId);\n if (!keep || !absorbed || keep.id === absorbed.id) return false;\n if (keep.geometry?.mode === 'boundaries' || absorbed.geometry?.mode === 'boundaries') {\n if (!mergeBoundaryGeometry(project, keep, absorbed)) return false;\n } else {\n const united = polygonClipping.union(footprint(keep).map(closeRing), footprint(absorbed).map(closeRing));\n if (united.length !== 1) return false;\n keep.rings = united[0].map(r => closeRing(r as Point[]));\n }\n project.objects = project.objects.filter(o => o.id !== absorbedId);\n for (const o of project.objects) if (o.parentId === absorbedId) o.parentId = keepId;\n // Anything that pointed at the absorbed space now points at the survivor, then duplicates collapse.\n for (const portal of project.portals ?? []) {\n if (portal.a === absorbedId) portal.a = keepId;\n if (portal.b === absorbedId) portal.b = keepId;\n }\n project.portals = (project.portals ?? []).filter(p => p.a !== p.b);\n for (const zone of project.zones ?? [])\n zone.spaceIds = [...new Set(zone.spaceIds.map(id => (id === absorbedId ? keepId : id)))];\n pruneOntology(project);\n return true;\n}\n"],"names":["PROBE","inferPortals","project","barriers","b","out","opening","TRAVERSABLE","at","objectPosition","nx","ny","clearance","barrier","a","barrierEnds","len","distance","rad","side","sign","probe","spaceAt","WALK","REACH","MIN_OPENING","inferOpenBoundaries","byFloor","standOn","space","key","list","landed","o","isVertical","shaftKey","floors","isSpace","here","floor","servedFloors","bounds","points","pad","box","x","y","within","shapes","rings","footprint","inside","point","shape","pointInRing","h","wallsByFloor","ends","wall","shared","floorKey","onFloor","walls","ordered","objectArea","ringRaw","ring","openRing","i","ux","uy","steps","s","t","px","py","segmentProjection","other","_a","_b","sharedBoundaryPortals","run","INFERRED","id","refreshPortals","previous","authored","p","held","heldPairs","edited","fresh","old","spacesDividedBy","floorId","interior","inSpace","closed","r","q","entered","divideSpaces","made","splitRoom","spacesRejoinedBy","barrierId","without","already","portal","one","two","mergeSpaces","keepId","absorbedId","keep","absorbed","mergeBoundaryGeometry","united","polygonClipping","closeRing","zone","pruneOntology"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const r = {
|
|
2
|
+
limestone: { name: "Limestone", material: "stone", color: "#e2e4e6" },
|
|
3
|
+
brick: { name: "Warm brick", material: "brick", color: "#9b705b" },
|
|
4
|
+
darkBrick: { name: "Dark brick", material: "brick", color: "#7a4636" },
|
|
5
|
+
plaster: { name: "Ivory render", material: "plaster", color: "#e0ddd4" },
|
|
6
|
+
timber: { name: "Cedar cladding", material: "timber", color: "#ab8b68" }
|
|
7
|
+
};
|
|
8
|
+
export {
|
|
9
|
+
r as EXTERIOR_PRESETS
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=materials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"materials.js","sources":["../../../../src/model/materials.ts"],"sourcesContent":["import type { MaterialKind } from './types';\n\nexport const EXTERIOR_PRESETS = {\n limestone: { name: 'Limestone', material: 'stone', color: '#e2e4e6' },\n brick: { name: 'Warm brick', material: 'brick', color: '#9b705b' },\n darkBrick: { name: 'Dark brick', material: 'brick', color: '#7a4636' },\n plaster: { name: 'Ivory render', material: 'plaster', color: '#e0ddd4' },\n timber: { name: 'Cedar cladding', material: 'timber', color: '#ab8b68' },\n} as const satisfies Record<string, { name: string; material: MaterialKind; color: string }>;\n\nexport type ExteriorPreset = keyof typeof EXTERIOR_PRESETS;\n"],"names":["EXTERIOR_PRESETS"],"mappings":"AAEO,MAAMA,IAAmB;AAAA,EAC9B,WAAW,EAAE,MAAM,aAAa,UAAU,SAAS,OAAO,UAAA;AAAA,EAC1D,OAAO,EAAE,MAAM,cAAc,UAAU,SAAS,OAAO,UAAA;AAAA,EACvD,WAAW,EAAE,MAAM,cAAc,UAAU,SAAS,OAAO,UAAA;AAAA,EAC3D,SAAS,EAAE,MAAM,gBAAgB,UAAU,WAAW,OAAO,UAAA;AAAA,EAC7D,QAAQ,EAAE,MAAM,kBAAkB,UAAU,UAAU,OAAO,UAAA;AAC/D;"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { uid as p } from "./types.js";
|
|
2
|
+
import { splitRoom as b, addBarrier as h, removeBarrier as I, duplicateFloor as g, removeFloor as w } from "./geometry.js";
|
|
3
|
+
import { encloseRoom as v, drawBarrier as j, applyGeometryDrag as y } from "./authoring.js";
|
|
4
|
+
import { createObject as k } from "./factory.js";
|
|
5
|
+
import { pruneOntology as l, setPortalGroupMembers as E, removePortalGroup as B, addPortalGroup as S, nestZone as O, setZoneMembers as P, removeZone as C, addZone as N } from "./ontology.js";
|
|
6
|
+
import { mergeSpaces as Z, divideSpaces as G, refreshPortals as $ } from "./inference.js";
|
|
7
|
+
import { transact as F } from "./validate.js";
|
|
8
|
+
import { disconnectSpace as M, connectSpace as z, addVirtualBoundary as f, addBoundaryHole as R, drawVirtualBoundary as _ } from "./boundaries.js";
|
|
9
|
+
const K = (o, e) => {
|
|
10
|
+
var s, i, c, t;
|
|
11
|
+
switch (e.kind) {
|
|
12
|
+
case "addBuilding":
|
|
13
|
+
return o.buildings.push(structuredClone(e.building)), e.building.id;
|
|
14
|
+
case "patchBuilding":
|
|
15
|
+
return d(o.buildings, e.buildingId, e.set, ["id"]);
|
|
16
|
+
case "addFloor":
|
|
17
|
+
return o.floors.push(structuredClone(e.floor)), e.floor.id;
|
|
18
|
+
case "patchFloor":
|
|
19
|
+
return d(o.floors, e.floorId, e.set, ["id", "buildingId"]);
|
|
20
|
+
case "removeFloor":
|
|
21
|
+
return w(o, e.floorId);
|
|
22
|
+
case "duplicateFloor":
|
|
23
|
+
if (!o.floors.some((r) => r.id === e.floorId)) throw new Error("No such floor.");
|
|
24
|
+
return g(o, e.floorId);
|
|
25
|
+
case "patchProject":
|
|
26
|
+
for (const r of Object.keys(e.set))
|
|
27
|
+
if (!["name", "description", "datum", "initialFloorId"].includes(r))
|
|
28
|
+
throw new Error(`Cannot patch project ${r}.`);
|
|
29
|
+
return Object.assign(o, e.set), o.id;
|
|
30
|
+
case "patchBarrier":
|
|
31
|
+
return d(o.barriers, e.barrierId, e.set, ["id", "startId", "endId", "floorId"]);
|
|
32
|
+
case "removeBarrier":
|
|
33
|
+
I(o, e.barrierId), l(o);
|
|
34
|
+
return;
|
|
35
|
+
case "moveGeometry":
|
|
36
|
+
return y(o, e.move), e.move.id;
|
|
37
|
+
case "drawBoundary":
|
|
38
|
+
return _(o, e.floorId, e.a, e.b);
|
|
39
|
+
case "drawBarrier":
|
|
40
|
+
return (s = j(o, e.floorId, e.a, e.b, e.barrierKind)) == null ? void 0 : s.id;
|
|
41
|
+
case "encloseRoom": {
|
|
42
|
+
const r = v(o, e.floorId, e.point);
|
|
43
|
+
if (!r) throw new Error("No enclosed region at that point. Complete its walls or virtual boundaries first.");
|
|
44
|
+
return e.name && (r.room.name = e.name), r.room.id;
|
|
45
|
+
}
|
|
46
|
+
case "addHole": {
|
|
47
|
+
const r = o.objects.find((a) => a.id === e.objectId);
|
|
48
|
+
if (!((i = r == null ? void 0 : r.rings) != null && i.length)) throw new Error("Select an area with a footprint.");
|
|
49
|
+
return ((c = r.geometry) == null ? void 0 : c.mode) === "boundaries" ? R(o, r, e.ring) : r.rings.push(structuredClone(e.ring)), r.id;
|
|
50
|
+
}
|
|
51
|
+
case "patchZone":
|
|
52
|
+
for (const r of Object.keys(e.set))
|
|
53
|
+
if (!["name", "purpose", "connects", "metadata"].includes(r)) throw new Error(`Cannot patch zone ${r}.`);
|
|
54
|
+
return d(o.zones ?? [], e.zoneId, e.set, ["id"]);
|
|
55
|
+
case "addPortal": {
|
|
56
|
+
const r = p();
|
|
57
|
+
return (o.portals ?? (o.portals = [])).push({ ...structuredClone(e.portal), id: r }), r;
|
|
58
|
+
}
|
|
59
|
+
case "removePortal":
|
|
60
|
+
o.portals = (o.portals ?? []).filter((r) => r.id !== e.portalId), l(o);
|
|
61
|
+
return;
|
|
62
|
+
case "setNavigation":
|
|
63
|
+
o.navNodes = structuredClone(e.nodes), o.navEdges = structuredClone(e.edges);
|
|
64
|
+
return;
|
|
65
|
+
case "addZone":
|
|
66
|
+
return N(o, e.name, e.spaceIds, e.purpose);
|
|
67
|
+
case "removeZone":
|
|
68
|
+
C(o, e.zoneId);
|
|
69
|
+
return;
|
|
70
|
+
case "setZoneMembers":
|
|
71
|
+
P(o, e.zoneId, e.spaceIds, e.member);
|
|
72
|
+
return;
|
|
73
|
+
case "nestZone": {
|
|
74
|
+
if (!O(o, e.parentId, e.childId)) throw new Error("That nesting would make a zone contain itself.");
|
|
75
|
+
return !0;
|
|
76
|
+
}
|
|
77
|
+
case "addPortalGroup":
|
|
78
|
+
return S(o, e.name, e.portalIds);
|
|
79
|
+
case "removePortalGroup":
|
|
80
|
+
B(o, e.groupId);
|
|
81
|
+
return;
|
|
82
|
+
case "setPortalGroupMembers":
|
|
83
|
+
E(o, e.groupId, e.portalIds, e.member);
|
|
84
|
+
return;
|
|
85
|
+
case "patchPortal":
|
|
86
|
+
return d(o.portals ?? [], e.portalId, e.set, ["id", "a", "b", "openingId"]);
|
|
87
|
+
case "patchObject":
|
|
88
|
+
return d(o.objects, e.objectId, e.set, ["id", "kind"]);
|
|
89
|
+
case "addObject": {
|
|
90
|
+
const r = k(e.objectKind, e.position, e.floorId, e.name);
|
|
91
|
+
return e.set && d([r], r.id, e.set, ["id", "kind"]), o.objects.push(r), r.id;
|
|
92
|
+
}
|
|
93
|
+
case "removeObjects": {
|
|
94
|
+
const r = new Set(e.ids), a = o.objects.length;
|
|
95
|
+
if (o.objects = o.objects.filter((u) => !r.has(u.id)), o.navNodes) {
|
|
96
|
+
const u = new Set(o.navNodes.filter((n) => n.objectId && r.has(n.objectId)).map((n) => n.id));
|
|
97
|
+
o.navNodes = o.navNodes.filter((n) => !u.has(n.id)), o.navEdges = (o.navEdges ?? []).filter(
|
|
98
|
+
(n) => !u.has(n.aId) && !u.has(n.bId) && (!n.objectId || !r.has(n.objectId))
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return l(o), a - o.objects.length;
|
|
102
|
+
}
|
|
103
|
+
case "refreshPortals":
|
|
104
|
+
return $(o);
|
|
105
|
+
case "pruneOntology":
|
|
106
|
+
l(o);
|
|
107
|
+
return;
|
|
108
|
+
case "addBarrier":
|
|
109
|
+
return (t = h(o, e.a, e.b, e.floorId, e.barrierKind)) == null ? void 0 : t.id;
|
|
110
|
+
case "addBoundary":
|
|
111
|
+
return f(o, e.floorId, e.a, e.b);
|
|
112
|
+
case "connectSpace":
|
|
113
|
+
return z(o, e.objectId, e.source), e.objectId;
|
|
114
|
+
case "disconnectSpace":
|
|
115
|
+
return M(o, e.objectId), e.objectId;
|
|
116
|
+
case "splitRoom":
|
|
117
|
+
return b(o, e.roomId, e.a, e.b, e.wall);
|
|
118
|
+
case "divideSpaces":
|
|
119
|
+
return G(o, e.floorId, e.a, e.b);
|
|
120
|
+
case "setOrigin":
|
|
121
|
+
return o.origin = [...e.origin], o.origin.join(",");
|
|
122
|
+
case "mergeSpaces": {
|
|
123
|
+
if (!Z(o, e.keepId, e.absorbedId))
|
|
124
|
+
throw new Error("Those spaces do not touch, so there is nothing to merge.");
|
|
125
|
+
return !0;
|
|
126
|
+
}
|
|
127
|
+
default:
|
|
128
|
+
throw new Error(`Unknown mutation: ${String(e.kind)}`);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
function d(o, e, s, i) {
|
|
132
|
+
const c = o.find((t) => t.id === e);
|
|
133
|
+
if (!c) throw new Error(`No such entity: ${e}`);
|
|
134
|
+
for (const t of Object.keys(s))
|
|
135
|
+
if (i.includes(t) || ["__proto__", "constructor", "prototype"].includes(t))
|
|
136
|
+
throw new Error(`Cannot patch ${t}.`);
|
|
137
|
+
return Object.assign(c, structuredClone(s)), e;
|
|
138
|
+
}
|
|
139
|
+
const L = (o, e) => x(o, [e]);
|
|
140
|
+
function x(o, e) {
|
|
141
|
+
const s = [], i = F(o, (c) => {
|
|
142
|
+
for (const [t, r] of e.entries())
|
|
143
|
+
try {
|
|
144
|
+
s.push(K(c, r));
|
|
145
|
+
} catch (a) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Mutation #${t + 1} (${r.kind}): ${a instanceof Error ? a.message : String(a)}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return i.ok ? { ...i, outcomes: s } : i;
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
L as applyMutation,
|
|
155
|
+
x as applyMutations
|
|
156
|
+
};
|
|
157
|
+
//# sourceMappingURL=mutations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutations.js","sources":["../../../../src/model/mutations.ts"],"sourcesContent":["// A uniform, serializable representation of change. Every mutation is plain data — a kind and its\n// arguments — executed through the same atomic gate (`transact`): clone, apply, run every rule,\n// commit or refuse. What that buys:\n//\n// - one execution path to reason about, instead of a hand-rolled clone/validate at every call site\n// - mutations can be logged, replayed, table-tested and sent over a wire, because they are data\n// - a sequence is atomic as a whole (`applyMutations`): a migration that fails at step 7 of 12\n// leaves the document exactly as it was, not seven-twelfths changed\n//\n// The vocabulary maps 1:1 onto the authoring functions the modules already export — this file adds\n// no new behaviour, only a data shape for the behaviour that exists. Interactive gestures (dragging\n// a wall, tracing a footprint) stay as plain `transact` callbacks in the editor; a Mutation is for\n// changes worth naming.\nimport type {\n Barrier,\n Building,\n Floor,\n NavEdge,\n NavNode,\n ObjectKind,\n Origin,\n Point,\n Portal,\n PortalGroup,\n ProjectDocument,\n Ring,\n SiteObject,\n Zone,\n} from './types';\nimport { uid } from './types';\nimport { addBarrier, duplicateFloor, removeBarrier, removeFloor, splitRoom } from './geometry';\nimport { applyGeometryDrag, drawBarrier, encloseRoom, type GeometryDrag } from './authoring';\nimport { createObject } from './factory';\nimport {\n addPortalGroup,\n addZone,\n nestZone,\n pruneOntology,\n removePortalGroup,\n removeZone,\n setPortalGroupMembers,\n setZoneMembers,\n} from './ontology';\nimport { divideSpaces, mergeSpaces, refreshPortals } from './inference';\nimport { transact, type TransactResult } from './validate';\nimport { addBoundaryHole, addVirtualBoundary, connectSpace, disconnectSpace, drawVirtualBoundary } from './boundaries';\n\n/** Every mutation the schema can execute, as data. `kind` names the operation; the rest are its\n * arguments, exactly as the underlying authoring function takes them. */\nexport type Mutation =\n | { kind: 'addBuilding'; building: Building }\n | { kind: 'patchBuilding'; buildingId: string; set: Partial<Omit<Building, 'id'>> }\n | { kind: 'addFloor'; floor: Floor }\n | { kind: 'patchFloor'; floorId: string; set: Partial<Omit<Floor, 'id' | 'buildingId'>> }\n | { kind: 'removeFloor'; floorId: string }\n | { kind: 'duplicateFloor'; floorId: string }\n | { kind: 'patchProject'; set: Partial<Pick<ProjectDocument, 'name' | 'description' | 'datum' | 'initialFloorId'>> }\n | { kind: 'patchBarrier'; barrierId: string; set: Partial<Omit<Barrier, 'id' | 'startId' | 'endId' | 'floorId'>> }\n | { kind: 'removeBarrier'; barrierId: string }\n | { kind: 'moveGeometry'; move: GeometryDrag }\n | { kind: 'drawBoundary'; a: Point; b: Point; floorId: string | null }\n | { kind: 'drawBarrier'; a: Point; b: Point; floorId: string | null; barrierKind?: 'wall' | 'fence' }\n | { kind: 'encloseRoom'; floorId: string | null; point: Point; name?: string }\n | { kind: 'addHole'; objectId: string; ring: Ring }\n | { kind: 'patchZone'; zoneId: string; set: Partial<Pick<Zone, 'name' | 'purpose' | 'connects' | 'metadata'>> }\n | { kind: 'addPortal'; portal: Omit<Portal, 'id'> }\n | { kind: 'removePortal'; portalId: string }\n | { kind: 'setNavigation'; nodes: NavNode[]; edges: NavEdge[] }\n | { kind: 'addZone'; name: string; spaceIds: string[]; purpose?: string }\n | { kind: 'removeZone'; zoneId: string }\n | { kind: 'setZoneMembers'; zoneId: string; spaceIds: string[]; member: boolean }\n | { kind: 'nestZone'; parentId: string; childId: string }\n | { kind: 'addPortalGroup'; name: string; portalIds: string[] }\n | { kind: 'removePortalGroup'; groupId: string }\n | { kind: 'setPortalGroupMembers'; groupId: string; portalIds: string[]; member: boolean }\n // patchPortal deliberately cannot touch a/b/openingId — which spaces a portal joins is the\n // plan's to say (inference or an explicit new portal), never a field edit. patchObject is wide\n // open bar id and kind: every field it can set is re-checked by the full rule set on commit.\n | { kind: 'patchPortal'; portalId: string; set: Partial<Omit<Portal, 'id' | 'a' | 'b' | 'openingId'>> }\n | { kind: 'patchObject'; objectId: string; set: Partial<Omit<SiteObject, 'id' | 'kind'>> }\n // addObject seeds with createObject's defaults, then applies the given fields — so a mutation\n // script says only what differs (rings, width, a barrier binding), not every dimension.\n | {\n kind: 'addObject';\n objectKind: ObjectKind;\n name: string;\n position: Point;\n floorId: string | null;\n set?: Partial<Omit<SiteObject, 'id' | 'kind'>>;\n }\n | { kind: 'removeObjects'; ids: string[] }\n | { kind: 'refreshPortals' }\n | { kind: 'pruneOntology' }\n | { kind: 'addBarrier'; a: Point; b: Point; floorId: string | null; barrierKind: 'wall' | 'fence' }\n | { kind: 'addBoundary'; a: Point; b: Point; floorId: string | null }\n | { kind: 'connectSpace'; objectId: string; source?: 'outline' | 'walls' }\n | { kind: 'disconnectSpace'; objectId: string }\n | { kind: 'splitRoom'; roomId: string; a: Point; b: Point; wall?: boolean }\n | { kind: 'divideSpaces'; floorId: string | null; a: Point; b: Point }\n | { kind: 'mergeSpaces'; keepId: string; absorbedId: string }\n /** Move or turn the whole site. The geometry is untouched — local metres stay what they were; only\n * where the frame is pinned and which way it faces change. A plan carries no heading, so this is\n * how an imported one is squared onto the street it actually stands on. */\n | { kind: 'setOrigin'; origin: Origin };\n\n/** What a mutation reports back, beyond the new document: the entity it created, or whether it did\n * anything at all. Read it off the result rather than hunting the document for what changed.\n * Entity outcomes are views INTO the frozen result — read them freely, but editing one throws;\n * the next change goes through the next transaction like every other. */\nexport type MutationOutcome = Zone | PortalGroup | string[] | string | boolean | number | undefined;\n\nconst run = (draft: ProjectDocument, m: Mutation): MutationOutcome => {\n switch (m.kind) {\n case 'addBuilding':\n draft.buildings.push(structuredClone(m.building));\n return m.building.id;\n case 'patchBuilding':\n return patch(draft.buildings, m.buildingId, m.set, ['id']);\n case 'addFloor':\n draft.floors.push(structuredClone(m.floor));\n return m.floor.id;\n case 'patchFloor':\n return patch(draft.floors, m.floorId, m.set, ['id', 'buildingId']);\n case 'removeFloor':\n return removeFloor(draft, m.floorId);\n case 'duplicateFloor':\n if (!draft.floors.some(f => f.id === m.floorId)) throw new Error('No such floor.');\n return duplicateFloor(draft, m.floorId);\n case 'patchProject':\n for (const key of Object.keys(m.set))\n if (!['name', 'description', 'datum', 'initialFloorId'].includes(key))\n throw new Error(`Cannot patch project ${key}.`);\n Object.assign(draft, m.set);\n return draft.id;\n case 'patchBarrier':\n return patch(draft.barriers, m.barrierId, m.set, ['id', 'startId', 'endId', 'floorId']);\n case 'removeBarrier':\n removeBarrier(draft, m.barrierId);\n pruneOntology(draft);\n return undefined;\n case 'moveGeometry':\n applyGeometryDrag(draft, m.move);\n return m.move.id;\n case 'drawBoundary':\n return drawVirtualBoundary(draft, m.floorId, m.a, m.b);\n case 'drawBarrier':\n return drawBarrier(draft, m.floorId, m.a, m.b, m.barrierKind)?.id;\n case 'encloseRoom': {\n const result = encloseRoom(draft, m.floorId, m.point);\n if (!result) throw new Error('No enclosed region at that point. Complete its walls or virtual boundaries first.');\n if (m.name) result.room.name = m.name;\n return result.room.id;\n }\n case 'addHole': {\n const object = draft.objects.find(o => o.id === m.objectId);\n if (!object?.rings?.length) throw new Error('Select an area with a footprint.');\n if (object.geometry?.mode === 'boundaries') addBoundaryHole(draft, object, m.ring);\n else object.rings.push(structuredClone(m.ring));\n return object.id;\n }\n case 'patchZone':\n for (const key of Object.keys(m.set))\n if (!['name', 'purpose', 'connects', 'metadata'].includes(key)) throw new Error(`Cannot patch zone ${key}.`);\n return patch(draft.zones ?? [], m.zoneId, m.set, ['id']);\n case 'addPortal': {\n const id = uid();\n (draft.portals ??= []).push({ ...structuredClone(m.portal), id });\n return id;\n }\n case 'removePortal':\n draft.portals = (draft.portals ?? []).filter(p => p.id !== m.portalId);\n pruneOntology(draft);\n return undefined;\n case 'setNavigation':\n draft.navNodes = structuredClone(m.nodes);\n draft.navEdges = structuredClone(m.edges);\n return undefined;\n case 'addZone':\n return addZone(draft, m.name, m.spaceIds, m.purpose);\n case 'removeZone':\n removeZone(draft, m.zoneId);\n return undefined;\n case 'setZoneMembers':\n setZoneMembers(draft, m.zoneId, m.spaceIds, m.member);\n return undefined;\n case 'nestZone': {\n // A refused nesting (it would cycle) is an error, not a shrug: data-shaped callers cannot\n // check a boolean the way code-shaped callers can, so the refusal must surface as one.\n if (!nestZone(draft, m.parentId, m.childId)) throw new Error('That nesting would make a zone contain itself.');\n return true;\n }\n case 'addPortalGroup':\n return addPortalGroup(draft, m.name, m.portalIds);\n case 'removePortalGroup':\n removePortalGroup(draft, m.groupId);\n return undefined;\n case 'setPortalGroupMembers':\n setPortalGroupMembers(draft, m.groupId, m.portalIds, m.member);\n return undefined;\n case 'patchPortal': {\n return patch(draft.portals ?? [], m.portalId, m.set, ['id', 'a', 'b', 'openingId']);\n }\n case 'patchObject': {\n return patch(draft.objects, m.objectId, m.set, ['id', 'kind']);\n }\n case 'addObject': {\n const object = createObject(m.objectKind, m.position, m.floorId, m.name);\n if (m.set) patch([object], object.id, m.set, ['id', 'kind']);\n draft.objects.push(object);\n return object.id;\n }\n case 'removeObjects': {\n const gone = new Set(m.ids);\n const before = draft.objects.length;\n draft.objects = draft.objects.filter(o => !gone.has(o.id));\n // Everything that pointed at the removed objects follows them out: ontology references via\n // pruneOntology, and any authored graph nodes/edges bound to them — validation refuses\n // dangling references, so leaving them would turn this delete into a refused transaction.\n if (draft.navNodes) {\n const deadNodes = new Set(draft.navNodes.filter(n => n.objectId && gone.has(n.objectId)).map(n => n.id));\n draft.navNodes = draft.navNodes.filter(n => !deadNodes.has(n.id));\n draft.navEdges = (draft.navEdges ?? []).filter(\n e => !deadNodes.has(e.aId) && !deadNodes.has(e.bId) && (!e.objectId || !gone.has(e.objectId)),\n );\n }\n pruneOntology(draft);\n return before - draft.objects.length;\n }\n case 'refreshPortals':\n return refreshPortals(draft);\n case 'pruneOntology':\n pruneOntology(draft);\n return undefined;\n case 'addBarrier':\n return addBarrier(draft, m.a, m.b, m.floorId, m.barrierKind)?.id;\n case 'addBoundary':\n return addVirtualBoundary(draft, m.floorId, m.a, m.b);\n case 'connectSpace':\n connectSpace(draft, m.objectId, m.source);\n return m.objectId;\n case 'disconnectSpace':\n disconnectSpace(draft, m.objectId);\n return m.objectId;\n case 'splitRoom':\n return splitRoom(draft, m.roomId, m.a, m.b, m.wall);\n case 'divideSpaces':\n return divideSpaces(draft, m.floorId, m.a, m.b);\n case 'setOrigin': {\n draft.origin = [...m.origin] as Origin;\n return draft.origin.join(',');\n }\n case 'mergeSpaces': {\n if (!mergeSpaces(draft, m.keepId, m.absorbedId))\n throw new Error('Those spaces do not touch, so there is nothing to merge.');\n return true;\n }\n default:\n throw new Error(`Unknown mutation: ${String((m as { kind?: unknown }).kind)}`);\n }\n};\n\nfunction patch<T extends { id: string }>(items: T[], id: string, set: object, immutable: string[]) {\n const item = items.find(x => x.id === id);\n if (!item) throw new Error(`No such entity: ${id}`);\n for (const key of Object.keys(set))\n if (immutable.includes(key) || ['__proto__', 'constructor', 'prototype'].includes(key))\n throw new Error(`Cannot patch ${key}.`);\n Object.assign(item, structuredClone(set));\n return id;\n}\n\nexport type MutationResult = TransactResult & { outcomes?: MutationOutcome[] };\n\n/** Execute one mutation atomically. Sugar over `applyMutations` with a single step. */\nexport const applyMutation = (project: ProjectDocument, mutation: Mutation): MutationResult =>\n applyMutations(project, [mutation]);\n\n/** Execute a sequence of mutations as ONE transaction: all of them apply and the result passes\n * every rule, or none of them do and `error` says why. `outcomes` reports what each step returned\n * (created zones, split-off ids), in order — present only on success. */\nexport function applyMutations(project: ProjectDocument, mutations: Mutation[]): MutationResult {\n const outcomes: MutationOutcome[] = [];\n const result = transact(project, draft => {\n for (const [index, mutation] of mutations.entries()) {\n try {\n outcomes.push(run(draft, mutation));\n } catch (error) {\n throw new Error(\n `Mutation #${index + 1} (${mutation.kind}): ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n });\n return result.ok ? { ...result, outcomes } : result;\n}\n"],"names":["run","draft","m","patch","removeFloor","f","duplicateFloor","key","removeBarrier","pruneOntology","applyGeometryDrag","drawVirtualBoundary","_a","drawBarrier","result","encloseRoom","object","o","_b","_c","id","uid","p","addZone","removeZone","setZoneMembers","nestZone","addPortalGroup","removePortalGroup","setPortalGroupMembers","createObject","gone","before","deadNodes","e","refreshPortals","_d","addBarrier","addVirtualBoundary","connectSpace","disconnectSpace","splitRoom","divideSpaces","mergeSpaces","items","set","immutable","item","x","applyMutation","project","mutation","applyMutations","mutations","outcomes","transact","index","error"],"mappings":"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"}
|