@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,49 @@
|
|
|
1
|
+
import "polygon-clipping";
|
|
2
|
+
import { barrierEnds as x, objectPosition as $, footprint as M } from "../model/geometry.js";
|
|
3
|
+
import { isSpace as u } from "../model/types.js";
|
|
4
|
+
const b = (o) => o.replace(/[&<>"']/g, (a) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[a]);
|
|
5
|
+
function S(o, a = 1600) {
|
|
6
|
+
var g;
|
|
7
|
+
const h = [];
|
|
8
|
+
for (const t of o.objects) for (const n of t.rings ?? []) h.push(...n);
|
|
9
|
+
for (const t of o.barriers) h.push(...x(o, t));
|
|
10
|
+
if (!h.length)
|
|
11
|
+
return '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="60"><text x="10" y="30">empty document</text></svg>';
|
|
12
|
+
const f = h.reduce(
|
|
13
|
+
(t, n) => [Math.min(t[0], n[0]), Math.min(t[1], n[1]), Math.max(t[2], n[0]), Math.max(t[3], n[1])],
|
|
14
|
+
[1 / 0, 1 / 0, -1 / 0, -1 / 0]
|
|
15
|
+
), [d, m, w, l] = [f[0] - 1, f[1] - 1, f[2] + 1, f[3] + 1], c = Math.min(a / (w - d), 2e3 / (l - m)), k = Math.max(60, Math.round((l - m) * c)), s = (t) => ((t - d) * c).toFixed(1), e = (t) => ((l - t) * c).toFixed(1), r = [];
|
|
16
|
+
for (const t of o.objects)
|
|
17
|
+
if (u(t.kind) && ((g = t.rings) != null && g.length)) {
|
|
18
|
+
const n = t.rings.map((y) => `M ${y.map((p) => `${s(p[0])} ${e(p[1])}`).join(" L ")} Z`).join(" ");
|
|
19
|
+
r.push(`<path d="${n}" fill="#dde6da" fill-rule="evenodd" stroke="#7a8577" stroke-width="1"/>`);
|
|
20
|
+
const i = $(o, t);
|
|
21
|
+
r.push(
|
|
22
|
+
`<text x="${s(i[0])}" y="${e(i[1])}" font-size="12" text-anchor="middle" fill="#3c4440">${b(t.name)}</text>`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
for (const t of o.barriers) {
|
|
26
|
+
const [n, i] = x(o, t);
|
|
27
|
+
r.push(
|
|
28
|
+
`<line x1="${s(n[0])}" y1="${e(n[1])}" x2="${s(i[0])}" y2="${e(i[1])}" stroke="#2b2f2c" stroke-width="${Math.max(2, t.thickness * c)}" stroke-linecap="square"/>`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
for (const t of o.objects)
|
|
32
|
+
if (t.kind === "door" || t.kind === "window" || t.kind === "gate") {
|
|
33
|
+
const n = $(o, t);
|
|
34
|
+
r.push(
|
|
35
|
+
`<circle cx="${s(n[0])}" cy="${e(n[1])}" r="4" fill="${t.kind === "window" ? "#4a90d9" : "#d97b4a"}"/>`
|
|
36
|
+
);
|
|
37
|
+
} else if (!u(t.kind)) {
|
|
38
|
+
const n = M(t)[0];
|
|
39
|
+
n != null && n.length && r.push(
|
|
40
|
+
`<path d="M ${n.map((i) => `${s(i[0])} ${e(i[1])}`).join(" L ")} Z" fill="none" stroke="#9a62c9" stroke-width="1"/>`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${a}" height="${k}"><rect width="100%" height="100%" fill="white"/><g font-family="system-ui">${r.join(`
|
|
44
|
+
`)}</g></svg>`;
|
|
45
|
+
}
|
|
46
|
+
export {
|
|
47
|
+
S as documentSvg
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=documentSvg.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documentSvg.js","sources":["../../../../src/import/documentSvg.ts"],"sourcesContent":["// Render a Kerros ProjectDocument to a plain SVG plan view — spaces as filled plates with names,\n// barriers as strokes, openings as dots, other objects as outlines. This is the \"what the document\n// says\" side of a visual diff against a rendering of the source drawing; both the apply CLI and the\n// import agent use it, so a person and a vision model see the same picture.\nimport { barrierEnds, footprint, isSpace, objectPosition } from '../schema';\nimport type { ProjectDocument } from '../schema';\n\nconst escapeXml = (text: string) =>\n text.replace(/[&<>\"']/g, c => ({ '&': '&', '<': '<', '>': '>', '\"': '"', \"'\": ''' })[c]!);\n\nexport function documentSvg(doc: ProjectDocument, width = 1600): string {\n const pts: [number, number][] = [];\n for (const o of doc.objects) for (const ring of o.rings ?? []) pts.push(...(ring as [number, number][]));\n for (const b of doc.barriers) pts.push(...barrierEnds(doc, b));\n if (!pts.length)\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"200\" height=\"60\"><text x=\"10\" y=\"30\">empty document</text></svg>`;\n const bounds = pts.reduce(\n (b, p) => [Math.min(b[0], p[0]), Math.min(b[1], p[1]), Math.max(b[2], p[0]), Math.max(b[3], p[1])],\n [Infinity, Infinity, -Infinity, -Infinity],\n );\n const [x0, y0, x1, y1] = [bounds[0] - 1, bounds[1] - 1, bounds[2] + 1, bounds[3] + 1];\n const S = Math.min(width / (x1 - x0), 2000 / (y1 - y0)),\n H = Math.max(60, Math.round((y1 - y0) * S));\n const X = (v: number) => ((v - x0) * S).toFixed(1);\n const Y = (v: number) => ((y1 - v) * S).toFixed(1);\n const shapes: string[] = [];\n for (const o of doc.objects) {\n if (isSpace(o.kind) && o.rings?.length) {\n const d = o.rings.map(r => `M ${r.map(p => `${X(p[0])} ${Y(p[1])}`).join(' L ')} Z`).join(' ');\n shapes.push(`<path d=\"${d}\" fill=\"#dde6da\" fill-rule=\"evenodd\" stroke=\"#7a8577\" stroke-width=\"1\"/>`);\n const at = objectPosition(doc, o);\n shapes.push(\n `<text x=\"${X(at[0])}\" y=\"${Y(at[1])}\" font-size=\"12\" text-anchor=\"middle\" fill=\"#3c4440\">${escapeXml(o.name)}</text>`,\n );\n }\n }\n for (const b of doc.barriers) {\n const [a, z] = barrierEnds(doc, b);\n // Square caps, not the default butt: two walls meeting at a shared corner junction each end at\n // the corner POINT, so butt caps leave the outer half-thickness quadrant of the corner bare.\n // A square cap extends each stroke by half its width — exactly filling that quadrant, the same\n // job the map's 'line-cap: round' does in the editor.\n shapes.push(\n `<line x1=\"${X(a[0])}\" y1=\"${Y(a[1])}\" x2=\"${X(z[0])}\" y2=\"${Y(z[1])}\" stroke=\"#2b2f2c\" stroke-width=\"${Math.max(2, b.thickness * S)}\" stroke-linecap=\"square\"/>`,\n );\n }\n for (const o of doc.objects) {\n if (o.kind === 'door' || o.kind === 'window' || o.kind === 'gate') {\n const at = objectPosition(doc, o);\n shapes.push(\n `<circle cx=\"${X(at[0])}\" cy=\"${Y(at[1])}\" r=\"4\" fill=\"${o.kind === 'window' ? '#4a90d9' : '#d97b4a'}\"/>`,\n );\n } else if (!isSpace(o.kind)) {\n const b = footprint(o)[0];\n if (b?.length)\n shapes.push(\n `<path d=\"M ${b.map(p => `${X(p[0])} ${Y(p[1])}`).join(' L ')} Z\" fill=\"none\" stroke=\"#9a62c9\" stroke-width=\"1\"/>`,\n );\n }\n }\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${width}\" height=\"${H}\"><rect width=\"100%\" height=\"100%\" fill=\"white\"/><g font-family=\"system-ui\">${shapes.join('\\n')}</g></svg>`;\n}\n"],"names":["escapeXml","text","c","documentSvg","doc","width","pts","o","ring","b","barrierEnds","bounds","p","x0","y0","x1","y1","S","H","X","v","Y","shapes","isSpace","_a","d","r","at","objectPosition","a","z","footprint"],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function r(e) {
|
|
2
|
+
const t = e;
|
|
3
|
+
if (!t || typeof t.id != "string" || !/^[a-zA-Z0-9_-]{1,100}$/.test(t.id) || typeof t.text != "string" || !t.text.trim() || t.text.length > 2e3)
|
|
4
|
+
throw new Error("Instructions must contain 1–2,000 characters and a valid message ID.");
|
|
5
|
+
return { id: t.id, text: t.text.trim() };
|
|
6
|
+
}
|
|
7
|
+
function i(...e) {
|
|
8
|
+
const t = [
|
|
9
|
+
...new Map(
|
|
10
|
+
e.flat().map(r).map((n) => [n.id, n])
|
|
11
|
+
).values()
|
|
12
|
+
];
|
|
13
|
+
for (; t.length > 20 || JSON.stringify(t).length > 8e3; ) t.shift();
|
|
14
|
+
return t;
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
i as mergeImportInstructions,
|
|
18
|
+
r as readImportInstruction
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=instructions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instructions.js","sources":["../../../../src/import/instructions.ts"],"sourcesContent":["export interface ImportInstruction {\n id: string;\n text: string;\n}\nexport function readImportInstruction(value: unknown): ImportInstruction {\n const m = value as ImportInstruction;\n if (\n !m ||\n typeof m.id !== 'string' ||\n !/^[a-zA-Z0-9_-]{1,100}$/.test(m.id) ||\n typeof m.text !== 'string' ||\n !m.text.trim() ||\n m.text.length > 2000\n )\n throw new Error('Instructions must contain 1–2,000 characters and a valid message ID.');\n return { id: m.id, text: m.text.trim() };\n}\n/** Keep the latest human steering separately from model-authored notes. */\nexport function mergeImportInstructions(...groups: ImportInstruction[][]): ImportInstruction[] {\n const merged = [\n ...new Map(\n groups\n .flat()\n .map(readImportInstruction)\n .map(m => [m.id, m]),\n ).values(),\n ];\n while (merged.length > 20 || JSON.stringify(merged).length > 8000) merged.shift();\n return merged;\n}\n"],"names":["readImportInstruction","value","m","mergeImportInstructions","groups","merged"],"mappings":"AAIO,SAASA,EAAsBC,GAAmC;AACvE,QAAMC,IAAID;AACV,MACE,CAACC,KACD,OAAOA,EAAE,MAAO,YAChB,CAAC,yBAAyB,KAAKA,EAAE,EAAE,KACnC,OAAOA,EAAE,QAAS,YAClB,CAACA,EAAE,KAAK,KAAA,KACRA,EAAE,KAAK,SAAS;AAEhB,UAAM,IAAI,MAAM,sEAAsE;AACxF,SAAO,EAAE,IAAIA,EAAE,IAAI,MAAMA,EAAE,KAAK,OAAK;AACvC;AAEO,SAASC,KAA2BC,GAAoD;AAC7F,QAAMC,IAAS;AAAA,IACb,GAAG,IAAI;AAAA,MACLD,EACG,KAAA,EACA,IAAIJ,CAAqB,EACzB,IAAI,CAAAE,MAAK,CAACA,EAAE,IAAIA,CAAC,CAAC;AAAA,IAAA,EACrB,OAAA;AAAA,EAAO;AAEX,SAAOG,EAAO,SAAS,MAAM,KAAK,UAAUA,CAAM,EAAE,SAAS,MAAM,CAAAA,EAAO,MAAA;AAC1E,SAAOA;AACT;"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { along as f } from "./walls.js";
|
|
2
|
+
const l = (i, o, r, e) => i.filter((t) => {
|
|
3
|
+
const n = o.axis === "h" ? t[0] : t[1], s = o.axis === "h" ? t[1] : t[0];
|
|
4
|
+
return n > r - 0.1 && n < e + 0.1 && Math.abs(s - o.c) < 0.9;
|
|
5
|
+
}).map((t) => o.axis === "h" ? t[0] : t[1]);
|
|
6
|
+
function m(i, o, r, e) {
|
|
7
|
+
const t = [...i].sort((a, h) => a - h), n = [];
|
|
8
|
+
let s = 0;
|
|
9
|
+
for (let a = 1; a <= t.length; a++)
|
|
10
|
+
if (a === t.length || t[a] - t[a - 1] > o) {
|
|
11
|
+
const h = a - s, c = t[a - 1] - t[s];
|
|
12
|
+
h >= r && c >= e && n.push([t[s], t[a - 1]]), s = a;
|
|
13
|
+
}
|
|
14
|
+
return n;
|
|
15
|
+
}
|
|
16
|
+
function x(i, o, r) {
|
|
17
|
+
const e = m(l(o, i, i.lo, i.hi), 0.4, 3, 0.5).map(
|
|
18
|
+
([n, s]) => ({
|
|
19
|
+
kind: "door",
|
|
20
|
+
centre: (n + s) / 2,
|
|
21
|
+
width: Math.min(3, Math.max(0.8, s - n + 0.15))
|
|
22
|
+
})
|
|
23
|
+
), t = M(l(r, i, i.lo, i.hi)).filter(
|
|
24
|
+
(n) => !e.some((s) => Math.abs(s.centre - n.centre) < (s.width + n.width) / 2 + 0.1)
|
|
25
|
+
);
|
|
26
|
+
return [...e, ...t];
|
|
27
|
+
}
|
|
28
|
+
function M(i) {
|
|
29
|
+
const o = m(i, 0.08, 1, 0), r = [];
|
|
30
|
+
for (let e = 0; e + 1 < o.length; ) {
|
|
31
|
+
const t = o[e + 1][0] - o[e][1];
|
|
32
|
+
if (t >= 0.25 && t <= 3) {
|
|
33
|
+
const n = o[e][0], s = o[e + 1][1];
|
|
34
|
+
r.push({ kind: "window", centre: (n + s) / 2, width: Math.max(0.4, s - n) }), e += 2;
|
|
35
|
+
} else e += 1;
|
|
36
|
+
}
|
|
37
|
+
return r;
|
|
38
|
+
}
|
|
39
|
+
function g(i, o, r, e) {
|
|
40
|
+
const [t, n] = o, s = l(r, i, t, n), a = l(e, i, t, n);
|
|
41
|
+
if (s.length >= 2) {
|
|
42
|
+
const h = Math.max(t, Math.min(...s)), c = Math.min(n, Math.max(...s)), d = Math.min(n - t, Math.max(0.8, c - h + 0.2)), u = Math.min(n - d / 2, Math.max(t + d / 2, (h + c) / 2));
|
|
43
|
+
return { gap: o, openings: [{ kind: "door", centre: u, width: d }] };
|
|
44
|
+
}
|
|
45
|
+
return a.length >= 2 ? { gap: o, openings: [{ kind: "window", centre: (t + n) / 2, width: Math.min(3, n - t) }] } : { gap: o, openings: [], passage: o };
|
|
46
|
+
}
|
|
47
|
+
function b(i, o) {
|
|
48
|
+
const r = [...o].sort((n, s) => n[0] - s[0]), e = [];
|
|
49
|
+
let t = i.lo;
|
|
50
|
+
for (const [n, s] of r)
|
|
51
|
+
n > t && e.push([t, n]), t = Math.max(t, s);
|
|
52
|
+
return i.hi > t && e.push([t, i.hi]), e;
|
|
53
|
+
}
|
|
54
|
+
const k = (i, o) => f(i, o.centre).map((r) => r.toFixed(1)).join(",");
|
|
55
|
+
export {
|
|
56
|
+
b as barrierSpans,
|
|
57
|
+
k as openingLabel,
|
|
58
|
+
x as resolveEnvelopeOpenings,
|
|
59
|
+
g as resolvePartitionGap
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=openings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openings.js","sources":["../../../../src/import/openings.ts"],"sourcesContent":["// Opening resolution. Two different sources of truth, learned from the drawings themselves:\n//\n// ENVELOPE walls: the face layers alternate coverage along the wall (each layer's \"gaps\" are the\n// other's segments), so face breaks say nothing about openings there. What does is the SYMBOL\n// geometry — a door leaf and swing, a window's sill-and-frame block — which always stands in the\n// opening it fills. Envelope openings are therefore symbol clusters projected onto the wall.\n//\n// PARTITION walls: both faces genuinely break at every opening, so the paired gap IS the opening;\n// symbols only decide its kind. A partition gap with no symbol at all is a doorless passage —\n// modelled as NO wall (the barrier splits around it), not as a sealed one.\nimport type { Point } from '../schema';\nimport { along, type Wall } from './walls';\n\nexport interface ResolvedOpening {\n kind: 'door' | 'window';\n /** Along-axis centre and width, in the wall's own coordinate. */\n centre: number;\n width: number;\n}\nexport interface GapResolution {\n gap: [number, number];\n openings: ResolvedOpening[];\n /** A stretch with no wall at all — the barrier must be split around it. */\n passage?: [number, number];\n}\n\n/** Along-coordinates of the symbol points standing in this wall's band. */\nconst inBand = (pts: Point[], w: Wall, lo: number, hi: number): number[] =>\n pts\n .filter(p => {\n const alongP = w.axis === 'h' ? p[0] : p[1];\n const crossP = w.axis === 'h' ? p[1] : p[0];\n return alongP > lo - 0.1 && alongP < hi + 0.1 && Math.abs(crossP - w.c) < 0.9;\n })\n .map(p => (w.axis === 'h' ? p[0] : p[1]));\n\n/** 1D clustering along the wall: sorted alongs split wherever consecutive points sit further apart\n * than `split`. Small or short clusters are noise, not openings. */\nfunction clusters(alongs: number[], split: number, minPoints: number, minExtent: number): [number, number][] {\n const sorted = [...alongs].sort((a, b) => a - b);\n const out: [number, number][] = [];\n let start = 0;\n for (let i = 1; i <= sorted.length; i++) {\n if (i === sorted.length || sorted[i] - sorted[i - 1] > split) {\n const size = i - start;\n const extent = sorted[i - 1] - sorted[start];\n if (size >= minPoints && extent >= minExtent) out.push([sorted[start], sorted[i - 1]]);\n start = i;\n }\n }\n return out;\n}\n\n/** Envelope openings: door and window symbol clusters projected onto the wall. Door clusters win\n * where the two overlap — a swing sweeps across the window symbols beside it. */\nexport function resolveEnvelopeOpenings(w: Wall, doorPts: Point[], windowPts: Point[]): ResolvedOpening[] {\n const doors = clusters(inBand(doorPts, w, w.lo, w.hi), 0.4, 3, 0.5).map(\n ([lo, hi]): ResolvedOpening => ({\n kind: 'door',\n centre: (lo + hi) / 2,\n width: Math.min(3, Math.max(0.8, hi - lo + 0.15)),\n }),\n );\n const windows = windowsFromJambs(inBand(windowPts, w, w.lo, w.hi)).filter(\n win => !doors.some(d => Math.abs(d.centre - win.centre) < (d.width + win.width) / 2 + 0.1),\n );\n return [...doors, ...windows];\n}\n\n/** Read windows off their jambs.\n *\n * A window symbol is not one blob of geometry: it is a dense little block at each jamb with the\n * glass between them drawn as nothing at all. So the biggest gap in a window is INSIDE it — around\n * 0.7 m for an ordinary casement — while the gap between two windows in a bank is the mullion, a\n * tenth of a metre. Clustering by gap size therefore cannot work and did not: splitting on anything\n * wide enough to separate two windows split every window down its own middle, and the halves were\n * then too short to count as openings and were dropped. A three-window bank imported as no windows\n * at all.\n *\n * Reading it as the drawing draws it: cluster tightly to find the jambs, then take each consecutive\n * PAIR of jambs a plausible pane apart as one window. A gap too narrow to be glass is the mullion\n * between two windows, and the walk simply steps over it to start the next pair. */\nfunction windowsFromJambs(alongs: number[]): ResolvedOpening[] {\n // 80 mm: wider than the few centimetres a jamb block spans, narrower than any mullion.\n const jambs = clusters(alongs, 0.08, 1, 0);\n const out: ResolvedOpening[] = [];\n for (let i = 0; i + 1 < jambs.length; ) {\n const glass = jambs[i + 1][0] - jambs[i][1];\n // A pane: too narrow and this is a mullion between two windows, too wide and the two jambs\n // belong to different openings with a stretch of blank wall between them.\n if (glass >= 0.25 && glass <= 3) {\n const lo = jambs[i][0],\n hi = jambs[i + 1][1];\n out.push({ kind: 'window', centre: (lo + hi) / 2, width: Math.max(0.4, hi - lo) });\n i += 2;\n } else i += 1;\n }\n return out;\n}\n\n/** Partition gaps: the gap is the opening; symbols say the kind, and no symbol means a passage. */\nexport function resolvePartitionGap(\n w: Wall,\n gap: [number, number],\n doorPts: Point[],\n windowPts: Point[],\n): GapResolution {\n const [glo, ghi] = gap;\n const doors = inBand(doorPts, w, glo, ghi);\n const windows = inBand(windowPts, w, glo, ghi);\n if (doors.length >= 2) {\n const lo = Math.max(glo, Math.min(...doors));\n const hi = Math.min(ghi, Math.max(...doors));\n const width = Math.min(ghi - glo, Math.max(0.8, hi - lo + 0.2));\n const centre = Math.min(ghi - width / 2, Math.max(glo + width / 2, (lo + hi) / 2));\n return { gap, openings: [{ kind: 'door', centre, width }] };\n }\n if (windows.length >= 2)\n return { gap, openings: [{ kind: 'window', centre: (glo + ghi) / 2, width: Math.min(3, ghi - glo) }] };\n return { gap, openings: [], passage: gap };\n}\n\n/** The barrier spans a wall keeps once its passages are cut out of it. */\nexport function barrierSpans(w: Wall, passages: [number, number][]): [number, number][] {\n const sorted = [...passages].sort((x, y) => x[0] - y[0]);\n const spans: [number, number][] = [];\n let start = w.lo;\n for (const [plo, phi] of sorted) {\n if (plo > start) spans.push([start, plo]);\n start = Math.max(start, phi);\n }\n if (w.hi > start) spans.push([start, w.hi]);\n return spans;\n}\n\nexport const openingLabel = (w: Wall, o: ResolvedOpening) =>\n along(w, o.centre)\n .map(v => v.toFixed(1))\n .join(',');\n"],"names":["inBand","pts","w","lo","hi","p","alongP","crossP","clusters","alongs","split","minPoints","minExtent","sorted","b","out","start","i","size","extent","resolveEnvelopeOpenings","doorPts","windowPts","doors","windows","windowsFromJambs","d","win","jambs","glass","resolvePartitionGap","gap","glo","ghi","width","centre","barrierSpans","passages","x","y","spans","plo","phi","openingLabel","along","v"],"mappings":"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"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { enclosedRegions as N, spaceAt as L } from "../model/spaces.js";
|
|
2
|
+
import { refreshPortals as O } from "../model/inference.js";
|
|
3
|
+
import { MIN_SEGMENT as P, addBarrier as T, distance as F, barrierEnds as z, segmentProjection as v, centroid as B, OPENING_MIN_SEGMENT as _ } from "../model/geometry.js";
|
|
4
|
+
import { createObject as I } from "../model/factory.js";
|
|
5
|
+
import { addVirtualBoundary as D, normalizeBoundaries as V, boundaryRegions as W, bindSpaceToRegion as Y, boundaryEdges as A } from "../model/boundaries.js";
|
|
6
|
+
import { VERTEX_LAYERS as X } from "./types.js";
|
|
7
|
+
import { resolveEnvelopeOpenings as q, resolvePartitionGap as C, barrierSpans as H, openingLabel as J } from "./openings.js";
|
|
8
|
+
import { symbolPoints as K, symbolVertices as Q, faceSegments as y, mergeRuns as S } from "./runs.js";
|
|
9
|
+
import { pairWalls as G, closeCorners as U, addGap as Z, along as $, snapEnds as oo } from "./walls.js";
|
|
10
|
+
function xo(n, i, u) {
|
|
11
|
+
const f = u.layers ?? X, { floorId: r } = u, s = {
|
|
12
|
+
walls: 0,
|
|
13
|
+
stairs: 0,
|
|
14
|
+
rooms: 0,
|
|
15
|
+
doors: 0,
|
|
16
|
+
windows: 0,
|
|
17
|
+
passages: 0,
|
|
18
|
+
named: 0,
|
|
19
|
+
skipped: []
|
|
20
|
+
}, o = to(i, f.interiorFace), { envelope: t, partitions: a } = no(o, f, s), c = K(o, f.doors), h = Q(o, f.windows), d = new Map(
|
|
21
|
+
t.map((e) => [e, q(e, c, h)])
|
|
22
|
+
), m = /* @__PURE__ */ new Map();
|
|
23
|
+
for (const e of a) {
|
|
24
|
+
const p = e.gaps.map((l) => C(e, l, c, h));
|
|
25
|
+
d.set(
|
|
26
|
+
e,
|
|
27
|
+
p.flatMap((l) => l.openings)
|
|
28
|
+
), m.set(
|
|
29
|
+
e,
|
|
30
|
+
p.flatMap((l) => l.passage ? [l.passage] : [])
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return so(n, r, t, a, m, s), co(n, r, c, s), ro(n, r, o, f, s), io(n, r, a, m, s), ao(n, r, t, a, d, s), po(n, r, o, f, s), O(n), s;
|
|
34
|
+
}
|
|
35
|
+
function to(n, i) {
|
|
36
|
+
const u = y(n, i);
|
|
37
|
+
if (!u.length) throw new Error("Plan import: no interior wall faces found on the expected layer.");
|
|
38
|
+
const f = [
|
|
39
|
+
Math.min(...u.map((o) => o.axis === "h" ? o.lo : o.c)) - 1,
|
|
40
|
+
Math.max(...u.map((o) => o.axis === "h" ? o.hi : o.c)) + 1
|
|
41
|
+
], r = [
|
|
42
|
+
Math.min(...u.map((o) => o.axis === "v" ? o.lo : o.c)) - 1,
|
|
43
|
+
Math.max(...u.map((o) => o.axis === "v" ? o.hi : o.c)) + 1
|
|
44
|
+
], s = (o) => !!o && o[0] >= f[0] && o[0] <= f[1] && o[1] >= r[0] && o[1] <= r[1];
|
|
45
|
+
return n.filter((o) => {
|
|
46
|
+
var t;
|
|
47
|
+
return [o.a, o.b, o.at, o.center, (t = o.points) == null ? void 0 : t[0]].some(s);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function no(n, i, u) {
|
|
51
|
+
const f = [...y(n, i.exteriorFace), ...y(n, i.interiorFace)], r = S(f, 4.6, [0.6, 4.6]).filter((c) => c.hi - c.lo > 0.8), s = G(r, r, 0.2, 0.65, 0.8, "either");
|
|
52
|
+
U(s);
|
|
53
|
+
const o = [
|
|
54
|
+
...S(y(n, i.exteriorFace), 4.6, [0.6, 4.6]),
|
|
55
|
+
...S(y(n, i.interiorFace), 4.6, [0.6, 4.6])
|
|
56
|
+
];
|
|
57
|
+
for (const c of s)
|
|
58
|
+
for (const h of o)
|
|
59
|
+
if (!(h.axis !== c.axis || Math.abs(h.c - c.c) > c.thickness / 2 + 0.05))
|
|
60
|
+
for (const d of h.gaps) Z(c, d);
|
|
61
|
+
const t = S(y(n, i.partitionFaces), 2.6, [0.6, 2.6]), a = G(t, t, 0.05, 0.35, 0.4, "both", "overlap").filter((c) => c.hi - c.lo >= P ? !0 : (u.skipped.push(`partition stub ${(c.hi - c.lo).toFixed(2)} m at ${$(c, c.lo).map((h) => h.toFixed(1))}`), !1));
|
|
62
|
+
return oo(a, [...s, ...a]), { envelope: s, partitions: a };
|
|
63
|
+
}
|
|
64
|
+
function so(n, i, u, f, r, s) {
|
|
65
|
+
var o;
|
|
66
|
+
for (const t of [...u, ...f]) {
|
|
67
|
+
for (const [a, c] of H(t, r.get(t) ?? [])) {
|
|
68
|
+
if (c - a < P) {
|
|
69
|
+
s.skipped.push(`wall piece under ${P} m at ${$(t, a).map((m) => m.toFixed(1))}`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
T(n, $(t, a), $(t, c), i, "wall");
|
|
73
|
+
const h = n.barriers.at(-1);
|
|
74
|
+
h.thickness = Math.max(0.05, t.thickness), h.name = u.includes(t) ? "Exterior wall" : "Partition";
|
|
75
|
+
const d = (o = n.floors.find((m) => m.id === i)) == null ? void 0 : o.height;
|
|
76
|
+
d && (h.height = d), s.walls++;
|
|
77
|
+
}
|
|
78
|
+
s.passages += (r.get(t) ?? []).length;
|
|
79
|
+
}
|
|
80
|
+
eo(n, i, s);
|
|
81
|
+
}
|
|
82
|
+
function eo(n, i, u) {
|
|
83
|
+
const f = new Map(n.junctions.map((o) => [o.id, o.position])), r = new Set(
|
|
84
|
+
n.barriers.filter((o) => {
|
|
85
|
+
if (o.floorId !== i) return !1;
|
|
86
|
+
const t = f.get(o.startId), a = f.get(o.endId);
|
|
87
|
+
return t && a && Math.hypot(a[0] - t[0], a[1] - t[1]) < P;
|
|
88
|
+
}).map((o) => o.id)
|
|
89
|
+
);
|
|
90
|
+
if (!r.size) return;
|
|
91
|
+
n.barriers = n.barriers.filter((o) => !r.has(o.id)), n.objects = n.objects.filter((o) => !o.barrierId || !r.has(o.barrierId));
|
|
92
|
+
const s = new Set(A(n).flatMap((o) => [o.startId, o.endId]));
|
|
93
|
+
n.junctions = n.junctions.filter((o) => s.has(o.id)), u.skipped.push(`${r.size} wall stub${r.size > 1 ? "s" : ""} under ${P} m`);
|
|
94
|
+
}
|
|
95
|
+
function io(n, i, u, f, r) {
|
|
96
|
+
for (const o of u)
|
|
97
|
+
for (const [t, a] of f.get(o) ?? []) D(n, i, $(o, t), $(o, a));
|
|
98
|
+
V(n, i);
|
|
99
|
+
const s = W(n, i);
|
|
100
|
+
if (!s.length) {
|
|
101
|
+
r.skipped.push("no enclosed regions: the walls do not close around anything");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
for (const o of s) {
|
|
105
|
+
const t = I("room", B(o.rings[0]), i, "Room");
|
|
106
|
+
try {
|
|
107
|
+
Y(n, t, o);
|
|
108
|
+
} catch (a) {
|
|
109
|
+
r.skipped.push(`room near ${t.position.map((c) => c.toFixed(2))}: ${a.message}`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
n.objects.push(t);
|
|
113
|
+
}
|
|
114
|
+
r.rooms = n.objects.filter((o) => o.kind === "room" && o.floorId === i).length;
|
|
115
|
+
}
|
|
116
|
+
function ao(n, i, u, f, r, s) {
|
|
117
|
+
for (const o of [...u, ...f])
|
|
118
|
+
for (const t of r.get(o) ?? []) {
|
|
119
|
+
const a = $(o, t.centre), c = J(o, t);
|
|
120
|
+
let h = null;
|
|
121
|
+
for (const e of n.barriers) {
|
|
122
|
+
if (e.floorId !== i) continue;
|
|
123
|
+
const [p, l] = z(n, e), g = Math.abs(p[1] - l[1]) < 0.01 ? "h" : Math.abs(p[0] - l[0]) < 0.01 ? "v" : null;
|
|
124
|
+
if (g !== o.axis) continue;
|
|
125
|
+
const [b, x] = g === "h" ? [0, 1] : [1, 0];
|
|
126
|
+
if (!(Math.abs(p[x] - a[x]) > 0.05) && !(a[b] < Math.min(p[b], l[b]) || a[b] > Math.max(p[b], l[b]))) {
|
|
127
|
+
h = { id: e.id, offset: Math.abs(a[b] - p[b]), length: Math.abs(l[b] - p[b]) };
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!h || h.length < _) {
|
|
132
|
+
s.skipped.push(`${t.kind} has no wall piece to sit in at ${c}`);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (h.offset - t.width / 2 < 0.01 || h.offset + t.width / 2 > h.length - 0.01) {
|
|
136
|
+
s.skipped.push(`${t.kind} does not fit its wall at ${c}`);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (n.objects.some(
|
|
140
|
+
(e) => e.barrierId === h.id && Math.abs((e.offset ?? 0) - h.offset) < (e.width + t.width) / 2 + 0.05
|
|
141
|
+
)) {
|
|
142
|
+
s.skipped.push(`${t.kind} overlapping another opening at ${c}`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const m = I(t.kind, a, i, t.kind === "door" ? "Door" : "Window");
|
|
146
|
+
m.width = Math.round(t.width * 100) / 100, m.barrierId = h.id, m.offset = Math.round(h.offset * 1e3) / 1e3, m.rotation = o.axis === "h" ? 0 : 90, n.objects.push(m), t.kind === "door" ? s.doors++ : s.windows++;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function ro(n, i, u, f, r) {
|
|
150
|
+
if (!f.stairs) return;
|
|
151
|
+
const s = [];
|
|
152
|
+
for (const t of u)
|
|
153
|
+
if (f.stairs.test(t.layer) && (t.type === "LINE" && t.a && t.b && s.push([t.a, t.b]), t.type === "POLYLINE" && t.points))
|
|
154
|
+
for (let a = 1; a < t.points.length; a++) s.push([t.points[a - 1], t.points[a]]);
|
|
155
|
+
if (!s.length) return;
|
|
156
|
+
const o = [];
|
|
157
|
+
for (const t of s) {
|
|
158
|
+
const a = o.find((c) => c.some(([h, d]) => t.some((m) => F(m, h) < 1.5 || F(m, d) < 1.5)));
|
|
159
|
+
a ? a.push(t) : o.push([t]);
|
|
160
|
+
}
|
|
161
|
+
for (const t of o) {
|
|
162
|
+
const a = t.flat(), c = a.map((g) => g[0]), h = a.map((g) => g[1]), d = Math.max(...c) - Math.min(...c), m = Math.max(...h) - Math.min(...h);
|
|
163
|
+
if (Math.min(d, m) < 0.6 || Math.max(d, m) > 8) {
|
|
164
|
+
r.skipped.push(`stair symbol ${d.toFixed(1)} x ${m.toFixed(1)} m is not a flight`);
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const e = [(Math.max(...c) + Math.min(...c)) / 2, (Math.max(...h) + Math.min(...h)) / 2], p = I("stairs", e, i, "Stairs"), l = m >= d;
|
|
168
|
+
p.width = l ? d : m, p.depth = l ? m : d, p.rotation = l ? 0 : 90, p.servedFloorIds = i ? [i] : [], n.objects.push(p), r.stairs++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function co(n, i, u, f) {
|
|
172
|
+
var m;
|
|
173
|
+
const r = n.barriers.filter((e) => e.floorId === i), s = /* @__PURE__ */ new Map();
|
|
174
|
+
for (const e of r) for (const p of [e.startId, e.endId]) s.set(p, (s.get(p) ?? 0) + 1);
|
|
175
|
+
const o = (e) => n.junctions.find((p) => p.id === e).position, t = [...s].filter(([, e]) => e === 1).map(([e]) => e), a = N(n, i).length, c = [];
|
|
176
|
+
for (const e of t)
|
|
177
|
+
for (const p of s.keys()) {
|
|
178
|
+
if (e === p) continue;
|
|
179
|
+
const l = o(e), g = o(p), b = F(l, g);
|
|
180
|
+
if (b < 0.6 || b > 1.8) continue;
|
|
181
|
+
const x = Math.abs((Math.atan2(g[1] - l[1], g[0] - l[0]) * 180 / Math.PI % 90 + 90) % 90);
|
|
182
|
+
if (x < 8 || x > 82 || r.some((M) => {
|
|
183
|
+
const [E, j] = z(n, M);
|
|
184
|
+
return v(l, E, j).distance < 0.15 && v(g, E, j).distance < 0.15;
|
|
185
|
+
}))
|
|
186
|
+
continue;
|
|
187
|
+
const k = u.filter((M) => v(M, l, g).distance < 0.4).map((M) => v(M, l, g).t);
|
|
188
|
+
k.length < 6 || !k.some((M) => M < 0.2) || !k.some((M) => M > 0.8) || c.push({ from: e, to: p, span: b, score: k.length / b });
|
|
189
|
+
}
|
|
190
|
+
const h = /* @__PURE__ */ new Set();
|
|
191
|
+
let d = a;
|
|
192
|
+
for (const e of c.sort((p, l) => p.span - l.span || l.score - p.score)) {
|
|
193
|
+
if (h.has(e.from) || h.has(e.to) || (s.get(e.from) ?? 0) !== 1) continue;
|
|
194
|
+
const p = o(e.from), l = o(e.to), g = r.filter((w) => [e.from, e.to].includes(w.startId) || [e.from, e.to].includes(w.endId)), b = Math.max(0.05, Math.min(...g.map((w) => w.thickness), 0.2)), x = {
|
|
195
|
+
...n,
|
|
196
|
+
junctions: [...n.junctions, { id: "probe-a", position: p, floorId: i }, { id: "probe-b", position: l, floorId: i }],
|
|
197
|
+
barriers: [
|
|
198
|
+
...n.barriers,
|
|
199
|
+
{ ...r[0], id: "probe", startId: "probe-a", endId: "probe-b", floorId: i, thickness: b }
|
|
200
|
+
]
|
|
201
|
+
}, k = N(x, i).length;
|
|
202
|
+
if (k <= d) continue;
|
|
203
|
+
d = k, h.add(e.from), h.add(e.to), s.set(e.from, 2), T(n, p, l, i, "wall");
|
|
204
|
+
const M = n.barriers.at(-1);
|
|
205
|
+
M.thickness = b, M.name = "Partition";
|
|
206
|
+
const E = (m = n.floors.find((w) => w.id === i)) == null ? void 0 : m.height;
|
|
207
|
+
E && (M.height = E), f.walls++;
|
|
208
|
+
const j = F(p, l) - 2 * (b + 0.02);
|
|
209
|
+
if (j < 0.5) {
|
|
210
|
+
f.skipped.push(`bridged doorway too narrow to hold a door at ${p.map((w) => w.toFixed(1))}`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const R = I("door", [(p[0] + l[0]) / 2, (p[1] + l[1]) / 2], i, "Door");
|
|
214
|
+
R.width = Math.round(j * 100) / 100, R.barrierId = M.id, R.offset = Math.round(F(p, l) / 2 * 1e3) / 1e3, n.objects.push(R), f.doors++;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function po(n, i, u, f, r) {
|
|
218
|
+
for (const s of u) {
|
|
219
|
+
if (s.type !== "TEXT" || !f.labels.test(s.layer) || !s.at || !s.text || /^[\d.,\s]+$/.test(s.text)) continue;
|
|
220
|
+
const o = L(n, i, s.at);
|
|
221
|
+
if (!o || o.floorId !== i) {
|
|
222
|
+
r.skipped.push(`label "${s.text}" stands in no room`);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
o.name = s.text.trim(), r.named++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
export {
|
|
229
|
+
X as VERTEX_LAYERS,
|
|
230
|
+
co as bridgeDoorways,
|
|
231
|
+
xo as importPlanEntities
|
|
232
|
+
};
|
|
233
|
+
//# sourceMappingURL=planImport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"planImport.js","sources":["../../../../src/import/planImport.ts"],"sourcesContent":["// Deterministic import of measured CAD plan entities into a Kerros floor — the orchestrator.\n//\n// The input is the normalized, metre-unit entity JSON produced host-side from a DWG (see\n// scripts/plan-import/extract.mjs — `PlanEntity` in types.ts is the contract for its output). The\n// drawings this understands are the axis-aligned kind produced by Finnish prefab CAD: walls drawn\n// as parallel face-line pairs on semantic layers, openings as gaps in those faces, room labels as\n// text. runs.ts merges face fragments into runs; walls.ts pairs runs into walls and traces the\n// envelope ring; this file turns walls into a document.\n//\n// The method leans on machinery the editor already trusts rather than new geometry: walls become\n// barriers; doorless passages become virtual boundaries; rooms follow the bounded faces of that\n// shared network. Their usable polygons are generated from the walls, including wall thickness\n// and holes. Labels name whatever room they stand in via spaceAt. Runs inside a transact like every other authoring\n// operation, so a drawing this cannot digest refuses cleanly instead of importing garbage.\nimport {\n MIN_SEGMENT,\n OPENING_MIN_SEGMENT,\n addBarrier,\n addVirtualBoundary,\n barrierEnds,\n bindSpaceToRegion,\n boundaryEdges,\n boundaryRegions,\n centroid,\n createObject,\n distance,\n enclosedRegions,\n normalizeBoundaries,\n refreshPortals,\n segmentProjection,\n spaceAt,\n} from '../schema';\nimport type { Point, ProjectDocument } from '../schema';\nimport { VERTEX_LAYERS, type PlanEntity, type PlanImportOptions, type PlanImportReport } from './types';\nimport {\n barrierSpans,\n openingLabel,\n resolveEnvelopeOpenings,\n resolvePartitionGap,\n type ResolvedOpening,\n} from './openings';\nimport { faceSegments, mergeRuns, symbolPoints, symbolVertices } from './runs';\nimport { addGap, along, closeCorners, pairWalls, snapEnds, type Wall } from './walls';\n\n/** Import a floor's worth of plan entities into `draft`. Call inside a transaction. */\nexport function importPlanEntities(\n draft: ProjectDocument,\n entities: PlanEntity[],\n options: PlanImportOptions,\n): PlanImportReport {\n const layers = options.layers ?? VERTEX_LAYERS;\n const { floorId } = options;\n const report: PlanImportReport = {\n walls: 0,\n stairs: 0,\n rooms: 0,\n doors: 0,\n windows: 0,\n passages: 0,\n named: 0,\n skipped: [],\n };\n\n const regional = planRegion(entities, layers.interiorFace);\n const { envelope, partitions } = reconstructWalls(regional, layers, report);\n const doorPts = symbolPoints(regional, layers.doors);\n // Vertices, not samples: a window is read from where its jambs are, and sampling fills the glass\n // between them. A door is read from the cloud its swing makes, and wants the samples.\n const windowPts = symbolVertices(regional, layers.windows);\n // Envelope openings come from symbol clusters; partition openings from the paired face gaps.\n const openings = new Map<Wall, ResolvedOpening[]>(\n envelope.map(w => [w, resolveEnvelopeOpenings(w, doorPts, windowPts)]),\n );\n const passages = new Map<Wall, [number, number][]>();\n for (const w of partitions) {\n const resolved = w.gaps.map(gap => resolvePartitionGap(w, gap, doorPts, windowPts));\n openings.set(\n w,\n resolved.flatMap(r => r.openings),\n );\n passages.set(\n w,\n resolved.flatMap(r => (r.passage ? [r.passage] : [])),\n );\n }\n emitBarriers(draft, floorId, envelope, partitions, passages, report);\n // Before the rooms, because a bridged doorway is what closes the region they are read from.\n bridgeDoorways(draft, floorId, doorPts, report);\n emitStairs(draft, floorId, regional, layers, report);\n emitRooms(draft, floorId, partitions, passages, report);\n emitOpenings(draft, floorId, envelope, partitions, openings, report);\n nameRooms(draft, floorId, regional, layers, report);\n refreshPortals(draft);\n return report;\n}\n\n/** The plan region is where the envelope's inner face lives; auxiliary views elsewhere in the\n * drawing (sections, detail callouts) are outside it and ignored. */\nfunction planRegion(entities: PlanEntity[], interiorFace: RegExp): PlanEntity[] {\n const inner = faceSegments(entities, interiorFace);\n if (!inner.length) throw new Error('Plan import: no interior wall faces found on the expected layer.');\n const rx = [\n Math.min(...inner.map(s => (s.axis === 'h' ? s.lo : s.c))) - 1,\n Math.max(...inner.map(s => (s.axis === 'h' ? s.hi : s.c))) + 1,\n ];\n const ry = [\n Math.min(...inner.map(s => (s.axis === 'v' ? s.lo : s.c))) - 1,\n Math.max(...inner.map(s => (s.axis === 'v' ? s.hi : s.c))) + 1,\n ];\n const inRegion = (p?: Point) => !!p && p[0] >= rx[0] && p[0] <= rx[1] && p[1] >= ry[0] && p[1] <= ry[1];\n return entities.filter(e => [e.a, e.b, e.at, e.center, e.points?.[0]].some(inRegion));\n}\n\n/** Envelope and partition walls from the face layers. Prefab drawings put BOTH faces of the outer\n * wall on the envelope layers (the silhouette continuous, the inner face broken at openings), so\n * the wall pairs are found by self-pairing the pooled runs; opening gaps are then harvested from\n * each face layer merged on its own, because an opening does not break every line of the wall. */\nfunction reconstructWalls(\n regional: PlanEntity[],\n layers: NonNullable<PlanImportOptions['layers']>,\n report: PlanImportReport,\n): { envelope: Wall[]; partitions: Wall[] } {\n const envelopePool = [...faceSegments(regional, layers.exteriorFace), ...faceSegments(regional, layers.interiorFace)];\n const envelopeRuns = mergeRuns(envelopePool, 4.6, [0.6, 4.6]).filter(r => r.hi - r.lo > 0.8);\n const envelope = pairWalls(envelopeRuns, envelopeRuns, 0.2, 0.65, 0.8, 'either');\n closeCorners(envelope);\n const faceGapRuns = [\n ...mergeRuns(faceSegments(regional, layers.exteriorFace), 4.6, [0.6, 4.6]),\n ...mergeRuns(faceSegments(regional, layers.interiorFace), 4.6, [0.6, 4.6]),\n ];\n for (const w of envelope)\n for (const run of faceGapRuns) {\n if (run.axis !== w.axis || Math.abs(run.c - w.c) > w.thickness / 2 + 0.05) continue;\n for (const gap of run.gaps) addGap(w, gap);\n }\n\n // Doorless passages between rooms run wider than doorways — record breaks up to 2.6 m.\n const partitionRuns = mergeRuns(faceSegments(regional, layers.partitionFaces), 2.6, [0.6, 2.6]);\n const partitions = pairWalls(partitionRuns, partitionRuns, 0.05, 0.35, 0.4, 'both', 'overlap').filter(w => {\n if (w.hi - w.lo >= MIN_SEGMENT) return true;\n report.skipped.push(`partition stub ${(w.hi - w.lo).toFixed(2)} m at ${along(w, w.lo).map(v => v.toFixed(1))}`);\n return false;\n });\n snapEnds(partitions, [...envelope, ...partitions]);\n return { envelope, partitions };\n}\n\n/** Walls become barriers with their measured thickness. A doorless passage splits its wall into\n * separate barrier pieces — the opening is the absence of wall, and sealing it with a continuous\n * barrier would wall off two rooms the drawing says flow into each other. */\nfunction emitBarriers(\n draft: ProjectDocument,\n floorId: string | null,\n envelope: Wall[],\n partitions: Wall[],\n passages: Map<Wall, [number, number][]>,\n report: PlanImportReport,\n) {\n for (const w of [...envelope, ...partitions]) {\n for (const [lo, hi] of barrierSpans(w, passages.get(w) ?? [])) {\n if (hi - lo < MIN_SEGMENT) {\n report.skipped.push(`wall piece under ${MIN_SEGMENT} m at ${along(w, lo).map(v => v.toFixed(1))}`);\n continue;\n }\n addBarrier(draft, along(w, lo), along(w, hi), floorId, 'wall');\n const barrier = draft.barriers.at(-1)!;\n barrier.thickness = Math.max(0.05, w.thickness);\n barrier.name = envelope.includes(w) ? 'Exterior wall' : 'Partition';\n // A plan is flat: the drawing says nothing about how tall its walls are, so take the storey\n // the plan is being imported onto. Leaving addBarrier's generic default would stand 3.5 m\n // walls on a 2.6 m floor — they punch through the storey above, and on a basement they rise\n // out of the ground.\n const storey = draft.floors.find(f => f.id === floorId)?.height;\n if (storey) barrier.height = storey;\n report.walls++;\n }\n report.passages += (passages.get(w) ?? []).length;\n }\n dropStubs(draft, floorId, report);\n}\n\n/** Adding a wall welds a junction wherever its end lands, which splits whatever already crossed\n * there — so a wall ending close to another's end can leave behind a piece shorter than the plan is\n * allowed to hold. The document is then refused, and the refusal names the rule rather than the\n * drawing, which is no help at all to someone importing one. Drop the stubs instead, and the\n * junctions they leave orphaned, and report how many. */\nfunction dropStubs(draft: ProjectDocument, floorId: string | null, report: PlanImportReport) {\n const at = new Map(draft.junctions.map(j => [j.id, j.position]));\n const stubs = new Set(\n draft.barriers\n .filter(b => {\n if (b.floorId !== floorId) return false;\n const p = at.get(b.startId),\n q = at.get(b.endId);\n return p && q && Math.hypot(q[0] - p[0], q[1] - p[1]) < MIN_SEGMENT;\n })\n .map(b => b.id),\n );\n if (!stubs.size) return;\n draft.barriers = draft.barriers.filter(b => !stubs.has(b.id));\n draft.objects = draft.objects.filter(o => !o.barrierId || !stubs.has(o.barrierId));\n const used = new Set(boundaryEdges(draft).flatMap(b => [b.startId, b.endId]));\n draft.junctions = draft.junctions.filter(j => used.has(j.id));\n report.skipped.push(`${stubs.size} wall stub${stubs.size > 1 ? 's' : ''} under ${MIN_SEGMENT} m`);\n}\n\n/** Rooms share the measured wall network, including virtual edges across doorless passages. */\nfunction emitRooms(\n draft: ProjectDocument,\n floorId: string | null,\n partitions: Wall[],\n passages: Map<Wall, [number, number][]>,\n report: PlanImportReport,\n) {\n // A passage has no physical wall, but still divides two named rooms. Add only its missing\n // centreline span; normalization joins it to both jambs and keeps the neighbouring wall IDs.\n for (const w of partitions)\n for (const [lo, hi] of passages.get(w) ?? []) addVirtualBoundary(draft, floorId, along(w, lo), along(w, hi));\n normalizeBoundaries(draft, floorId);\n const regions = boundaryRegions(draft, floorId);\n if (!regions.length) {\n report.skipped.push('no enclosed regions: the walls do not close around anything');\n return;\n }\n for (const region of regions) {\n const room = createObject('room', centroid(region.rings[0]), floorId, 'Room');\n try {\n bindSpaceToRegion(draft, room, region);\n } catch (error) {\n // Tiny or disconnected usable pieces cannot become labelled spaces. Keep their measured\n // boundaries and report the unresolved region instead of exporting a stale polygon.\n report.skipped.push(`room near ${room.position.map(v => v.toFixed(2))}: ${(error as Error).message}`);\n continue;\n }\n draft.objects.push(room);\n }\n report.rooms = draft.objects.filter(o => o.kind === 'room' && o.floorId === floorId).length;\n}\n\n/** Resolved openings become wall-bound door/window objects on whichever barrier piece holds them.\n * Doors sit where their symbols stand — not at the centre of the face break, which often spans a\n * doorway AND the window beside it. The piece is looked up in the DOCUMENT, not remembered from\n * emit: junction welds split barriers as later walls arrive, so only the document knows the\n * pieces that actually exist. Unresolvable envelope gaps are reported, never guessed. */\nfunction emitOpenings(\n draft: ProjectDocument,\n floorId: string | null,\n envelope: Wall[],\n partitions: Wall[],\n openings: Map<Wall, ResolvedOpening[]>,\n report: PlanImportReport,\n) {\n for (const w of [...envelope, ...partitions]) {\n for (const o of openings.get(w) ?? []) {\n const p = along(w, o.centre);\n const label = openingLabel(w, o);\n // The barrier piece under the opening: same floor, same axis, on the wall's line, containing\n // the opening's centre.\n let hit: { id: string; offset: number; length: number } | null = null;\n for (const b of draft.barriers) {\n if (b.floorId !== floorId) continue;\n const [a, z] = barrierEnds(draft, b);\n const axis = Math.abs(a[1] - z[1]) < 0.01 ? 'h' : Math.abs(a[0] - z[0]) < 0.01 ? 'v' : null;\n if (axis !== w.axis) continue;\n const [i, j] = axis === 'h' ? [0, 1] : [1, 0];\n if (Math.abs(a[j] - p[j]) > 0.05) continue;\n if (p[i] < Math.min(a[i], z[i]) || p[i] > Math.max(a[i], z[i])) continue;\n hit = { id: b.id, offset: Math.abs(p[i] - a[i]), length: Math.abs(z[i] - a[i]) };\n break;\n }\n if (!hit || hit.length < OPENING_MIN_SEGMENT) {\n report.skipped.push(`${o.kind} has no wall piece to sit in at ${label}`);\n continue;\n }\n if (hit.offset - o.width / 2 < 0.01 || hit.offset + o.width / 2 > hit.length - 0.01) {\n report.skipped.push(`${o.kind} does not fit its wall at ${label}`);\n continue;\n }\n const clash = draft.objects.some(\n x => x.barrierId === hit!.id && Math.abs((x.offset ?? 0) - hit!.offset) < (x.width + o.width) / 2 + 0.05,\n );\n if (clash) {\n report.skipped.push(`${o.kind} overlapping another opening at ${label}`);\n continue;\n }\n const opening = createObject(o.kind, p, floorId, o.kind === 'door' ? 'Door' : 'Window');\n opening.width = Math.round(o.width * 100) / 100;\n opening.barrierId = hit.id;\n opening.offset = Math.round(hit.offset * 1000) / 1000;\n opening.rotation = w.axis === 'h' ? 0 : 90;\n draft.objects.push(opening);\n if (o.kind === 'door') report.doors++;\n else report.windows++;\n }\n }\n}\n\n/** The flights of stairs the drawing shows, one object per stair.\n *\n * A stair is drawn as its treads, on a layer of their own, and that block of parallel lines is the\n * flight's footprint — where it stands, how wide it is, how far it runs and which way it climbs. All\n * of which the importer had been throwing away: it read walls, openings, rooms and labels and no\n * vertical circulation at all, so every stair and lift in an imported document was something a\n * person added afterwards by hand.\n *\n * A sheet only says what is on that storey, so each says its stair serves its own floor and no\n * other. Where the same stair is drawn on the sheets above and below, importing them into one\n * document puts the same flight in the same place on each — and `servedFloors` reads a shaft's\n * reach across all of them, so the stair ends up connecting exactly the levels it is drawn on. A\n * cellar with no stair drawn simply does not get one. */\nfunction emitStairs(\n draft: ProjectDocument,\n floorId: string | null,\n regional: PlanEntity[],\n layers: NonNullable<PlanImportOptions['layers']>,\n report: PlanImportReport,\n) {\n if (!layers.stairs) return;\n const treads: [Point, Point][] = [];\n for (const e of regional) {\n if (!layers.stairs.test(e.layer)) continue;\n if (e.type === 'LINE' && e.a && e.b) treads.push([e.a, e.b]);\n if (e.type === 'POLYLINE' && e.points)\n for (let i = 1; i < e.points.length; i++) treads.push([e.points[i - 1], e.points[i]]);\n }\n if (!treads.length) return;\n // One stair, or several: a building can have a core at each end. Treads of the same flight touch\n // or nearly touch; two cores are metres apart.\n const flights: [Point, Point][][] = [];\n for (const t of treads) {\n const near = flights.find(g => g.some(([a, b]) => t.some(p => distance(p, a) < 1.5 || distance(p, b) < 1.5)));\n if (near) near.push(t);\n else flights.push([t]);\n }\n for (const group of flights) {\n const pts = group.flat();\n const xs = pts.map(p => p[0]),\n ys = pts.map(p => p[1]);\n const width = Math.max(...xs) - Math.min(...xs),\n depth = Math.max(...ys) - Math.min(...ys);\n // A flight is at least a stride wide and at least a stride long, and a staircase big enough to\n // fill a room is something else drawn on the stair layer.\n if (Math.min(width, depth) < 0.6 || Math.max(width, depth) > 8) {\n report.skipped.push(`stair symbol ${width.toFixed(1)} x ${depth.toFixed(1)} m is not a flight`);\n continue;\n }\n const centre: Point = [(Math.max(...xs) + Math.min(...xs)) / 2, (Math.max(...ys) + Math.min(...ys)) / 2];\n const stair = createObject('stairs', centre, floorId, 'Stairs');\n // A flight is longer the way you walk it than it is wide — treads are shallow and you need a lot\n // of them — so the run lies along the LONGER side of the block the treads fill. Reading it off\n // the tread lines instead does not work on the stair most buildings have: a switchback draws as\n // many lines across the flights as along the stringers between them, and counting them just\n // picks whichever the drawing happened to have more of.\n const alongY = depth >= width;\n stair.width = alongY ? width : depth;\n stair.depth = alongY ? depth : width;\n // The renderer runs a flight along the object's own depth axis, which points along -y unturned.\n stair.rotation = alongY ? 0 : 90;\n stair.servedFloorIds = floorId ? [floorId] : [];\n draft.objects.push(stair);\n report.stairs++;\n }\n}\n\n/** Close an oblique doorway the Manhattan pass could not see.\n *\n * The reconstruction reads axis-aligned faces, which is what a Finnish prefab drawing is made of —\n * except where it is not. A room cut off at 45° leaves two wall ends dangling a metre or so apart\n * with nothing between them but the door symbol drawn across the gap, and every axis-aligned pass\n * drops all three: the faces because they are oblique, the wall because it has no faces, the door\n * because it has no wall. The region then leaks and two rooms import as one.\n *\n * What the drawing is actually saying there is legible without reading oblique geometry at all: two\n * loose wall ends, a plausible door's distance apart, with door symbols standing on the line between\n * them. That is a doorway, and this bridges it — a barrier on that line carrying the door.\n *\n * Deliberately conservative. It fires only between ends that nothing else has claimed, only when the\n * span is a door's width, and only when the drawing put a door symbol there: it can close a gap the\n * drawing shows a door in, and it cannot invent a wall anywhere else. */\nexport function bridgeDoorways(\n draft: ProjectDocument,\n floorId: string | null,\n doorPts: Point[],\n report: PlanImportReport,\n) {\n const mine = draft.barriers.filter(b => b.floorId === floorId);\n // A loose end is a junction exactly one wall reaches: a wall that simply stops is the drawing\n // admitting something is missing there.\n const uses = new Map<string, number>();\n for (const b of mine) for (const id of [b.startId, b.endId]) uses.set(id, (uses.get(id) ?? 0) + 1);\n const at = (id: string) => draft.junctions.find(j => j.id === id)!.position;\n const loose = [...uses].filter(([, n]) => n === 1).map(([id]) => id);\n const before = enclosedRegions(draft, floorId).length;\n\n type Candidate = { from: string; to: string; span: number; score: number };\n const candidates: Candidate[] = [];\n for (const from of loose)\n for (const to of uses.keys()) {\n if (from === to) continue;\n const a = at(from),\n b = at(to);\n const span = distance(a, b);\n // A door's worth of gap: a generous double door is 1.6 m, a single leaf 0.9. Shorter than\n // 0.6 is a butt joint the snapping missed; wider than 1.8 is not a doorway, it is a room.\n if (span < 0.6 || span > 1.8) continue;\n // Axis-aligned gaps are the Manhattan pass's own business — a wall it broke, a passage it\n // meant to leave. This is only for what it could not represent at all.\n const angle = Math.abs(((((Math.atan2(b[1] - a[1], b[0] - a[0]) * 180) / Math.PI) % 90) + 90) % 90);\n if (angle < 8 || angle > 82) continue;\n // Nothing already runs along this line: a wall the Manhattan pass DID find is not a gap.\n if (\n mine.some(w => {\n const [wa, wb] = barrierEnds(draft, w);\n return segmentProjection(a, wa, wb).distance < 0.15 && segmentProjection(b, wa, wb).distance < 0.15;\n })\n )\n continue;\n // Door symbols standing ON the line and reaching BOTH ends of it. A swing sweeping past is\n // not a door in the gap, and a symbol crowding one jamb is the door in the wall next to it.\n const ts = doorPts.filter(q => segmentProjection(q, a, b).distance < 0.4).map(q => segmentProjection(q, a, b).t);\n if (ts.length < 6) continue;\n if (!ts.some(t => t < 0.2) || !ts.some(t => t > 0.8)) continue;\n candidates.push({ from, to, span, score: ts.length / span });\n }\n\n // Strongest claim first. A junction takes part in one bridge and no more: a gap is one doorway,\n // and without this the same gap is bridged twice — once from each end — and a corner sprouts a\n // second doorway running off at another angle.\n const claimed = new Set<string>();\n let regions = before;\n // Shortest first. Where two lines from one loose end both carry door symbols and both close a\n // room, the doorway is the narrower: a door is the smallest gap that separates two places, and\n // the longer line is the same doorway plus a diagonal across the room behind it.\n for (const c of candidates.sort((x, y) => x.span - y.span || y.score - x.score)) {\n if (claimed.has(c.from) || claimed.has(c.to) || (uses.get(c.from) ?? 0) !== 1) continue;\n const a = at(c.from),\n b = at(c.to);\n const neighbours = mine.filter(w => [c.from, c.to].includes(w.startId) || [c.from, c.to].includes(w.endId));\n const thickness = Math.max(0.05, Math.min(...neighbours.map(w => w.thickness), 0.2));\n // The test that matters: does this actually close a room? A doorway is worth drawing where it\n // separates two places and nowhere else, and a line that leaves the plan exactly as open as it\n // was is a coincidence among the door symbols, not a wall. Probed on a copy, so a candidate that\n // fails leaves nothing behind.\n const probe: ProjectDocument = {\n ...draft,\n junctions: [...draft.junctions, { id: 'probe-a', position: a, floorId }, { id: 'probe-b', position: b, floorId }],\n barriers: [\n ...draft.barriers,\n { ...mine[0], id: 'probe', startId: 'probe-a', endId: 'probe-b', floorId, thickness },\n ],\n };\n const after = enclosedRegions(probe, floorId).length;\n // Re-measured each time, not against the count this pass started with: a second bridge judged\n // against a stale number is judged against a plan that no longer exists.\n if (after <= regions) continue;\n regions = after;\n claimed.add(c.from);\n claimed.add(c.to);\n uses.set(c.from, 2);\n addBarrier(draft, a, b, floorId, 'wall');\n const wall = draft.barriers.at(-1)!;\n wall.thickness = thickness;\n wall.name = 'Partition';\n const storey = draft.floors.find(f => f.id === floorId)?.height;\n if (storey) wall.height = storey;\n report.walls++;\n // The doorway fills the span but for a jamb at each end — a door flush to a wall end is one the\n // document will not hold.\n const clear = distance(a, b) - 2 * (thickness + 0.02);\n // Detection heuristic, not a schema limit: do not infer a doorway from a tiny gap.\n if (clear < 0.5) {\n report.skipped.push(`bridged doorway too narrow to hold a door at ${a.map(v => v.toFixed(1))}`);\n continue;\n }\n const door = createObject('door', [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2], floorId, 'Door');\n door.width = Math.round(clear * 100) / 100;\n door.barrierId = wall.id;\n door.offset = Math.round((distance(a, b) / 2) * 1000) / 1000;\n draft.objects.push(door);\n report.doors++;\n }\n}\n\n/** Each label christens the room it stands in. Pure numbers are the area figures — ignored. */\nfunction nameRooms(\n draft: ProjectDocument,\n floorId: string | null,\n regional: PlanEntity[],\n layers: NonNullable<PlanImportOptions['layers']>,\n report: PlanImportReport,\n) {\n for (const e of regional) {\n if (e.type !== 'TEXT' || !layers.labels.test(e.layer) || !e.at || !e.text) continue;\n if (/^[\\d.,\\s]+$/.test(e.text)) continue;\n const room = spaceAt(draft, floorId, e.at);\n if (!room || room.floorId !== floorId) {\n report.skipped.push(`label \"${e.text}\" stands in no room`);\n continue;\n }\n room.name = e.text.trim();\n report.named++;\n }\n}\n\nexport { VERTEX_LAYERS } from './types';\nexport type { PlanEntity, PlanImportOptions, PlanImportReport, PlanLayerMap } from './types';\n"],"names":["importPlanEntities","draft","entities","options","layers","VERTEX_LAYERS","floorId","report","regional","planRegion","envelope","partitions","reconstructWalls","doorPts","symbolPoints","windowPts","symbolVertices","openings","w","resolveEnvelopeOpenings","passages","resolved","gap","resolvePartitionGap","r","emitBarriers","bridgeDoorways","emitStairs","emitRooms","emitOpenings","nameRooms","refreshPortals","interiorFace","inner","faceSegments","rx","s","ry","inRegion","p","e","_a","envelopePool","envelopeRuns","mergeRuns","pairWalls","closeCorners","faceGapRuns","run","addGap","partitionRuns","MIN_SEGMENT","along","v","snapEnds","lo","hi","barrierSpans","addBarrier","barrier","storey","f","dropStubs","at","j","stubs","b","q","used","boundaryEdges","normalizeBoundaries","regions","boundaryRegions","region","room","createObject","centroid","bindSpaceToRegion","error","o","label","openingLabel","hit","a","z","barrierEnds","axis","i","OPENING_MIN_SEGMENT","x","opening","treads","flights","near","g","distance","group","pts","xs","ys","width","depth","centre","stair","alongY","mine","uses","id","loose","n","before","enclosedRegions","candidates","from","to","span","angle","wa","wb","segmentProjection","ts","t","claimed","c","y","neighbours","thickness","probe","after","wall","clear","door","spaceAt"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import "polygon-clipping";
|
|
2
|
+
import { distance as u } from "../model/geometry.js";
|
|
3
|
+
import { VERTEX_LAYERS as g } from "./types.js";
|
|
4
|
+
function b(m, c, r) {
|
|
5
|
+
const o = (t) => t && [t[0] + c, t[1] + r];
|
|
6
|
+
return m.map((t) => {
|
|
7
|
+
var s;
|
|
8
|
+
return {
|
|
9
|
+
...t,
|
|
10
|
+
a: o(t.a),
|
|
11
|
+
b: o(t.b),
|
|
12
|
+
at: o(t.at),
|
|
13
|
+
center: o(t.center),
|
|
14
|
+
points: (s = t.points) == null ? void 0 : s.map((i) => [i[0] + c, i[1] + r])
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function l(m, c) {
|
|
19
|
+
const r = [];
|
|
20
|
+
for (const t of m)
|
|
21
|
+
if (c.test(t.layer) && (t.a && t.b && r.push([t.a, t.b]), t.points))
|
|
22
|
+
for (let s = 1; s < t.points.length; s++) r.push([t.points[s - 1], t.points[s]]);
|
|
23
|
+
const o = [];
|
|
24
|
+
for (const t of r) {
|
|
25
|
+
const s = o.find((i) => i.some(([h, f]) => t.some((a) => u(a, h) < 1.5 || u(a, f) < 1.5)));
|
|
26
|
+
s ? s.push(t) : o.push([t]);
|
|
27
|
+
}
|
|
28
|
+
return o.map((t) => {
|
|
29
|
+
const s = t.flat(), i = s.map((p) => p[0]), h = s.map((p) => p[1]), f = Math.min(...i), a = Math.max(...i), n = Math.min(...h), e = Math.max(...h);
|
|
30
|
+
return { centre: [(f + a) / 2, (n + e) / 2], width: a - f, height: e - n };
|
|
31
|
+
}).filter((t) => t.width > 0.5 && t.height > 0.5);
|
|
32
|
+
}
|
|
33
|
+
function w(m, c, r = g) {
|
|
34
|
+
if (!r.stairs) return null;
|
|
35
|
+
const o = l(m, r.stairs), t = l(c, r.stairs);
|
|
36
|
+
if (!o.length || !t.length) return null;
|
|
37
|
+
const s = (n, e) => Math.abs(n.width - e.width) < 0.06 && Math.abs(n.height - e.height) < 0.06, i = o.flatMap((n) => t.filter((e) => s(n, e)).map((e) => [n, e]));
|
|
38
|
+
if (!i.length) return null;
|
|
39
|
+
const h = i.map(([n, e]) => [n.centre[0] - e.centre[0], n.centre[1] - e.centre[1]]), f = h.map((n) => ({ o: n, n: h.filter((e) => u(e, n) <= 0.1).length })), a = f.reduce((n, e) => e.n > n.n ? e : n);
|
|
40
|
+
return f.some((n) => n.n === a.n && u(n.o, a.o) > 0.1) ? null : [Math.round(a.o[0] * 1e3) / 1e3, Math.round(a.o[1] * 1e3) / 1e3];
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
l as landmarks,
|
|
44
|
+
w as sheetOffset,
|
|
45
|
+
b as shiftEntities
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sources":["../../../../src/import/register.ts"],"sourcesContent":["// Putting several sheets of the same building into one frame.\n//\n// Floor plans are drawn one storey per sheet, and nothing says the sheets share an origin — they\n// rarely do. Imported as they come, the storeys of a building land metres apart, and the only fix\n// has been for a person to find something drawn on two sheets, measure the offset between them by\n// hand, and type the numbers into an import script. Those numbers are the most fragile thing in such\n// a script: they are correct for exactly one set of files, they cannot be checked by reading them,\n// and a redrawn sheet silently invalidates them.\n//\n// The building can say it instead. A stair core is drawn on every storey it passes, in the same place\n// on each, because that is what a stair is — so the offset between two sheets is the offset between\n// the stair they share.\nimport { distance } from '../schema';\nimport type { Point } from '../schema';\nimport type { PlanEntity } from './types';\nimport { VERTEX_LAYERS, type PlanLayerMap } from './types';\n\n/** Every entity moved by (dx, dy). Sheets are registered before import, not after: the reconstruction\n * snaps and welds as it goes, and moving the result would mean re-deciding all of it. */\nexport function shiftEntities(entities: PlanEntity[], dx: number, dy: number): PlanEntity[] {\n const move = (p: Point | undefined) => (p ? ([p[0] + dx, p[1] + dy] as Point) : p);\n return entities.map(e => ({\n ...e,\n a: move(e.a),\n b: move(e.b),\n at: move(e.at),\n center: move(e.center),\n points: e.points?.map(p => [p[0] + dx, p[1] + dy] as Point),\n }));\n}\n\nexport interface Landmark {\n centre: Point;\n width: number;\n height: number;\n}\n\n/** The blocks a layer's geometry falls into — a stair core on a plan, and nothing else on that layer.\n * Single-link clustering: strokes of one stair touch, and two cores are metres apart. */\nexport function landmarks(entities: PlanEntity[], layer: RegExp): Landmark[] {\n const segments: [Point, Point][] = [];\n for (const e of entities) {\n if (!layer.test(e.layer)) continue;\n if (e.a && e.b) segments.push([e.a, e.b]);\n if (e.points) for (let i = 1; i < e.points.length; i++) segments.push([e.points[i - 1], e.points[i]]);\n }\n const groups: [Point, Point][][] = [];\n for (const seg of segments) {\n const near = groups.find(g => g.some(([a, b]) => seg.some(p => distance(p, a) < 1.5 || distance(p, b) < 1.5)));\n if (near) near.push(seg);\n else groups.push([seg]);\n }\n return groups\n .map(g => {\n const pts = g.flat();\n const xs = pts.map(p => p[0]),\n ys = pts.map(p => p[1]);\n const x0 = Math.min(...xs),\n x1 = Math.max(...xs),\n y0 = Math.min(...ys),\n y1 = Math.max(...ys);\n return { centre: [(x0 + x1) / 2, (y0 + y1) / 2] as Point, width: x1 - x0, height: y1 - y0 };\n })\n .filter(l => l.width > 0.5 && l.height > 0.5);\n}\n\n/** The shift that puts `sheet` into `reference`'s frame, or null when they share no landmark.\n *\n * Matching is by SIZE: a stair core is a particular 1.87 x 2.05 m block and the one on the storey\n * above is the same block, while a second core at the other end of the building is a different one.\n * Two sheets whose stairs are the same size but in genuinely different places would register\n * wrongly, so the answer is only offered when exactly one pairing is plausible — a registration\n * that might be wrong is worse than none, because nothing downstream can tell.\n *\n * Returns null for a sheet with no stair drawn on it, which a cellar entered from outside will not\n * have. There is nothing to match there and the caller must say where that storey goes. */\nexport function sheetOffset(\n reference: PlanEntity[],\n sheet: PlanEntity[],\n layers: PlanLayerMap = VERTEX_LAYERS,\n): Point | null {\n if (!layers.stairs) return null;\n const here = landmarks(reference, layers.stairs),\n there = landmarks(sheet, layers.stairs);\n if (!here.length || !there.length) return null;\n // 60 mm: two drawings of one stair differ by rounding, not by a step's worth.\n const same = (a: Landmark, b: Landmark) => Math.abs(a.width - b.width) < 0.06 && Math.abs(a.height - b.height) < 0.06;\n const pairs = here.flatMap(a => there.filter(b => same(a, b)).map(b => [a, b] as const));\n if (!pairs.length) return null;\n const offsets = pairs.map(([a, b]) => [a.centre[0] - b.centre[0], a.centre[1] - b.centre[1]] as Point);\n // Every core is tried against every other, so a building with two cores produces the right shift\n // twice — once per core, both agreeing — and the two cross-pairings once each, disagreeing. The\n // shift the most pairings vote for is the one where the whole building lines up.\n const votes = offsets.map(o => ({ o, n: offsets.filter(x => distance(x, o) <= 0.1).length }));\n const best = votes.reduce((a, b) => (b.n > a.n ? b : a));\n // A clear winner or nothing. Cores of the same size in genuinely different places give every\n // pairing one vote, and a registration that might be wrong is worse than none: nothing downstream\n // can tell the difference, and the storey lands metres out with no sign that it did.\n if (votes.some(v => v.n === best.n && distance(v.o, best.o) > 0.1)) return null;\n return [Math.round(best.o[0] * 1000) / 1000, Math.round(best.o[1] * 1000) / 1000];\n}\n"],"names":["shiftEntities","entities","dx","dy","move","p","e","_a","landmarks","layer","segments","i","groups","seg","near","g","a","b","distance","pts","xs","ys","x0","x1","y0","y1","l","sheetOffset","reference","sheet","layers","VERTEX_LAYERS","here","there","same","pairs","offsets","votes","o","x","best","v"],"mappings":"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"}
|