@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 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../../../src/model/types.ts"],"sourcesContent":["import type { ExteriorPreset } from './materials';\n\nexport type Point = [number, number];\n/** Geographic anchor of the local tangent frame — [lng, lat] in WGS84 degrees; an optional third\n * element rotates the site clockwise from true north (degrees). */\nexport type Origin = [number, number] | [number, number, number];\nexport type Ring = Point[];\n/** The built-in taxonomy of drawable things, spanning the toolkit's use-cases (spaces, openings,\n * devices, logistics, safety). It is intentionally broad rather than tied to one domain; model\n * host-specific subtypes (e.g. a turnstile variety, a sensor class) as `category`/`metadata` on the\n * object rather than by extending this union. Runtime tuple so the type and the document validator\n * share one source of truth. */\nexport const OBJECT_KINDS = [\n 'zone',\n 'room',\n 'parcel',\n 'building',\n 'office',\n 'container',\n 'storage',\n 'door',\n 'window',\n 'gate',\n 'turnstile',\n 'reader',\n 'camera',\n 'elevator',\n 'stairs',\n 'poi',\n 'fixture',\n 'light',\n 'landscape',\n 'sensor',\n 'alarm',\n 'equipment',\n 'evacuation',\n] as const;\nexport type ObjectKind = (typeof OBJECT_KINDS)[number];\nexport type MaterialKind =\n | 'brick'\n | 'stone'\n | 'plaster'\n | 'timber'\n | 'oak'\n | 'tile'\n | 'terrazzo'\n | 'grass'\n | 'paving'\n | 'carpet'\n | 'wallpaper';\nexport type ModelKind =\n | 'bed'\n | 'sofa'\n | 'dining'\n | 'cabinet'\n | 'counter'\n | 'toilet'\n | 'basin'\n | 'shower'\n | 'sauna'\n | 'tree'\n | 'shrub'\n | 'lamp'\n | 'car'\n | 'chimney'\n | 'vent'\n | 'post';\nexport interface RoofSection {\n footprint: Ring;\n ridge: [Point, Point];\n eave: number;\n peak: number;\n}\nexport interface BuildingRoof {\n floorId: string;\n color: string;\n sections: RoofSection[];\n}\n/** mezzanine marks an intermediate (entresol) level: the plan draws it in context of the levels around it. */\nexport interface Floor {\n id: string;\n buildingId: string;\n name: string;\n elevation: number;\n height: number;\n mezzanine?: boolean;\n code?: string;\n /** The artificial light this level burns — colour temperature and how brightly. Absent means a\n * fluorescent ceiling at working brightness, which is what is actually overhead in most of the\n * buildings anyone draws. A building's own lighting does not follow the sun, so this is what\n * keeps a storey legible in a plan opened after dark. */\n light?: InteriorLight;\n /** What this level sounds like from inside it — the default for every space on it that does not\n * say otherwise. Absent is silence. */\n ambience?: Ambience;\n}\n/** The sound of a space, heard at eye level: a named soundscape or musical preset,\n * and how loud, 0 to 1 (absent is 1). Kept in\n * the document rather than the renderer for the same reason light is: what a room sounds like\n * describes the room. */\nexport interface Ambience {\n preset: AmbiencePreset;\n level?: number;\n}\nexport type AmbiencePreset = 'silent' | 'office' | 'backrooms' | 'plant' | 'baths' | 'elevator';\nexport const AMBIENCE_PRESETS: AmbiencePreset[] = ['silent', 'office', 'backrooms', 'plant', 'baths', 'elevator'];\n/** Colour temperature in kelvin (2700 tungsten, 4000 fluorescent, 6500 daylight) and how brightly,\n * 0 to 1. Kept here rather than in the renderer: what a space is lit by describes the space. */\nexport interface InteriorLight {\n kelvin: number;\n level: number;\n /** Optional colour cast from the fittings or reflected surroundings. */\n tint?: string;\n}\n/** A ceiling luminaire. Object height is the mounting height above the floor, intensity is candela,\n * range is metres, and flicker is the optional 0..1 depth of intermittent ballast dimming. */\nexport interface LightFixture {\n /** Optional mounting elevation above the floor; negative values place a lamp inside a pool. */\n mountHeight?: number;\n kelvin: number;\n intensity: number;\n range: number;\n flicker?: number;\n}\nexport interface Building {\n id: string;\n name: string;\n roof?: BuildingRoof;\n /** Provenance id of the basemap footprint a building was adopted from — the value of the host's\n * configured footprints.idField (a string or number, depending on the tile source); absent for\n * hand-drawn buildings. */\n sourceId?: string | number;\n exteriorPreset?: ExteriorPreset;\n}\nexport interface Junction {\n id: string;\n floorId: string | null;\n position: Point;\n}\n/** An unwalled space boundary. Physical boundaries use Barrier IDs and endpoints directly. */\nexport interface VirtualBoundary {\n id: string;\n floorId: string | null;\n startId: string;\n endId: string;\n}\nexport interface BoundaryUse {\n edgeId: string;\n /** Traverse from endId to startId. Absent means startId to endId. */\n reversed?: boolean;\n}\nexport type SpaceGeometry = { mode: 'independent' } | { mode: 'boundaries'; loops: BoundaryUse[][] };\nexport interface Barrier {\n id: string;\n floorId: string | null;\n startId: string;\n endId: string;\n kind: 'wall' | 'fence';\n thickness: number;\n height: number;\n name: string;\n color?: string;\n material?: MaterialKind;\n category?: string;\n metadata?: Record<string, unknown>;\n}\n/** A sloped area: the footprint falls linearly from `high` at `axis[0]` to `low` at `axis[1]`, and\n * stays flat beyond either end — so a footprint longer than its axis keeps level aprons where it\n * meets the decks it joins. Elevations are absolute metres in the same frame as Floor.elevation, so a\n * ramp can span floors (a garage ramp, a loading incline, a sloped plaza). Any area object may carry\n * one; without it an area is flat. The axis always runs downhill: `high` >= `low`. */\nexport interface Slope {\n axis: [Point, Point];\n high: number;\n low: number;\n}\nexport interface SiteObject {\n id: string;\n kind: ObjectKind;\n floorId: string | null;\n name: string;\n position: Point;\n rotation: number;\n rings?: Ring[];\n /** Boundary-driven spaces keep rings and dimensions as a validated, generated cache.\n * Absent is an independent outline, preserving existing/imported projects without guessing. */\n geometry?: SpaceGeometry;\n /** Makes this area a sloped plane rather than a flat plate; see Slope. */\n slope?: Slope;\n /** Clear ceiling height above this floor's datum; omitted uses the storey height.\n * A room spanning storeys uses this with holes in the plates above it. */\n ceilingHeight?: number;\n /** A basin below this area's walking surface, filled with clear water. */\n water?: { depth: number; ripple?: number };\n /** Open water-slide centreline, in metres relative to the object's position and rotation.\n * The third coordinate is height above its floor. */\n slide?: { path: [number, number, number][]; radius: number };\n /** Bottom of a raised fixture, metres above the floor surface (e.g. a doorway lintel). */\n baseHeight?: number;\n width: number;\n depth: number;\n height: number;\n parentId?: string;\n barrierId?: string;\n offset?: number;\n /** Door hinge at the wall start (left, default) or end (right). */\n doorHinge?: 'left' | 'right';\n /** Door mechanism. Omitted means a conventional single hinged leaf. */\n doorType?: 'hinged' | 'sliding' | 'double';\n /** Door opens to the left (+1, default) or right (-1) of the directed wall. */\n doorSwing?: 1 | -1;\n feedId?: string;\n symbol?:\n | 'personnel'\n | 'service'\n | 'driveway'\n | 'parking'\n | 'assembly'\n | 'info'\n | 'aed'\n | 'extinguisher'\n | 'firstaid'\n | 'exit'\n | 'firealarm';\n servedFloorIds?: string[];\n /** Stairs only: which geometry this climb has.\n *\n * `straight` is one run. `switchback` turns back on itself around a half-landing — the half-turn\n * stair that fits a storey into a short core box, and what most building stairs actually are.\n * `dogleg` turns a quarter at its landing, for a stair in the corner of a plan. `spiral` winds\n * around a pole and can land on every level it passes. `escalator` is a machine: a fixed 30° pitch\n * and a flat comb plate at each end.\n *\n * All of them connect the same floors and route identically, which is why this is one field rather\n * than five kinds — the difference is what it looks like and how much run it needs. Absent picks\n * the shape that fits: a straight run where the footprint allows a civil pitch, a switchback where\n * it does not, since a plan that gives a 4.5 m box to a 4.4 m storey means a stair that turns, not\n * a ladder. */\n stairModel?: 'straight' | 'switchback' | 'dogleg' | 'spiral' | 'escalator';\n /** Stairs with the same group on a level share one rectangular floor/ceiling opening. */\n wellGroup?: string;\n /** Escalators only: which way the machine carries you.\n *\n * It is a property of the machine, not of the drawing and not of its name: an escalator runs one\n * way, a bank of them runs alternate ways, and which way this one runs is the first thing anyone\n * asks of it. The same vocabulary as `Zone.connects`, so the described layer and the object never\n * need translating between them. Absent means up, which is what a lone escalator nearly always\n * does. A stair that is not an escalator ignores it — a stair carries you both ways.\n *\n * An imported escalator arrives once per storey it is drawn on; the twin standing lowest is the\n * one the model draws and routes from, so that is the one to author. */\n travel?: 'up' | 'down';\n /** Elevators only: which faces of the car open, relative to the object's own rotation. A through-car\n * opens front and back; a corner lobby car opens front and one side. Absent means the front alone,\n * which is what almost every lift does. Which FLOORS it opens on is `servedFloorIds` — a level the\n * shaft passes without serving has no doors on any side. */\n doorSides?: ('front' | 'back' | 'left' | 'right')[];\n coverageAngle?: number;\n coverageRange?: number;\n color?: string;\n /** Camera: ids of the doors, rooms and elevators this camera observes (event footage lookup). */\n watchedIds?: string[];\n material?: MaterialKind;\n model?: ModelKind;\n light?: LightFixture;\n /** A space's own sound, overriding its floor's. See Floor.ambience. */\n ambience?: Ambience;\n /** Host-defined subtype/category (e.g. 'meeting-room', 'speedgate', 'cold-store') for styling, filtering and event handlers. */\n category?: string;\n /** Arbitrary host metadata; ignored by the core, available to style hooks and event handlers. */\n metadata?: Record<string, unknown>;\n}\nexport interface Drawing {\n id: string;\n floorId: string | null;\n assetId: string;\n name: string;\n width: number;\n height: number;\n origin: Point;\n scale: number;\n rotation: number;\n opacity: number;\n visible: boolean;\n locked: boolean;\n}\n/** A named set of spaces — the area objects listed in `spaceIds`.\n *\n * A zone is a *membership*, not a shape: it has no geometry of its own, which is exactly what lets it\n * span floors, skip buildings, and overlap other zones freely. (Contrast `SiteObject.parentId`, which\n * is the strict single-parent, same-floor, geometrically-contained tree.) A lift shaft is a zone of\n * its per-floor lift spaces; a security area is a zone of the rooms inside it. */\nexport interface Zone {\n id: string;\n name: string;\n spaceIds: string[];\n /** Zones may contain zones: a DAG, not a tree. Cycles are rejected; several parents are fine.\n * Membership does NOT imply access inheritance — that is policy, and policy is the host's. */\n childZoneIds?: string[];\n /** How the spaces within this zone reach each other, when they are not joined by portals.\n * 'all' — every space mutually reachable: a lift serving all of its landings.\n * 'adjacent' — reachable in elevation order, both ways: a stair, passing each level in turn.\n * 'up'/'down'— adjacent but one-way: an escalator, which carries you in a single direction.\n * omitted — no implied connectivity; the zone is a grouping only. */\n connects?: 'all' | 'adjacent' | 'up' | 'down';\n /** Host vocabulary ('security', 'evacuation', 'hvac', …). The core never interprets it. */\n purpose?: string;\n metadata?: Record<string, unknown>;\n}\n/** A traversable connection between exactly two spaces.\n *\n * Direction is deliberately not baked in: a door's entry and exit sides are relative to whichever\n * area you are asking about, so the two sides are stored symmetrically and direction is derived\n * (see `entryInto`). Applications that attach a rule per direction of travel split a two-way door into\n * two objects; that is the right shape for an application layer and the wrong shape for a topology one. */\nexport interface Portal {\n id: string;\n /** The opening object (door/gate/turnstile) crossed here; absent for a virtual portal — an open\n * boundary between two spaces with no physical separator, which malls and open-plan floors need.\n * Kept as a reference rather than folded in, so two portals can be known to share one door leaf. */\n openingId?: string;\n /** The two sides. Order is storage, not meaning — ask `entryInto` rather than reading it. */\n a: string;\n b: string;\n /** Which way passage is possible at all. Default 'both'. */\n passage?: 'both' | 'a-to-b' | 'b-to-a' | 'none';\n /** How far a crossing here can be trusted to have actually happened.\n * 'confirmed' — a sensor saw it (a door monitor opening and closing).\n * 'assumed' — inferred from a grant; nobody watched. The default for a plain door.\n * 'none' — the crossing is a *request*, not an event: a lift floor button, where the\n * passenger may go anywhere. Anything counting crossings must ignore these. */\n attests?: 'confirmed' | 'assumed' | 'none';\n name?: string;\n metadata?: Record<string, unknown>;\n}\n/** An explicit set of portals, for sets that are not a zone boundary — \"all loading-bay shutters\",\n * \"every door with a reader\". Where a set *is* a zone boundary, derive it instead: see `perimeter`. */\nexport interface PortalGroup {\n id: string;\n name: string;\n portalIds: string[];\n metadata?: Record<string, unknown>;\n}\n/** Authored route-graph node for indoor navigation. objectId binds it to a SiteObject (door, elevator, stairs, room, poi) so routing can anchor at that object and instructions can name it. */\nexport interface NavNode {\n id: string;\n floorId: string | null;\n position: Point;\n objectId?: string;\n}\nexport type NavEdgeKind = 'walk' | 'door' | 'stairs' | 'elevator';\n/** Undirected route-graph edge. walk stays on one floor; door stays on one floor or steps outside (one end floorId null); stairs/elevator join different floors and bind the serving object via objectId. weight overrides the derived metres-equivalent cost. */\nexport interface NavEdge {\n id: string;\n kind: NavEdgeKind;\n aId: string;\n bId: string;\n /** Passable from `aId` to `bId` only. Escalators and one-way doors; absent means both ways. */\n directed?: boolean;\n objectId?: string;\n weight?: number;\n}\nexport interface ProjectDocument {\n schemaVersion: 1;\n id: string;\n name: string;\n description: string;\n updatedAt: string;\n origin: Origin;\n datum: string;\n buildings: Building[];\n floors: Floor[];\n junctions: Junction[];\n barriers: Barrier[];\n virtualBoundaries?: VirtualBoundary[];\n objects: SiteObject[];\n drawings: Drawing[];\n referenceNote?: string;\n /** The floor a viewer should open on — the document's own front door, so a project that is really\n * about its offices does not always land on the street. `null` means the outdoor site; omit it\n * entirely to let the viewer pick (ground floor, else the lowest). Hosts can still override per\n * session via ViewState.floor / FloorViewer's `floorId`. */\n initialFloorId?: string | null;\n /** The spatial ontology layered over the geometry: which spaces group together, and what connects\n * to what. Spaces themselves are the ringed area objects in `objects` — nothing is duplicated. */\n zones?: Zone[];\n portals?: Portal[];\n portalGroups?: PortalGroup[];\n /** Optional authored navigation graph. When absent, the graph is derived from spaces and portals\n * (see model/topology.ts) — the plan is then the single source of truth and cannot drift out of\n * step with a hand-kept copy of itself. */\n navNodes?: NavNode[];\n navEdges?: NavEdge[];\n}\nexport interface ProjectSummary {\n id: string;\n name: string;\n updatedAt: string;\n}\nexport interface ProjectRepository {\n list(): Promise<ProjectSummary[]>;\n load(id: string): Promise<ProjectDocument | null>;\n save(project: ProjectDocument): Promise<void>;\n delete(id: string): Promise<void>;\n}\nexport interface AssetRepository {\n get(id: string): Promise<Blob | undefined>;\n put(id: string, asset: Blob): Promise<void>;\n delete(id: string): Promise<void>;\n}\nexport type Tool =\n | 'select'\n | 'pan'\n | 'wall'\n | 'boundary'\n | 'partition'\n | 'fence'\n | 'zone'\n | 'room'\n | 'rectangle'\n | 'enclose'\n | 'hole'\n | 'door'\n | 'window'\n | 'gate'\n | 'turnstile'\n | 'reader'\n | 'camera'\n | 'elevator'\n | 'stairs'\n | 'office'\n | 'container'\n | 'storage'\n | 'poi'\n | 'measure'\n | 'adopt'\n | 'split'\n | 'route'\n | 'sensor'\n | 'light'\n | 'alarm'\n | 'equipment'\n | 'evacuation';\n/** A fresh entity id. A bare UUIDv4 (no type prefix) so ids drop straight into a database `uuid`\n * column and validate there; the entity's type is already given by which collection it lives in. */\nexport const uid = () => crypto.randomUUID();\nexport const isArea = (kind: ObjectKind) =>\n ['zone', 'room', 'parcel', 'building', 'office', 'container', 'storage', 'evacuation'].includes(kind);\nexport const isDevice = (kind: ObjectKind) =>\n ['door', 'gate', 'turnstile', 'reader', 'camera', 'elevator', 'sensor', 'alarm', 'equipment'].includes(kind);\nexport const isOpening = (kind: ObjectKind) => kind === 'door' || kind === 'window' || kind === 'gate';\n/** A *space* is somewhere you can stand — the unit the ontology groups and connects. Areas qualify,\n * and so do lifts and stairs: the inside of a car is as much a place as the lobby it opens onto, and\n * the through-car case depends on being able to say so. */\nexport const isSpace = (kind: ObjectKind) => isArea(kind) || kind === 'elevator' || kind === 'stairs';\n"],"names":["OBJECT_KINDS","AMBIENCE_PRESETS","uid","isArea","kind","isDevice","isOpening","isSpace"],"mappings":"AAYO,MAAMA,IAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAqEaC,IAAqC,CAAC,UAAU,UAAU,aAAa,SAAS,SAAS,UAAU,GAoVnGC,IAAM,MAAM,OAAO,WAAA,GACnBC,IAAS,CAACC,MACrB,CAAC,QAAQ,QAAQ,UAAU,YAAY,UAAU,aAAa,WAAW,YAAY,EAAE,SAASA,CAAI,GACzFC,IAAW,CAACD,MACvB,CAAC,QAAQ,QAAQ,aAAa,UAAU,UAAU,YAAY,UAAU,SAAS,WAAW,EAAE,SAASA,CAAI,GAChGE,IAAY,CAACF,MAAqBA,MAAS,UAAUA,MAAS,YAAYA,MAAS,QAInFG,IAAU,CAACH,MAAqBD,EAAOC,CAAI,KAAKA,MAAS,cAAcA,MAAS;"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { synchronizeGeometry as G, validateSpaceBoundaries as P } from "./boundaries.js";
|
|
2
|
+
import { objectArea as y, distance as b, pointInRing as A, barrierEnds as $, MIN_SEGMENT as H, containedBy as C, OPENING_MIN_SEGMENT as N, openRing as j, ringArea as Z, intersects as R } from "./geometry.js";
|
|
3
|
+
import { navNodes as D, navEdges as J } from "./navigation.js";
|
|
4
|
+
import { EXTERIOR_PRESETS as K } from "./materials.js";
|
|
5
|
+
import { isArea as S, OBJECT_KINDS as L, isSpace as U, isOpening as V, AMBIENCE_PRESETS as W } from "./types.js";
|
|
6
|
+
import { MIN_SPACE_AREA as O, MIN_FACE_AREA as X } from "./precision.js";
|
|
7
|
+
const q = 1e5, a = (n) => typeof n == "number" && Number.isFinite(n), w = (n) => Array.isArray(n) && n.length === 2 && n.every((r) => a(r) && Math.abs(r) <= q), v = (n) => !!n && typeof n == "object" && !Array.isArray(n), h = (n) => typeof n == "string" && n.length > 0, I = (n) => new Set(n).size === n.length;
|
|
8
|
+
function Q(n, r = 1e-3, d = 0.01) {
|
|
9
|
+
if (!n.length) return "Draw an outer boundary first.";
|
|
10
|
+
for (const l of n) {
|
|
11
|
+
const i = j(l);
|
|
12
|
+
if (i.length < 3 || Z(i) < d) return "An area needs at least three vertices and a non-zero area.";
|
|
13
|
+
for (let o = 0; o < i.length; o++) {
|
|
14
|
+
if (b(i[o], i[(o + 1) % i.length]) < r) return "Remove duplicate vertices.";
|
|
15
|
+
for (let s = o + 1; s < i.length; s++)
|
|
16
|
+
if (!(s === o + 1 || o === 0 && s === i.length - 1) && R(i[o], i[(o + 1) % i.length], i[s], i[(s + 1) % i.length]))
|
|
17
|
+
return "Area boundaries cannot cross themselves.";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
for (let l = 1; l < n.length; l++) {
|
|
21
|
+
if (!n[l].every((i) => A(i, n[0]))) return "A hole must be completely inside the outer boundary.";
|
|
22
|
+
for (let i = 0; i < l; i++) {
|
|
23
|
+
const o = j(n[l]), s = j(n[i]);
|
|
24
|
+
for (let c = 0; c < o.length; c++)
|
|
25
|
+
for (let f = 0; f < s.length; f++)
|
|
26
|
+
if (R(o[c], o[(c + 1) % o.length], s[f], s[(f + 1) % s.length]))
|
|
27
|
+
return "Hole boundaries must not touch or overlap.";
|
|
28
|
+
if (i > 0 && (A(o[0], s) || A(s[0], o))) return "Holes cannot overlap.";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function Y(n) {
|
|
34
|
+
var i;
|
|
35
|
+
const r = new Map(n.objects.map((o) => [o.id, o])), d = new Map(n.barriers.map((o) => [o.id, o])), l = /* @__PURE__ */ new Map();
|
|
36
|
+
for (const o of n.objects) {
|
|
37
|
+
if (!o.barrierId) continue;
|
|
38
|
+
const s = l.get(o.barrierId) ?? [];
|
|
39
|
+
s.push(o), l.set(o.barrierId, s);
|
|
40
|
+
}
|
|
41
|
+
for (const o of n.barriers) {
|
|
42
|
+
const [s, c] = $(n, o);
|
|
43
|
+
if (b(s, c) < H - 1e-6) return `A wall or fence must be at least ${H} m long.`;
|
|
44
|
+
}
|
|
45
|
+
for (const o of n.objects) {
|
|
46
|
+
if (o.rings) {
|
|
47
|
+
const s = ((i = o.geometry) == null ? void 0 : i.mode) === "boundaries", c = Q(o.rings, s ? 1e-6 : 1e-3, s ? X : 0.01);
|
|
48
|
+
if (c) return c;
|
|
49
|
+
}
|
|
50
|
+
if (o.parentId) {
|
|
51
|
+
const s = r.get(o.parentId);
|
|
52
|
+
if (!(s != null && s.rings) || !o.rings || s.floorId !== o.floorId || !C(o.rings, s.rings))
|
|
53
|
+
return "A nested zone must remain inside its parent on the same floor.";
|
|
54
|
+
const c = /* @__PURE__ */ new Set([o.id]);
|
|
55
|
+
let f = s;
|
|
56
|
+
for (; f; ) {
|
|
57
|
+
if (c.has(f.id)) return "Zone parents cannot form a cycle.";
|
|
58
|
+
c.add(f.id), f = f.parentId ? r.get(f.parentId) : void 0;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (o.barrierId) {
|
|
62
|
+
if (!V(o.kind)) return "Only a door, window or gate can sit in a barrier.";
|
|
63
|
+
const s = d.get(o.barrierId);
|
|
64
|
+
if (!s || s.floorId !== o.floorId)
|
|
65
|
+
return "Opening references a missing barrier or a different floor.";
|
|
66
|
+
const [c, f] = $(n, s);
|
|
67
|
+
if (b(c, f) < N - 1e-6)
|
|
68
|
+
return `A door, window or gate needs a segment at least ${N} m long to sit in.`;
|
|
69
|
+
const u = o.offset ?? 0;
|
|
70
|
+
if (u - o.width / 2 < -1e-3 || u + o.width / 2 > b(c, f) + 1e-3)
|
|
71
|
+
return `The barrier is too short for its attached opening. Opening "${o.name || o.id}" (${o.id}) on barrier ${s.id}: width ${o.width.toFixed(2)} m, centre offset ${u.toFixed(2)} m, segment length ${b(c, f).toFixed(2)} m. The centre offset must be between ${(o.width / 2).toFixed(2)} and ${(b(c, f) - o.width / 2).toFixed(2)} m; if that interval is empty, choose a longer host segment.`;
|
|
72
|
+
if (l.get(s.id).some((p) => p.id !== o.id && Math.abs((p.offset ?? 0) - u) < (p.width + o.width) / 2 - 1e-3))
|
|
73
|
+
return "Attached openings cannot overlap.";
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function ee(n) {
|
|
79
|
+
const r = D(n), d = new Map(r.map((o) => [o.id, o])), l = new Map(n.objects.map((o) => [o.id, o])), i = new Set(n.floors.map((o) => o.id));
|
|
80
|
+
for (const o of r) {
|
|
81
|
+
if (!w(o.position)) return "A route node needs a valid position.";
|
|
82
|
+
if (o.floorId !== null && !i.has(o.floorId)) return "A route node references an unknown floor.";
|
|
83
|
+
if (o.objectId !== void 0 && !l.has(o.objectId)) return "A route node references a missing object.";
|
|
84
|
+
}
|
|
85
|
+
for (const o of J(n)) {
|
|
86
|
+
if (!["walk", "door", "stairs", "elevator"].includes(o.kind)) return "Unknown route edge kind.";
|
|
87
|
+
const s = d.get(o.aId), c = d.get(o.bId);
|
|
88
|
+
if (!s || !c || o.aId === o.bId) return "A route edge must join two different route nodes.";
|
|
89
|
+
if (o.weight !== void 0 && (typeof o.weight != "number" || !Number.isFinite(o.weight) || o.weight <= 0))
|
|
90
|
+
return "A route edge weight must be a positive number.";
|
|
91
|
+
const f = o.kind === "stairs" || o.kind === "elevator";
|
|
92
|
+
if (f && s.floorId === c.floorId) return "Stairs and elevator edges must join different floors.";
|
|
93
|
+
if (o.kind === "walk" && s.floorId !== c.floorId) return "Walk edges must stay on one floor.";
|
|
94
|
+
if (o.kind === "door" && s.floorId !== c.floorId && s.floorId !== null && c.floorId !== null)
|
|
95
|
+
return "Door edges must stay on one floor or step outside.";
|
|
96
|
+
if (o.objectId === void 0) continue;
|
|
97
|
+
const u = l.get(o.objectId);
|
|
98
|
+
if (!u) return "A route edge references a missing object.";
|
|
99
|
+
if (o.kind === "door" && !["door", "gate", "turnstile"].includes(u.kind))
|
|
100
|
+
return "A door edge must bind a door, gate or turnstile.";
|
|
101
|
+
if (f && u.kind !== o.kind) return "A vertical edge must bind a matching stairs or elevator object.";
|
|
102
|
+
if (f && u.servedFloorIds && [s, c].some((p) => p.floorId !== null && !u.servedFloorIds.includes(p.floorId)))
|
|
103
|
+
return "A vertical edge joins a floor its stairs or elevator does not serve.";
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
function oe(n) {
|
|
108
|
+
const r = (e) => {
|
|
109
|
+
throw new Error(`Invalid project: ${e}`);
|
|
110
|
+
};
|
|
111
|
+
(!v(n) || n.schemaVersion !== 1) && r(
|
|
112
|
+
"unsupported schema version (expected 1). The schema is still changing before release — re-create the project."
|
|
113
|
+
);
|
|
114
|
+
const d = n, l = (e) => Array.isArray(e) && (e.length === 2 || e.length === 3) && e.every(a) && Math.abs(e[0]) <= 180 && Math.abs(e[1]) <= 90;
|
|
115
|
+
(!h(d.id) || !h(d.name) || !l(d.origin) || typeof d.description != "string" || typeof d.datum != "string" || !h(d.updatedAt)) && r("missing project metadata.");
|
|
116
|
+
for (const e of ["buildings", "floors", "junctions", "barriers", "objects", "drawings"])
|
|
117
|
+
(!Array.isArray(d[e]) || d[e].some((t) => !v(t) || !h(t.id))) && r(`malformed ${e}.`);
|
|
118
|
+
for (const e of ["navNodes", "navEdges", "zones", "portals", "portalGroups", "virtualBoundaries"])
|
|
119
|
+
d[e] !== void 0 && (!Array.isArray(d[e]) || d[e].some((t) => !v(t) || !h(t.id))) && r(`malformed ${e}.`);
|
|
120
|
+
const i = d, o = [
|
|
121
|
+
...i.buildings,
|
|
122
|
+
...i.floors,
|
|
123
|
+
...i.junctions,
|
|
124
|
+
...i.barriers,
|
|
125
|
+
...i.virtualBoundaries ?? [],
|
|
126
|
+
...i.objects,
|
|
127
|
+
...i.drawings,
|
|
128
|
+
...i.navNodes ?? [],
|
|
129
|
+
...i.navEdges ?? [],
|
|
130
|
+
...i.zones ?? [],
|
|
131
|
+
...i.portals ?? [],
|
|
132
|
+
...i.portalGroups ?? []
|
|
133
|
+
];
|
|
134
|
+
new Set(o.map((e) => e.id)).size !== o.length && r("duplicate entity IDs.");
|
|
135
|
+
const s = new Set(i.floors.map((e) => e.id)), c = new Set(i.buildings.map((e) => e.id));
|
|
136
|
+
(!i.buildings.length || i.buildings.some(
|
|
137
|
+
(e) => !h(e.name) || e.sourceId !== void 0 && !h(e.sourceId) && !a(e.sourceId)
|
|
138
|
+
) || !i.floors.length || i.floors.some(
|
|
139
|
+
(e) => !c.has(e.buildingId) || !h(e.name) || !a(e.elevation) || !a(e.height) || e.height <= 0
|
|
140
|
+
)) && r("invalid buildings or floors."), i.objects.some((e) => e.travel !== void 0 && (e.kind !== "stairs" || e.travel !== "up" && e.travel !== "down")) && r("travel belongs to an escalator and must be up or down."), i.floors.some(
|
|
141
|
+
(e) => e.light !== void 0 && (!a(e.light.kelvin) || e.light.kelvin < 1e3 || e.light.kelvin > 12e3 || !a(e.light.level) || e.light.level < 0 || e.light.level > 1 || e.light.tint !== void 0 && (typeof e.light.tint != "string" || !/^#[0-9a-f]{6}$/i.test(e.light.tint)))
|
|
142
|
+
) && r("a floor names a light that is not a lamp.");
|
|
143
|
+
const f = (e) => e !== void 0 && (!W.includes(e.preset) || e.level !== void 0 && (!a(e.level) || e.level < 0 || e.level > 1));
|
|
144
|
+
(i.floors.some((e) => f(e.ambience)) || i.objects.some((e) => f(e.ambience))) && r("an ambience names a preset that does not exist, or a level outside 0..1."), i.initialFloorId !== void 0 && i.initialFloorId !== null && !i.floors.some((e) => e.id === i.initialFloorId) && r("initialFloorId names a floor that does not exist."), i.buildings.some((e) => e.exteriorPreset !== void 0 && !Object.hasOwn(K, e.exteriorPreset)) && r("unknown exterior preset.");
|
|
145
|
+
for (const e of [...i.junctions, ...i.barriers, ...i.objects, ...i.drawings])
|
|
146
|
+
e.floorId !== null && !s.has(e.floorId) && r("unknown floor reference.");
|
|
147
|
+
i.junctions.some((e) => !w(e.position)) && r("invalid junction coordinates.");
|
|
148
|
+
for (const e of i.virtualBoundaries ?? []) {
|
|
149
|
+
const t = i.junctions.find((m) => m.id === e.startId), g = i.junctions.find((m) => m.id === e.endId);
|
|
150
|
+
(!t || !g || t.floorId !== e.floorId || g.floorId !== e.floorId || b(t.position, g.position) < 1e-3 - 1e-6) && r("invalid virtual boundary.");
|
|
151
|
+
}
|
|
152
|
+
i.barriers.some(
|
|
153
|
+
(e) => !["wall", "fence"].includes(e.kind) || !h(e.name) || !i.junctions.some((t) => t.id === e.startId && t.floorId === e.floorId) || !i.junctions.some((t) => t.id === e.endId && t.floorId === e.floorId) || e.startId === e.endId || !a(e.height) || e.height <= 0 || !a(e.thickness) || e.thickness <= 0
|
|
154
|
+
) && r("invalid barrier.");
|
|
155
|
+
const u = /* @__PURE__ */ new Map();
|
|
156
|
+
for (const e of i.objects)
|
|
157
|
+
e.floorId && S(e.kind) && Array.isArray(e.rings) && Array.isArray(e.rings[0]) && u.set(e.floorId, [...u.get(e.floorId) ?? [], e]);
|
|
158
|
+
for (const e of i.objects) {
|
|
159
|
+
if ((!L.includes(e.kind) || !h(e.name) || !w(e.position) || !a(e.rotation) || !a(e.width) || e.width <= 0 || !a(e.depth) || e.depth <= 0 || !a(e.height) || e.height < 0) && r("invalid object dimensions or type."), e.rings && (!Array.isArray(e.rings) || e.rings.some((t) => !Array.isArray(t) || t.some((g) => !w(g)))) && r("invalid polygon coordinates."), e.servedFloorIds && (!Array.isArray(e.servedFloorIds) || e.servedFloorIds.some((t) => !s.has(t))) && r("unknown served floor."), e.servedFloorIds && !I(e.servedFloorIds) && r("served floors must be listed once each."), e.servedFloorIds && (e.kind === "stairs" || e.kind === "elevator")) {
|
|
160
|
+
const g = e.servedFloorIds.map((m) => u.get(m)).filter((m) => !!(m != null && m.length)).map((m) => m.some((_) => A(e.position, _.rings[0])));
|
|
161
|
+
g.some(Boolean) && !g.every(Boolean) && r("a stair or lift serves a level it does not stand on — its landing there opens onto nothing.");
|
|
162
|
+
}
|
|
163
|
+
if ((e.doorHinge !== void 0 || e.doorSwing !== void 0 || e.doorType !== void 0) && (e.kind !== "door" && r("only doors carry mechanism, hinge and swing settings."), e.doorType !== void 0 && !["hinged", "sliding", "double"].includes(e.doorType) && r("door type must be hinged, sliding or double."), e.doorHinge !== void 0 && !["left", "right"].includes(e.doorHinge) && r("door hinge must be left or right."), e.doorSwing !== void 0 && e.doorSwing !== 1 && e.doorSwing !== -1 && r("door swing must be 1 or -1.")), e.doorSides !== void 0 && (e.kind !== "elevator" && r("only an elevator lists door sides."), (!Array.isArray(e.doorSides) || !e.doorSides.length || e.doorSides.some((t) => !["front", "back", "left", "right"].includes(t)) || !I(e.doorSides)) && r("door sides are front, back, left or right, listed once each.")), e.wellGroup !== void 0 && (e.kind !== "stairs" || !h(e.wellGroup) || !e.wellGroup.trim()) && r("only stairs carry a non-empty shared well group."), e.stairModel !== void 0 && (e.kind !== "stairs" && r("only stairs carry a stair model."), ["straight", "switchback", "dogleg", "spiral", "escalator"].includes(e.stairModel) || r("a stair model is straight, switchback, dogleg, spiral or escalator.")), e.light !== void 0 || e.kind === "light") {
|
|
164
|
+
const t = e.light;
|
|
165
|
+
(e.kind !== "light" || !v(t) || t.mountHeight !== void 0 && (!a(t.mountHeight) || t.mountHeight < -20 || t.mountHeight > 100) || !a(t.kelvin) || t.kelvin < 1e3 || t.kelvin > 12e3 || !a(t.intensity) || t.intensity < 0 || t.intensity > 1e4 || !a(t.range) || t.range <= 0 || t.range > 100 || t.flicker !== void 0 && (!a(t.flicker) || t.flicker < 0 || t.flicker > 1)) && r("a light needs a valid colour temperature, intensity, range and optional flicker.");
|
|
166
|
+
}
|
|
167
|
+
if (e.watchedIds && (!Array.isArray(e.watchedIds) || e.watchedIds.some((t) => !i.objects.some((g) => g.id === t))) && r("unknown watched object."), e.watchedIds && !I(e.watchedIds) && r("watched objects must be listed once each."), e.ceilingHeight !== void 0 && (!["room", "zone"].includes(e.kind) || !a(e.ceilingHeight) || e.ceilingHeight <= 0) && r("a space ceiling height must be positive."), e.baseHeight !== void 0 && (e.kind !== "fixture" || !a(e.baseHeight) || e.baseHeight < 0 || e.baseHeight > 100) && r("a raised fixture needs a base height between 0 and 100 m."), e.water !== void 0) {
|
|
168
|
+
const t = e.water;
|
|
169
|
+
(!["room", "zone"].includes(e.kind) || !e.rings || !v(t) || !a(t.depth) || t.depth <= 0 || t.depth > 20 || t.ripple !== void 0 && (!a(t.ripple) || t.ripple < 0 || t.ripple > 0.1)) && r("water needs an area, a depth up to 20 m and ripples up to 10 cm.");
|
|
170
|
+
}
|
|
171
|
+
if (e.slide !== void 0) {
|
|
172
|
+
const t = e.slide;
|
|
173
|
+
(e.kind !== "fixture" || !v(t) || !a(t.radius) || t.radius <= 0 || !Array.isArray(t.path) || t.path.length < 2 || t.path.length > 100 || !t.path.every((g) => Array.isArray(g) && g.length === 3 && g.every(a) && g[2] >= 0)) && r("a water slide needs a positive radius and a finite path above its floor.");
|
|
174
|
+
}
|
|
175
|
+
if (e.slope !== void 0) {
|
|
176
|
+
const t = e.slope;
|
|
177
|
+
(!v(t) || !Array.isArray(t.axis) || t.axis.length !== 2 || !t.axis.every(w) || !a(t.high) || !a(t.low) || b(t.axis[0], t.axis[1]) < 0.01 || t.high < t.low) && r("invalid slope: needs a downhill two-point axis (high >= low) of non-zero length."), e.rings || r("a sloped object must be an area with a footprint.");
|
|
178
|
+
}
|
|
179
|
+
e.category !== void 0 && !h(e.category) && r("object category must be a string."), e.metadata !== void 0 && !v(e.metadata) && r("object metadata must be an object."), e.barrierId && !a(e.offset) && r("missing opening offset."), e.feedId !== void 0 && typeof e.feedId != "string" && r("invalid feed binding."), e.coverageRange !== void 0 && (!a(e.coverageRange) || e.coverageRange <= 0) && r("invalid camera range."), e.coverageAngle !== void 0 && (!a(e.coverageAngle) || e.coverageAngle <= 0 || e.coverageAngle > 360) && r("invalid camera angle.");
|
|
180
|
+
}
|
|
181
|
+
for (const e of i.drawings)
|
|
182
|
+
(!h(e.assetId) || !h(e.name) || !w(e.origin) || !a(e.scale) || e.scale <= 0 || !a(e.width) || e.width <= 0 || !a(e.height) || e.height <= 0 || !a(e.rotation) || !a(e.opacity) || e.opacity < 0 || e.opacity > 1 || typeof e.visible != "boolean" || typeof e.locked != "boolean") && r("invalid reference drawing.");
|
|
183
|
+
const p = new Set(i.objects.filter((e) => U(e.kind)).map((e) => e.id)), T = new Map(i.objects.map((e) => [e.id, e.kind])), z = new Set((i.zones ?? []).map((e) => e.id));
|
|
184
|
+
for (const e of i.zones ?? [])
|
|
185
|
+
(!h(e.name) || !Array.isArray(e.spaceIds)) && r("a zone needs a name and a list of spaces."), e.spaceIds.some((t) => !p.has(t)) && r("a zone lists a space that is not an area object."), I(e.spaceIds) || r("a zone must list each space once — membership is a set."), e.childZoneIds && e.childZoneIds.some((t) => !z.has(t)) && r("a zone nests a zone that does not exist."), e.childZoneIds && !I(e.childZoneIds) && r("a zone must nest each child once."), e.connects !== void 0 && !["all", "adjacent", "up", "down"].includes(e.connects) && r("a zone's connects must be 'all', 'adjacent', 'up' or 'down'.");
|
|
186
|
+
const k = /* @__PURE__ */ new Set(), E = /* @__PURE__ */ new Set(), F = (e) => {
|
|
187
|
+
var g;
|
|
188
|
+
if (E.has(e)) return !1;
|
|
189
|
+
if (k.has(e)) return !0;
|
|
190
|
+
k.add(e);
|
|
191
|
+
const t = (((g = (i.zones ?? []).find((m) => m.id === e)) == null ? void 0 : g.childZoneIds) ?? []).some(F);
|
|
192
|
+
return k.delete(e), E.add(e), t;
|
|
193
|
+
};
|
|
194
|
+
[...z].some(F) && r("zones cannot contain themselves.");
|
|
195
|
+
for (const e of i.portals ?? [])
|
|
196
|
+
(!p.has(e.a) || !p.has(e.b)) && r("a portal must join two areas that exist on the plan."), e.a === e.b && r("a portal must join two different spaces."), e.openingId && !["door", "gate", "turnstile"].includes(T.get(e.openingId) ?? "") && r("a portal's opening must be a door, gate or turnstile."), e.passage !== void 0 && !["both", "a-to-b", "b-to-a", "none"].includes(e.passage) && r("invalid portal passage direction."), e.attests !== void 0 && !["confirmed", "assumed", "none"].includes(e.attests) && r("invalid portal attestation.");
|
|
197
|
+
const B = new Set((i.portals ?? []).map((e) => e.id));
|
|
198
|
+
for (const e of i.portalGroups ?? [])
|
|
199
|
+
(!h(e.name) || !Array.isArray(e.portalIds) || e.portalIds.some((t) => !B.has(t))) && r("a portal group needs a name and must list portals that exist."), I(e.portalIds) || r("a portal group must list each portal once.");
|
|
200
|
+
const x = Y(i);
|
|
201
|
+
x && r(x), P(i);
|
|
202
|
+
const M = ee(i);
|
|
203
|
+
return M && r(M), i;
|
|
204
|
+
}
|
|
205
|
+
function ie(n) {
|
|
206
|
+
const r = (d) => {
|
|
207
|
+
if (d && typeof d == "object" && !Object.isFrozen(d)) {
|
|
208
|
+
Object.freeze(d);
|
|
209
|
+
for (const l of Object.values(d)) r(l);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
return r(n), n;
|
|
213
|
+
}
|
|
214
|
+
function le(n, r, d) {
|
|
215
|
+
try {
|
|
216
|
+
const l = structuredClone(n);
|
|
217
|
+
r(l), G(n, l);
|
|
218
|
+
for (const i of l.objects)
|
|
219
|
+
if (S(i.kind) && y(i) < O) {
|
|
220
|
+
const o = n.objects.find((s) => s.id === i.id && S(s.kind));
|
|
221
|
+
if (!o || y(o) !== y(i))
|
|
222
|
+
throw new Error(
|
|
223
|
+
`A space needs at least ${O} m² of usable area. "${i.name || i.id}" (${i.id}) has ${y(i).toFixed(3)} m².`
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
return oe(l), { ok: !0, project: (d == null ? void 0 : d.freeze) === !1 ? l : ie(l) };
|
|
227
|
+
} catch (l) {
|
|
228
|
+
return { ok: !1, error: l instanceof Error ? l.message : String(l) };
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
export {
|
|
232
|
+
q as COORD_LIMIT,
|
|
233
|
+
ie as freezeProject,
|
|
234
|
+
le as transact,
|
|
235
|
+
ee as validateNavigation,
|
|
236
|
+
oe as validateProject,
|
|
237
|
+
Y as validateRelationships,
|
|
238
|
+
Q as validateRings
|
|
239
|
+
};
|
|
240
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sources":["../../../../src/model/validate.ts"],"sourcesContent":["// The validity layer, in one place: every rule a Kerros document must satisfy — structural, ontological,\n// geometric, navigational — and the transaction gate that enforces them on every change.\n//\n// The contract the rest of the library leans on: a ProjectDocument in circulation is ALWAYS valid.\n// Loading validates (importProject, parseExport, the repositories), and editing validates through\n// `transact`, which works on a clone and refuses to return a document that fails any rule — so an\n// invalid state is never observable, only reported as the error that would have created it.\nimport { synchronizeGeometry, validateSpaceBoundaries } from './boundaries';\nimport {\n MIN_SEGMENT,\n OPENING_MIN_SEGMENT,\n barrierEnds,\n containedBy,\n distance,\n intersects,\n openRing,\n objectArea,\n pointInRing,\n ringArea,\n} from './geometry';\nimport { navEdges, navNodes } from './navigation';\nimport { EXTERIOR_PRESETS } from './materials';\nimport { AMBIENCE_PRESETS, OBJECT_KINDS, isArea, isOpening, isSpace } from './types';\nimport type { Ambience, Point, ProjectDocument, Ring, SiteObject } from './types';\nimport { MIN_FACE_AREA, MIN_SPACE_AREA } from './precision';\n\n/** How far from the site origin a coordinate may sit, in metres. A guard against corrupt data, not a\n * modelling limit: no site is 100 km across, but a NaN that became 1e15 through arithmetic would\n * otherwise sail through every finite() check and break projection math much later and much worse. */\nexport const COORD_LIMIT = 100_000;\n\nconst finite = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);\nconst point = (v: unknown) =>\n Array.isArray(v) && v.length === 2 && v.every(n => finite(n) && Math.abs(n) <= COORD_LIMIT);\nconst object = (v: unknown): v is Record<string, unknown> => !!v && typeof v === 'object' && !Array.isArray(v);\nconst string = (v: unknown): v is string => typeof v === 'string' && v.length > 0;\nconst distinct = (ids: readonly unknown[]) => new Set(ids).size === ids.length;\n\n/** Geometric sanity of an area's rings: a real outer boundary, no self-crossings, holes inside and\n * disjoint. Returns an error message or null. */\nexport function validateRings(rings: Ring[], minEdge = 0.001, minArea = 0.01): string | null {\n if (!rings.length) return 'Draw an outer boundary first.';\n for (const ring of rings) {\n const p = openRing(ring);\n if (p.length < 3 || ringArea(p) < minArea) return 'An area needs at least three vertices and a non-zero area.';\n for (let i = 0; i < p.length; i++) {\n if (distance(p[i], p[(i + 1) % p.length]) < minEdge) return 'Remove duplicate vertices.';\n for (let j = i + 1; j < p.length; j++) {\n if (j === i + 1 || (i === 0 && j === p.length - 1)) continue;\n if (intersects(p[i], p[(i + 1) % p.length], p[j], p[(j + 1) % p.length]))\n return 'Area boundaries cannot cross themselves.';\n }\n }\n }\n for (let i = 1; i < rings.length; i++) {\n if (!rings[i].every(p => pointInRing(p, rings[0]))) return 'A hole must be completely inside the outer boundary.';\n for (let j = 0; j < i; j++) {\n const a = openRing(rings[i]),\n b = openRing(rings[j]);\n for (let k = 0; k < a.length; k++)\n for (let l = 0; l < b.length; l++)\n if (intersects(a[k], a[(k + 1) % a.length], b[l], b[(l + 1) % b.length]))\n return 'Hole boundaries must not touch or overlap.';\n if (j > 0 && (pointInRing(a[0], b) || pointInRing(b[0], a))) return 'Holes cannot overlap.';\n }\n }\n return null;\n}\n\n/** Spatial relationships between entities: ring sanity per object, parent containment without\n * cycles, and openings that actually fit the barriers they sit in. */\nexport function validateRelationships(project: ProjectDocument): string | null {\n // A facade can have thousands of windows. Index attachments once per validation pass rather\n // than searching every object again for every opening; drafts may mutate between passes.\n const objects = new Map(project.objects.map(o => [o.id, o]));\n const barriers = new Map(project.barriers.map(b => [b.id, b]));\n const attachments = new Map<string, SiteObject[]>();\n for (const o of project.objects) {\n if (!o.barrierId) continue;\n const list = attachments.get(o.barrierId) ?? [];\n list.push(o);\n attachments.set(o.barrierId, list);\n }\n // Reject degenerate segments, while allowing real returns and jambs below the drawing-grid size.\n for (const barrier of project.barriers) {\n const [a, b] = barrierEnds(project, barrier);\n if (distance(a, b) < MIN_SEGMENT - 1e-6) return `A wall or fence must be at least ${MIN_SEGMENT} m long.`;\n }\n for (const object of project.objects) {\n if (object.rings) {\n const shared = object.geometry?.mode === 'boundaries';\n const error = validateRings(object.rings, shared ? 1e-6 : 0.001, shared ? MIN_FACE_AREA : 0.01);\n if (error) return error;\n }\n if (object.parentId) {\n const parent = objects.get(object.parentId);\n if (\n !parent?.rings ||\n !object.rings ||\n parent.floorId !== object.floorId ||\n !containedBy(object.rings, parent.rings)\n )\n return 'A nested zone must remain inside its parent on the same floor.';\n const seen = new Set([object.id]);\n let ancestor: SiteObject | undefined = parent;\n while (ancestor) {\n if (seen.has(ancestor.id)) return 'Zone parents cannot form a cycle.';\n seen.add(ancestor.id);\n ancestor = ancestor.parentId ? objects.get(ancestor.parentId) : undefined;\n }\n }\n if (object.barrierId) {\n // Only an opening belongs in a barrier: a camera \"set into\" a wall is a placement bug, and\n // validating it as an opening would let it punch a hole through the rendering.\n if (!isOpening(object.kind)) return 'Only a door, window or gate can sit in a barrier.';\n const barrier = barriers.get(object.barrierId);\n if (!barrier || barrier.floorId !== object.floorId)\n return 'Opening references a missing barrier or a different floor.';\n const [a, b] = barrierEnds(project, barrier);\n // Actual opening width decides the required wall length, including narrow windows and doors.\n if (distance(a, b) < OPENING_MIN_SEGMENT - 1e-6)\n return `A door, window or gate needs a segment at least ${OPENING_MIN_SEGMENT} m long to sit in.`;\n const offset = object.offset ?? 0;\n if (offset - object.width / 2 < -0.001 || offset + object.width / 2 > distance(a, b) + 0.001)\n return `The barrier is too short for its attached opening. Opening \"${object.name || object.id}\" (${object.id}) on barrier ${barrier.id}: width ${object.width.toFixed(2)} m, centre offset ${offset.toFixed(2)} m, segment length ${distance(a, b).toFixed(2)} m. The centre offset must be between ${(object.width / 2).toFixed(2)} and ${(distance(a, b) - object.width / 2).toFixed(2)} m; if that interval is empty, choose a longer host segment.`;\n if (\n attachments\n .get(barrier.id)!\n .some(o => o.id !== object.id && Math.abs((o.offset ?? 0) - offset) < (o.width + object.width) / 2 - 0.001)\n )\n return 'Attached openings cannot overlap.';\n }\n }\n return null;\n}\n\n/** Semantic validation of the navigation graph — authored or derived; structural checks (ids,\n * uniqueness) live in validateProject. Returns an error message or null. */\nexport function validateNavigation(p: ProjectDocument): string | null {\n const nodes = navNodes(p),\n byId = new Map(nodes.map(n => [n.id, n]));\n const objects = new Map(p.objects.map(o => [o.id, o]));\n const floorIds = new Set(p.floors.map(f => f.id));\n for (const n of nodes) {\n if (!point(n.position)) return 'A route node needs a valid position.';\n if (n.floorId !== null && !floorIds.has(n.floorId)) return 'A route node references an unknown floor.';\n if (n.objectId !== undefined && !objects.has(n.objectId)) return 'A route node references a missing object.';\n }\n for (const e of navEdges(p)) {\n if (!['walk', 'door', 'stairs', 'elevator'].includes(e.kind)) return 'Unknown route edge kind.';\n const a = byId.get(e.aId),\n b = byId.get(e.bId);\n if (!a || !b || e.aId === e.bId) return 'A route edge must join two different route nodes.';\n if (e.weight !== undefined && (typeof e.weight !== 'number' || !Number.isFinite(e.weight) || e.weight <= 0))\n return 'A route edge weight must be a positive number.';\n const vertical = e.kind === 'stairs' || e.kind === 'elevator';\n if (vertical && a.floorId === b.floorId) return 'Stairs and elevator edges must join different floors.';\n if (e.kind === 'walk' && a.floorId !== b.floorId) return 'Walk edges must stay on one floor.';\n if (e.kind === 'door' && a.floorId !== b.floorId && a.floorId !== null && b.floorId !== null)\n return 'Door edges must stay on one floor or step outside.';\n if (e.objectId === undefined) continue;\n const object = objects.get(e.objectId);\n if (!object) return 'A route edge references a missing object.';\n if (e.kind === 'door' && !['door', 'gate', 'turnstile'].includes(object.kind))\n return 'A door edge must bind a door, gate or turnstile.';\n if (vertical && object.kind !== e.kind) return 'A vertical edge must bind a matching stairs or elevator object.';\n // Only meaningful when the object declares which floors it serves. A lift whose floors come from\n // a zone's `connects` declares nothing here, and is correct by construction — the edges were\n // generated from that zone's own membership, so there is no second list to disagree with.\n if (\n vertical &&\n object.servedFloorIds &&\n [a, b].some(n => n.floorId !== null && !object.servedFloorIds!.includes(n.floorId))\n )\n return 'A vertical edge joins a floor its stairs or elevator does not serve.';\n }\n return null;\n}\n\n/** Parse and validate an unknown value into a ProjectDocument; throws with a reason on any failure.\n * Every rule the schema has lives behind this one entry point. */\nexport function validateProject(value: unknown): ProjectDocument {\n const fail = (message: string): never => {\n throw new Error(`Invalid project: ${message}`);\n };\n // One version until the first release. The schema is still being cut, so rather than carry\n // migrations for shapes nobody has shipped against, anything else is simply not a Kerros document.\n if (!object(value) || value.schemaVersion !== 1)\n fail(\n 'unsupported schema version (expected 1). The schema is still changing before release — re-create the project.',\n );\n const v = value as Record<string, unknown>;\n // Origin is [lng, lat, bearing?] in WGS84 — the range check also flags pre-v2 projected (metre) origins.\n const origin = (o: unknown) =>\n Array.isArray(o) &&\n (o.length === 2 || o.length === 3) &&\n o.every(finite) &&\n Math.abs(o[0]) <= 180 &&\n Math.abs(o[1]) <= 90;\n if (\n !string(v.id) ||\n !string(v.name) ||\n !origin(v.origin) ||\n typeof v.description !== 'string' ||\n typeof v.datum !== 'string' ||\n !string(v.updatedAt)\n )\n fail('missing project metadata.');\n for (const key of ['buildings', 'floors', 'junctions', 'barriers', 'objects', 'drawings'])\n if (!Array.isArray(v[key]) || (v[key] as unknown[]).some(x => !object(x) || !string(x.id)))\n fail(`malformed ${key}.`);\n for (const key of ['navNodes', 'navEdges', 'zones', 'portals', 'portalGroups', 'virtualBoundaries'] as const)\n if (\n v[key] !== undefined &&\n (!Array.isArray(v[key]) || (v[key] as unknown[]).some(x => !object(x) || !string(x.id)))\n )\n fail(`malformed ${key}.`);\n const p = v as unknown as ProjectDocument;\n const all = [\n ...p.buildings,\n ...p.floors,\n ...p.junctions,\n ...p.barriers,\n ...(p.virtualBoundaries ?? []),\n ...p.objects,\n ...p.drawings,\n ...(p.navNodes ?? []),\n ...(p.navEdges ?? []),\n ...(p.zones ?? []),\n ...(p.portals ?? []),\n ...(p.portalGroups ?? []),\n ];\n if (new Set(all.map(x => x.id)).size !== all.length) fail('duplicate entity IDs.');\n const floorIds = new Set(p.floors.map(f => f.id)),\n buildingIds = new Set(p.buildings.map(b => b.id));\n if (\n !p.buildings.length ||\n p.buildings.some(\n b => !string(b.name) || (b.sourceId !== undefined && !string(b.sourceId) && !finite(b.sourceId)),\n ) ||\n !p.floors.length ||\n p.floors.some(\n f =>\n !buildingIds.has(f.buildingId) || !string(f.name) || !finite(f.elevation) || !finite(f.height) || f.height <= 0,\n )\n )\n fail('invalid buildings or floors.');\n // An escalator runs one way; a stair does not have a way to run. Saying a staircase travels up\n // describes nothing, and something downstream would eventually believe it.\n if (\n p.objects.some(o => o.travel !== undefined && (o.kind !== 'stairs' || (o.travel !== 'up' && o.travel !== 'down')))\n )\n fail('travel belongs to an escalator and must be up or down.');\n // A lamp that exists: a colour temperature inside the range a luminaire is actually made in, and\n // a level between off and fully lit. Absent is the common case and means a fluorescent ceiling.\n if (\n p.floors.some(\n f =>\n f.light !== undefined &&\n (!finite(f.light.kelvin) ||\n f.light.kelvin < 1000 ||\n f.light.kelvin > 12000 ||\n !finite(f.light.level) ||\n f.light.level < 0 ||\n f.light.level > 1 ||\n (f.light.tint !== undefined && (typeof f.light.tint !== 'string' || !/^#[0-9a-f]{6}$/i.test(f.light.tint)))),\n )\n )\n fail('a floor names a light that is not a lamp.');\n const badAmbience = (a: Ambience | undefined) =>\n a !== undefined &&\n (!AMBIENCE_PRESETS.includes(a.preset) ||\n (a.level !== undefined && (!finite(a.level) || a.level < 0 || a.level > 1)));\n if (p.floors.some(f => badAmbience(f.ambience)) || p.objects.some(o => badAmbience(o.ambience)))\n fail('an ambience names a preset that does not exist, or a level outside 0..1.');\n if (p.initialFloorId !== undefined && p.initialFloorId !== null && !p.floors.some(f => f.id === p.initialFloorId))\n fail('initialFloorId names a floor that does not exist.');\n if (p.buildings.some(b => b.exteriorPreset !== undefined && !Object.hasOwn(EXTERIOR_PRESETS, b.exteriorPreset)))\n fail('unknown exterior preset.');\n for (const e of [...p.junctions, ...p.barriers, ...p.objects, ...p.drawings])\n if (e.floorId !== null && !floorIds.has(e.floorId)) fail('unknown floor reference.');\n if (p.junctions.some(j => !point(j.position))) fail('invalid junction coordinates.');\n for (const edge of p.virtualBoundaries ?? []) {\n const a = p.junctions.find(j => j.id === edge.startId),\n b = p.junctions.find(j => j.id === edge.endId);\n if (\n !a ||\n !b ||\n a.floorId !== edge.floorId ||\n b.floorId !== edge.floorId ||\n distance(a.position, b.position) < 0.001 - 1e-6\n )\n fail('invalid virtual boundary.');\n }\n if (\n p.barriers.some(\n b =>\n !['wall', 'fence'].includes(b.kind) ||\n !string(b.name) ||\n !p.junctions.some(j => j.id === b.startId && j.floorId === b.floorId) ||\n !p.junctions.some(j => j.id === b.endId && j.floorId === b.floorId) ||\n b.startId === b.endId ||\n !finite(b.height) ||\n b.height <= 0 ||\n !finite(b.thickness) ||\n b.thickness <= 0,\n )\n )\n fail('invalid barrier.');\n // The drawn areas of each level, gathered once: the served-floor rule below asks what is under a\n // shaft on every level it claims, and a department store asks that a couple of hundred times.\n const areasByFloor = new Map<string, SiteObject[]>();\n for (const o of p.objects)\n if (o.floorId && isArea(o.kind) && Array.isArray(o.rings) && Array.isArray(o.rings[0]))\n areasByFloor.set(o.floorId, [...(areasByFloor.get(o.floorId) ?? []), o]);\n for (const o of p.objects) {\n if (\n !(OBJECT_KINDS as readonly string[]).includes(o.kind) ||\n !string(o.name) ||\n !point(o.position) ||\n !finite(o.rotation) ||\n !finite(o.width) ||\n o.width <= 0 ||\n !finite(o.depth) ||\n o.depth <= 0 ||\n !finite(o.height) ||\n o.height < 0\n )\n fail('invalid object dimensions or type.');\n if (o.rings && (!Array.isArray(o.rings) || o.rings.some(r => !Array.isArray(r) || r.some(p => !point(p)))))\n fail('invalid polygon coordinates.');\n if (o.servedFloorIds && (!Array.isArray(o.servedFloorIds) || o.servedFloorIds.some(id => !floorIds.has(id))))\n fail('unknown served floor.');\n if (o.servedFloorIds && !distinct(o.servedFloorIds)) fail('served floors must be listed once each.');\n // A shaft stands in one place and reaches a list of levels, so the plan has to agree with itself\n // about that place: where the drawn areas of the levels it serves put floor under it, they all\n // have to. One that lands on twelve plates and falls outside the thirteenth is the real case —\n // a lift core beside a mezzanine gallery, its doors opening onto the hall two storeys down, and\n // routing sending someone through them. Judged on outer boundaries only (a hole is exactly where\n // a shaft belongs: a stair in a light well, a lift in an atrium), and only against levels that\n // are drawn at all — a plan whose floors are not areas yet says nothing about any of its shafts,\n // which is why a shaft standing off every one of them is left alone.\n if (o.servedFloorIds && (o.kind === 'stairs' || o.kind === 'elevator')) {\n const drawn = o.servedFloorIds.map(id => areasByFloor.get(id)).filter(areas => !!areas?.length);\n const on = drawn.map(areas => areas!.some(a => pointInRing(o.position, (a.rings as Ring[])[0])));\n if (on.some(Boolean) && !on.every(Boolean))\n fail('a stair or lift serves a level it does not stand on — its landing there opens onto nothing.');\n }\n if (o.doorHinge !== undefined || o.doorSwing !== undefined || o.doorType !== undefined) {\n if (o.kind !== 'door') fail('only doors carry mechanism, hinge and swing settings.');\n if (o.doorType !== undefined && !['hinged', 'sliding', 'double'].includes(o.doorType))\n fail('door type must be hinged, sliding or double.');\n if (o.doorHinge !== undefined && !['left', 'right'].includes(o.doorHinge))\n fail('door hinge must be left or right.');\n if (o.doorSwing !== undefined && o.doorSwing !== 1 && o.doorSwing !== -1) fail('door swing must be 1 or -1.');\n }\n if (o.doorSides !== undefined) {\n if (o.kind !== 'elevator') fail('only an elevator lists door sides.');\n if (\n !Array.isArray(o.doorSides) ||\n !o.doorSides.length ||\n o.doorSides.some(d => !['front', 'back', 'left', 'right'].includes(d as string)) ||\n !distinct(o.doorSides as string[])\n )\n fail('door sides are front, back, left or right, listed once each.');\n }\n if (o.wellGroup !== undefined && (o.kind !== 'stairs' || !string(o.wellGroup) || !o.wellGroup.trim()))\n fail('only stairs carry a non-empty shared well group.');\n if (o.stairModel !== undefined) {\n if (o.kind !== 'stairs') fail('only stairs carry a stair model.');\n if (!['straight', 'switchback', 'dogleg', 'spiral', 'escalator'].includes(o.stairModel as string))\n fail('a stair model is straight, switchback, dogleg, spiral or escalator.');\n }\n if (o.light !== undefined || o.kind === 'light') {\n const lamp = o.light;\n if (\n o.kind !== 'light' ||\n !object(lamp) ||\n (lamp.mountHeight !== undefined &&\n (!finite(lamp.mountHeight) || lamp.mountHeight < -20 || lamp.mountHeight > 100)) ||\n !finite(lamp.kelvin) ||\n lamp.kelvin < 1000 ||\n lamp.kelvin > 12000 ||\n !finite(lamp.intensity) ||\n lamp.intensity < 0 ||\n lamp.intensity > 10000 ||\n !finite(lamp.range) ||\n lamp.range <= 0 ||\n lamp.range > 100 ||\n (lamp.flicker !== undefined && (!finite(lamp.flicker) || lamp.flicker < 0 || lamp.flicker > 1))\n )\n fail('a light needs a valid colour temperature, intensity, range and optional flicker.');\n }\n if (o.watchedIds && (!Array.isArray(o.watchedIds) || o.watchedIds.some(id => !p.objects.some(x => x.id === id))))\n fail('unknown watched object.');\n if (o.watchedIds && !distinct(o.watchedIds)) fail('watched objects must be listed once each.');\n if (\n o.ceilingHeight !== undefined &&\n (!['room', 'zone'].includes(o.kind as string) || !finite(o.ceilingHeight) || o.ceilingHeight <= 0)\n )\n fail('a space ceiling height must be positive.');\n if (\n o.baseHeight !== undefined &&\n (o.kind !== 'fixture' || !finite(o.baseHeight) || o.baseHeight < 0 || o.baseHeight > 100)\n )\n fail('a raised fixture needs a base height between 0 and 100 m.');\n if (o.water !== undefined) {\n const water = o.water;\n if (\n !['room', 'zone'].includes(o.kind as string) ||\n !o.rings ||\n !object(water) ||\n !finite(water.depth) ||\n water.depth <= 0 ||\n water.depth > 20 ||\n (water.ripple !== undefined && (!finite(water.ripple) || water.ripple < 0 || water.ripple > 0.1))\n )\n fail('water needs an area, a depth up to 20 m and ripples up to 10 cm.');\n }\n if (o.slide !== undefined) {\n const slide = o.slide;\n if (\n o.kind !== 'fixture' ||\n !object(slide) ||\n !finite(slide.radius) ||\n slide.radius <= 0 ||\n !Array.isArray(slide.path) ||\n slide.path.length < 2 ||\n slide.path.length > 100 ||\n !slide.path.every(p => Array.isArray(p) && p.length === 3 && p.every(finite) && (p[2] as number) >= 0)\n )\n fail('a water slide needs a positive radius and a finite path above its floor.');\n }\n if (o.slope !== undefined) {\n const s = o.slope;\n if (\n !object(s) ||\n !Array.isArray(s.axis) ||\n s.axis.length !== 2 ||\n !s.axis.every(point) ||\n !finite(s.high) ||\n !finite(s.low) ||\n distance(s.axis[0] as Point, s.axis[1] as Point) < 0.01 ||\n s.high < s.low\n )\n fail('invalid slope: needs a downhill two-point axis (high >= low) of non-zero length.');\n if (!o.rings) fail('a sloped object must be an area with a footprint.');\n }\n if (o.category !== undefined && !string(o.category)) fail('object category must be a string.');\n if (o.metadata !== undefined && !object(o.metadata)) fail('object metadata must be an object.');\n if (o.barrierId && !finite(o.offset)) fail('missing opening offset.');\n if (o.feedId !== undefined && typeof o.feedId !== 'string') fail('invalid feed binding.');\n if (o.coverageRange !== undefined && (!finite(o.coverageRange) || o.coverageRange <= 0))\n fail('invalid camera range.');\n if (o.coverageAngle !== undefined && (!finite(o.coverageAngle) || o.coverageAngle <= 0 || o.coverageAngle > 360))\n fail('invalid camera angle.');\n }\n for (const d of p.drawings)\n if (\n !string(d.assetId) ||\n !string(d.name) ||\n !point(d.origin) ||\n !finite(d.scale) ||\n d.scale <= 0 ||\n !finite(d.width) ||\n d.width <= 0 ||\n !finite(d.height) ||\n d.height <= 0 ||\n !finite(d.rotation) ||\n !finite(d.opacity) ||\n d.opacity < 0 ||\n d.opacity > 1 ||\n typeof d.visible !== 'boolean' ||\n typeof d.locked !== 'boolean'\n )\n fail('invalid reference drawing.');\n // ——— The spatial ontology. Spaces are the ringed area objects; zones and portals only reference\n // them, so every reference has to resolve or the derived graph would quietly lose edges.\n const spaceIds = new Set(p.objects.filter(o => isSpace(o.kind)).map(o => o.id));\n const kindById = new Map(p.objects.map(o => [o.id, o.kind]));\n const zoneIds = new Set((p.zones ?? []).map(z => z.id));\n for (const z of p.zones ?? []) {\n if (!string(z.name) || !Array.isArray(z.spaceIds)) fail('a zone needs a name and a list of spaces.');\n if (z.spaceIds.some(id => !spaceIds.has(id))) fail('a zone lists a space that is not an area object.');\n if (!distinct(z.spaceIds)) fail('a zone must list each space once — membership is a set.');\n if (z.childZoneIds && z.childZoneIds.some(id => !zoneIds.has(id))) fail('a zone nests a zone that does not exist.');\n if (z.childZoneIds && !distinct(z.childZoneIds)) fail('a zone must nest each child once.');\n if (z.connects !== undefined && !['all', 'adjacent', 'up', 'down'].includes(z.connects))\n fail(\"a zone's connects must be 'all', 'adjacent', 'up' or 'down'.\");\n }\n // Zones nest as a DAG. Cycles are tolerated at read time (ontology.ts guards) but are still a\n // malformed document, and saying so here is cheaper than debugging a zone that contains itself.\n const walking = new Set<string>(),\n settled = new Set<string>();\n const cycles = (id: string): boolean => {\n if (settled.has(id)) return false;\n if (walking.has(id)) return true;\n walking.add(id);\n const found = ((p.zones ?? []).find(z => z.id === id)?.childZoneIds ?? []).some(cycles);\n walking.delete(id);\n settled.add(id);\n return found;\n };\n if ([...zoneIds].some(cycles)) fail('zones cannot contain themselves.');\n for (const portal of p.portals ?? []) {\n if (!spaceIds.has(portal.a) || !spaceIds.has(portal.b))\n fail('a portal must join two areas that exist on the plan.');\n if (portal.a === portal.b) fail('a portal must join two different spaces.');\n // A window is an opening but never a portal — you cannot walk through it. Same list as\n // inference's TRAVERSABLE, inlined so the rules depend on nothing that changes at runtime.\n if (portal.openingId && !['door', 'gate', 'turnstile'].includes(kindById.get(portal.openingId) ?? ''))\n fail(\"a portal's opening must be a door, gate or turnstile.\");\n if (portal.passage !== undefined && !['both', 'a-to-b', 'b-to-a', 'none'].includes(portal.passage))\n fail('invalid portal passage direction.');\n if (portal.attests !== undefined && !['confirmed', 'assumed', 'none'].includes(portal.attests))\n fail('invalid portal attestation.');\n }\n const portalIds = new Set((p.portals ?? []).map(x => x.id));\n for (const g of p.portalGroups ?? []) {\n if (!string(g.name) || !Array.isArray(g.portalIds) || g.portalIds.some(id => !portalIds.has(id)))\n fail('a portal group needs a name and must list portals that exist.');\n if (!distinct(g.portalIds)) fail('a portal group must list each portal once.');\n }\n const error = validateRelationships(p);\n if (error) fail(error);\n validateSpaceBoundaries(p);\n const navError = validateNavigation(p);\n if (navError) fail(navError);\n return p;\n}\n\n// ——— The transaction gate.\n\n/** Deep-freeze a document in place and return it. Writes to a frozen document throw (modules run in\n * strict mode), so an out-of-band mutation — one that dodged `transact` — fails at the assignment\n * that would have corrupted the document, not three renders later. */\nexport function freezeProject(project: ProjectDocument): ProjectDocument {\n const walk = (value: unknown) => {\n if (value && typeof value === 'object' && !Object.isFrozen(value)) {\n Object.freeze(value);\n for (const inner of Object.values(value)) walk(inner);\n }\n };\n walk(project);\n return project;\n}\n\nexport type TransactResult = { ok: true; project: ProjectDocument } | { ok: false; error: string };\n\n/** Apply a change atomically: work on a clone, run every rule, and only then hand the result back —\n * frozen, so nothing can edit it except the next transaction. If the change throws, or the outcome\n * breaks any rule, the original is untouched and the reason comes back as `error`; there is no\n * state in which a caller holds a partly-applied or invalid document.\n *\n * `updatedAt` is deliberately not stamped here — that belongs to whoever owns persistence and\n * history (see commitHistory), which also stamps undo and redo. */\nexport function transact(\n project: ProjectDocument,\n change: (draft: ProjectDocument) => void,\n options?: { freeze?: boolean },\n): TransactResult {\n try {\n const draft = structuredClone(project); // clones of frozen objects are mutable: drafts stay editable\n change(draft);\n synchronizeGeometry(project, draft);\n // Older imported outlines remain readable. New or resized areas use the same usable-area\n // minimum as enclosure and splitting; changing a name never invalidates a legacy small area.\n for (const o of draft.objects)\n if (isArea(o.kind) && objectArea(o) < MIN_SPACE_AREA) {\n const old = project.objects.find(previous => previous.id === o.id && isArea(previous.kind));\n if (!old || objectArea(old) !== objectArea(o))\n throw new Error(\n `A space needs at least ${MIN_SPACE_AREA} m² of usable area. \"${o.name || o.id}\" (${o.id}) has ${objectArea(o).toFixed(3)} m².`,\n );\n }\n validateProject(draft);\n return { ok: true, project: options?.freeze === false ? draft : freezeProject(draft) };\n } catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) };\n }\n}\n"],"names":["COORD_LIMIT","finite","v","point","n","object","string","distinct","ids","validateRings","rings","minEdge","minArea","ring","p","openRing","ringArea","i","distance","j","intersects","pointInRing","a","b","k","l","validateRelationships","project","objects","barriers","attachments","list","barrier","barrierEnds","MIN_SEGMENT","shared","error","MIN_FACE_AREA","parent","containedBy","seen","ancestor","isOpening","OPENING_MIN_SEGMENT","offset","o","validateNavigation","nodes","navNodes","byId","floorIds","f","e","navEdges","vertical","validateProject","value","fail","message","origin","key","x","all","buildingIds","badAmbience","AMBIENCE_PRESETS","EXTERIOR_PRESETS","edge","areasByFloor","isArea","OBJECT_KINDS","r","id","on","areas","d","lamp","water","slide","s","spaceIds","isSpace","kindById","zoneIds","z","walking","settled","cycles","found","_a","portal","portalIds","g","validateSpaceBoundaries","navError","freezeProject","walk","inner","transact","change","options","draft","synchronizeGeometry","objectArea","MIN_SPACE_AREA","old","previous"],"mappings":"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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { footprint as k, closeRing as R, objectRotation as y, rotate as F, rectangle as G, objectPosition as A } from "./geometry.js";
|
|
2
|
+
const I = (t) => t === "stairs" || t === "elevator", w = (t) => `${t.kind}|${t.name}|${t.position[0].toFixed(2)}|${t.position[1].toFixed(2)}`;
|
|
3
|
+
function x(t, o) {
|
|
4
|
+
const n = w(o), i = t.objects.filter((e) => I(e.kind) && w(e) === n).flatMap((e) => {
|
|
5
|
+
var s;
|
|
6
|
+
return (s = e.servedFloorIds) != null && s.length ? e.servedFloorIds : e.floorId ? [e.floorId] : [];
|
|
7
|
+
});
|
|
8
|
+
return [...new Set(i.length ? i : o.floorId ? [o.floorId] : [])].map((e) => t.floors.find((s) => s.id === e)).filter((e) => !!e).sort((e, s) => e.elevation - s.elevation);
|
|
9
|
+
}
|
|
10
|
+
function T(t, o) {
|
|
11
|
+
const n = x(t, o), i = [];
|
|
12
|
+
for (let e = 1; e < n.length; e++)
|
|
13
|
+
i.push({ from: n[e - 1], to: n[e], rise: n[e].elevation - n[e - 1].elevation });
|
|
14
|
+
return i;
|
|
15
|
+
}
|
|
16
|
+
const _ = (t, o, n) => n !== null && (o.floorId === n || x(t, o).some((i) => i.id === n));
|
|
17
|
+
function q(t, o, n) {
|
|
18
|
+
const i = T(t, o);
|
|
19
|
+
return {
|
|
20
|
+
up: i.find((e) => e.from.id === n),
|
|
21
|
+
down: i.find((e) => e.to.id === n)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function z(t, o) {
|
|
25
|
+
const n = x(t, o);
|
|
26
|
+
return n.length < 2 ? null : { bottom: n[0].elevation, top: n[n.length - 1].elevation };
|
|
27
|
+
}
|
|
28
|
+
const P = { escalator: 30, stairs: 32 }, O = (t, o = P.stairs) => t / Math.tan(o * Math.PI / 180), S = (t, o) => o > 0 ? Math.atan(t / o) * 180 / Math.PI : 90, $ = 0.32, X = 0.175, E = 2.1;
|
|
29
|
+
function H(t, o) {
|
|
30
|
+
const n = Math.max(2, Math.min(24, Math.round(t / X)));
|
|
31
|
+
return { steps: n, going: Math.min($, o / n) };
|
|
32
|
+
}
|
|
33
|
+
function V(t, o) {
|
|
34
|
+
if (o.stairModel) return o.stairModel;
|
|
35
|
+
const n = T(t, o)[0];
|
|
36
|
+
return n && S(n.rise, Math.max(0.6, o.depth)) > 38 ? "switchback" : "straight";
|
|
37
|
+
}
|
|
38
|
+
const Y = (t, o) => t === "escalator" && o % 2 === 1;
|
|
39
|
+
function B(t, o, n, i, e = 0) {
|
|
40
|
+
const s = A(t, o), h = y(t, o) + (Y(i, e) ? 180 : 0), d = h * Math.PI / 180, p = Math.sin(d), a = -Math.cos(d), u = (f) => [s[0] + p * f, s[1] + a * f], r = Math.max(0.6, o.depth), c = r / 2;
|
|
41
|
+
if (i === "escalator") {
|
|
42
|
+
const f = Math.min(1, r / 4), g = Math.min(r - f * 2, Math.max(0.4, O(n, P.escalator)));
|
|
43
|
+
return { rotation: h, at: u, length: r, half: c, pad: f, incline: g, topT: -c + f, footT: -c + f + g };
|
|
44
|
+
}
|
|
45
|
+
const { steps: v, going: m } = H(n, r), l = Math.min(r, v * m);
|
|
46
|
+
return { rotation: h, at: u, length: r, half: c, pad: 0, incline: l, topT: c - l, footT: c };
|
|
47
|
+
}
|
|
48
|
+
function C(t, o, n, i) {
|
|
49
|
+
const e = o > 0.01 ? t.incline / o : t.incline, h = Math.min(t.footT, Math.max(t.topT + 0.6, t.footT - n * e + E * e)) - t.topT;
|
|
50
|
+
return R(G(t.at(t.topT + h / 2), i, h, t.rotation));
|
|
51
|
+
}
|
|
52
|
+
function J(t) {
|
|
53
|
+
const o = /* @__PURE__ */ new Map();
|
|
54
|
+
for (const n of t.objects) {
|
|
55
|
+
if (!I(n.kind)) continue;
|
|
56
|
+
const i = w(n), e = o.get(i), s = (h) => {
|
|
57
|
+
var d;
|
|
58
|
+
return ((d = t.floors.find((p) => p.id === h.floorId)) == null ? void 0 : d.elevation) ?? 0;
|
|
59
|
+
};
|
|
60
|
+
(!e || s(n) < s(e)) && o.set(i, n);
|
|
61
|
+
}
|
|
62
|
+
return new Set([...o.values()].map((n) => n.id));
|
|
63
|
+
}
|
|
64
|
+
function D(t, o, n) {
|
|
65
|
+
const i = t.floors.find((s) => s.id === n);
|
|
66
|
+
if (!i) return !1;
|
|
67
|
+
const e = x(t, o);
|
|
68
|
+
return e.some((s) => s.elevation < i.elevation - 0.01) && e.some((s) => s.elevation >= i.elevation - 0.01);
|
|
69
|
+
}
|
|
70
|
+
function K(t, o, n) {
|
|
71
|
+
const i = t.floors.find((s) => s.id === n);
|
|
72
|
+
if (!i) return !1;
|
|
73
|
+
const e = x(t, o);
|
|
74
|
+
return e.some((s) => s.elevation <= i.elevation + 0.01) && e.some((s) => s.elevation > i.elevation + 0.01);
|
|
75
|
+
}
|
|
76
|
+
const L = (t, o, n) => D(t, o, n) && K(t, o, n);
|
|
77
|
+
function Q(t, o, n, i = {}) {
|
|
78
|
+
const e = t.floors.find((a) => a.id === o);
|
|
79
|
+
if (!e) return [];
|
|
80
|
+
const s = i.through === "ceiling", h = i.lowest ?? -1 / 0, d = [], p = /* @__PURE__ */ new Map();
|
|
81
|
+
for (const a of t.objects) {
|
|
82
|
+
if (!I(a.kind) || n && !n.has(a.id)) continue;
|
|
83
|
+
const u = T(t, a), r = s ? u.findIndex((l) => l.from.elevation <= e.elevation + 0.01 && l.to.elevation > e.elevation + 0.01) : u.findIndex((l) => l.from.elevation < e.elevation - 0.01 && l.to.elevation >= e.elevation - 0.01);
|
|
84
|
+
if (r < 0) continue;
|
|
85
|
+
const c = u[r];
|
|
86
|
+
if (c.from.elevation < h) continue;
|
|
87
|
+
if (a.kind === "stairs" && a.wellGroup) {
|
|
88
|
+
const l = p.get(a.wellGroup) ?? [];
|
|
89
|
+
l.push(a), p.set(a.wellGroup, l);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const v = V(t, a);
|
|
93
|
+
if (a.kind === "stairs" && (v === "straight" || v === "escalator")) {
|
|
94
|
+
const l = B(t, a, c.rise, v, r), f = e.elevation + (s ? e.height : 0) - c.from.elevation;
|
|
95
|
+
d.push(C(l, c.rise, f, a.width));
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const m = k(a)[0];
|
|
99
|
+
m != null && m.length && d.push(R(m.map((l) => [l[0], l[1]])));
|
|
100
|
+
}
|
|
101
|
+
for (const a of p.values()) {
|
|
102
|
+
const u = y(t, a[0]), r = a.flatMap((M) => k(M)[0]).map((M) => F(M, -u)), c = r.map((M) => M[0]), v = r.map((M) => M[1]), m = Math.min(...c), l = Math.max(...c), f = Math.min(...v), g = Math.max(...v);
|
|
103
|
+
d.push(G(F([(m + l) / 2, (f + g) / 2], u), l - m, g - f, u));
|
|
104
|
+
}
|
|
105
|
+
return d;
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
$ as MAX_GOING,
|
|
109
|
+
P as PITCH,
|
|
110
|
+
D as arrivesAt,
|
|
111
|
+
K as departsFrom,
|
|
112
|
+
Y as flightReversed,
|
|
113
|
+
B as flightRun,
|
|
114
|
+
T as flights,
|
|
115
|
+
q as flightsAt,
|
|
116
|
+
I as isVertical,
|
|
117
|
+
L as passesThrough,
|
|
118
|
+
S as pitchOf,
|
|
119
|
+
J as primaryShafts,
|
|
120
|
+
_ as reaches,
|
|
121
|
+
O as runFor,
|
|
122
|
+
x as servedFloors,
|
|
123
|
+
w as shaftKey,
|
|
124
|
+
Q as shaftVoids,
|
|
125
|
+
z as span,
|
|
126
|
+
V as stairModel,
|
|
127
|
+
H as treads
|
|
128
|
+
};
|
|
129
|
+
//# sourceMappingURL=vertical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vertical.js","sources":["../../../../src/model/vertical.ts"],"sourcesContent":["// Vertical circulation: which levels a stair, escalator or lift actually reaches, and what it has to\n// climb between them.\n//\n// A shaft is the one object that is not on a floor so much as *between* floors, and the document has\n// always said so — `servedFloorIds` is authored in the inspector, seeded by the factory, threaded by\n// routing and enforced by validation. Nothing else read it. The renderer guessed instead, taking\n// \"the next floor up in the same building\" as the rise and drawing the object only on the storey it\n// was filed under, which is why a flight arriving at a level was invisible from that level and why a\n// lift serving eight floors was a box you could see over.\n//\n// This is where that question gets answered once, so the plan, the route and the model agree about\n// where a stair goes.\nimport { closeRing, footprint, objectPosition, objectRotation, rectangle, rotate } from './geometry';\nimport type { Floor, Point, ProjectDocument, Ring, SiteObject } from './types';\n\n/** True for the kinds that connect levels. */\nexport const isVertical = (kind: string) => kind === 'stairs' || kind === 'elevator';\n\n/** Which shaft this object is part of.\n *\n * A shaft is one thing standing in one place reaching several levels, and that is how the document\n * can describe it: one object, one position, one `servedFloorIds`. Older documents — the demo among\n * them — instead repeat the object on every storey it serves, because that is what you had to do\n * before anything read the served list. Both must draw as one lift, so a shaft is identified by what\n * makes it the same shaft: its kind, its name, and where it stands. */\nexport const shaftKey = (object: SiteObject) =>\n `${object.kind}|${object.name}|${object.position[0].toFixed(2)}|${object.position[1].toFixed(2)}`;\n\n/** Every level this shaft reaches, lowest first.\n *\n * `servedFloorIds` when it is authored — that is the document's own statement and outranks any\n * inference. A shaft that declares nothing serves the floor it stands on and no other: better to\n * draw a stair going nowhere, which is visibly wrong and easy to fix, than to invent a connection\n * that routing would then happily send someone through. */\nexport function servedFloors(project: ProjectDocument, object: SiteObject): Floor[] {\n // Every twin's reach, not just this one's. A plan sheet only says what is on its own storey, so an\n // import gives the same stair once per floor it is drawn on, each claiming only that floor. Read\n // together they say what the stair actually connects — which is the question being asked, and the\n // reason a stair imported from three sheets is one flight and not three plates.\n const key = shaftKey(object);\n const ids = project.objects\n .filter(o => isVertical(o.kind) && shaftKey(o) === key)\n .flatMap(o => (o.servedFloorIds?.length ? o.servedFloorIds : o.floorId ? [o.floorId] : []));\n return [...new Set(ids.length ? ids : object.floorId ? [object.floorId] : [])]\n .map(id => project.floors.find(f => f.id === id))\n .filter((f): f is Floor => !!f)\n .sort((a, b) => a.elevation - b.elevation);\n}\n\n/** One climb between two levels the shaft serves. */\nexport interface Flight {\n from: Floor;\n to: Floor;\n /** Metres climbed, always positive. */\n rise: number;\n}\n\n/** The climbs this shaft makes, bottom first.\n *\n * Consecutive served levels, so a stair serving 0-1-2 makes two flights and a lift serving the same\n * three makes two hops of shaft. A level in between that the shaft passes without stopping is simply\n * not in the list, and the flight spanning it is the taller for it — which is the right picture: an\n * express lift's shaft does pass those floors, and a spiral stair's helix does wind past a mezzanine\n * it has no landing on. */\nexport function flights(project: ProjectDocument, object: SiteObject): Flight[] {\n const levels = servedFloors(project, object);\n const out: Flight[] = [];\n for (let i = 1; i < levels.length; i++)\n out.push({ from: levels[i - 1], to: levels[i], rise: levels[i].elevation - levels[i - 1].elevation });\n return out;\n}\n\n/** Does this shaft reach the given level? Includes the floor it is filed under. */\nexport const reaches = (project: ProjectDocument, object: SiteObject, floorId: string | null): boolean =>\n floorId !== null && (object.floorId === floorId || servedFloors(project, object).some(f => f.id === floorId));\n\n/** The flight arriving at a level from below, and the one leaving it upward — what you would see\n * standing on that level. Either may be absent at the ends of the run. */\nexport function flightsAt(\n project: ProjectDocument,\n object: SiteObject,\n floorId: string | null,\n): { up?: Flight; down?: Flight } {\n const all = flights(project, object);\n return {\n up: all.find(f => f.from.id === floorId),\n down: all.find(f => f.to.id === floorId),\n };\n}\n\n/** How far a shaft's body runs, as absolute elevations: the lowest level it serves to the highest.\n * Null when it serves fewer than two levels and so has no body to speak of. */\nexport function span(project: ProjectDocument, object: SiteObject): { bottom: number; top: number } | null {\n const levels = servedFloors(project, object);\n if (levels.length < 2) return null;\n return { bottom: levels[0].elevation, top: levels[levels.length - 1].elevation };\n}\n\n/** The angle a flight of this kind wants to climb at, in degrees from horizontal.\n *\n * An escalator is a machine built to one geometry: 30°, the world over. A stair is free-er but a\n * comfortable flight is around 32°, and anything past about 42° is a ladder. These are what the\n * plan should be *checked against* — the footprint stays the truth of where the thing is — so a run\n * too short for its rise reads as the steep slope it would really be. */\nexport const PITCH = { escalator: 30, stairs: 32 } as const;\n\n/** The run a flight of this rise needs to climb at its natural pitch. Use it to size a shaft the\n * drawing has not sized, and to tell whether one it has sized is plausible. */\nexport const runFor = (rise: number, pitch: number = PITCH.stairs) => rise / Math.tan((pitch * Math.PI) / 180);\n\n/** The pitch a flight actually climbs at, given the run the plan gives it. */\nexport const pitchOf = (rise: number, run: number) => (run > 0 ? (Math.atan(rise / run) * 180) / Math.PI : 90);\n\n/** The deepest tread that is still a tread. Past about a third of a metre a flight stops being a\n * stair: the going grows with the box while the rise per step does not, and what you get is a ramp\n * with slabs laid on it. A box longer than the climb needs keeps its length — the surplus is\n * landing, which is what that spare floor really is. */\nexport const MAX_GOING = 0.32;\n/** A comfortable domestic riser, and close to code everywhere. */\nconst RISER = 0.175;\n/** Head clearance under a flight, and so the reach of the hole one needs in the plate it climbs\n * through: everything above the plate, plus the stretch below it where a person standing on the\n * plate would still be under the soffit. */\nconst HEADROOM = 2.1;\n\n/** The treads one flight of this rise gets, and how deep each is in a run of this length. Capped\n * both ways: a handful of boxes reads as a stair, two hundred reads as a stair and costs a level. */\nexport function treads(rise: number, run: number): { steps: number; going: number } {\n const steps = Math.max(2, Math.min(24, Math.round(rise / RISER)));\n return { steps, going: Math.min(MAX_GOING, run / steps) };\n}\n\nexport type StairModel = NonNullable<SiteObject['stairModel']>;\n\n/** How a shaft is built — as the document says, or as its own footprint implies.\n *\n * A footprint that cannot take the rise at a civil pitch is describing a stair that turns, which is\n * what a short wide core box always is. Both the renderer and the plate it cuts its hole in have to\n * agree about that, so the reading lives here rather than in either of them. */\nexport function stairModel(project: ProjectDocument, object: SiteObject): StairModel {\n if (object.stairModel) return object.stairModel;\n const first = flights(project, object)[0];\n return first && pitchOf(first.rise, Math.max(0.6, object.depth)) > 38 ? 'switchback' : 'straight';\n}\n\n/** A bank of escalators is stacked criss-cross: every other flight runs the other way, so you step\n * off one and turn to step onto the next. Which way round a given flight is decides which end of\n * the footprint it arrives at, and so which end of it the floor above has to be open. */\nexport const flightReversed = (model: StairModel, index: number) => model === 'escalator' && index % 2 === 1;\n\n/** Where one flight's sloping part actually lies inside the box the plan drew.\n *\n * The plan gives a footprint; the climb is what has to fit in it. An escalator keeps a metre of flat\n * comb plate at each end and then climbs at the machine's own 30°, so a box with run to spare gets a\n * longer landing at its foot instead of a shallower ramp — a 1.6 m rise in a 12 m box is a stair with\n * a lot of floor round it, not a slide. A stair's treads are capped at MAX_GOING for the same reason.\n * A box too short for its rise is left alone: it really is that steep and the picture should say so.\n *\n * `t` runs along the object's depth axis from its centre. The foot of a flight is at +depth/2 and its\n * head at -depth/2, which is how the renderer has always laid one out; the surplus goes to whichever\n * end that leaves free — the foot of an escalator, whose comb plates must stay at the ends the plan\n * drew them at, and the head of a stair, which has none. */\nexport interface FlightRun {\n /** The rotation this flight is drawn at — see flightReversed. */\n rotation: number;\n /** A point t metres along the run from the object's centre. */\n at: (t: number) => Point;\n /** How long the box is along that axis, and half of it. */\n length: number;\n half: number;\n /** The flat plate at each end of an escalator; zero for a stair. */\n pad: number;\n /** The sloping part: how long it is, and where it starts and ends along the run. */\n incline: number;\n topT: number;\n footT: number;\n}\nexport function flightRun(\n project: ProjectDocument,\n object: SiteObject,\n rise: number,\n model: StairModel,\n index = 0,\n): FlightRun {\n const position = objectPosition(project, object);\n const rotation = objectRotation(project, object) + (flightReversed(model, index) ? 180 : 0);\n const rad = (rotation * Math.PI) / 180;\n const ux = Math.sin(rad),\n uy = -Math.cos(rad);\n const at = (t: number): Point => [position[0] + ux * t, position[1] + uy * t];\n const length = Math.max(0.6, object.depth),\n half = length / 2;\n if (model === 'escalator') {\n const pad = Math.min(1, length / 4);\n const incline = Math.min(length - pad * 2, Math.max(0.4, runFor(rise, PITCH.escalator)));\n return { rotation, at, length, half, pad, incline, topT: -half + pad, footT: -half + pad + incline };\n }\n const { steps, going } = treads(rise, length);\n const incline = Math.min(length, steps * going);\n return { rotation, at, length, half, pad: 0, incline, topT: half - incline, footT: half };\n}\n\n/** The hole one flight needs in a plate standing `height` metres above its foot.\n *\n * Not the flight's whole footprint. Everything from the head of the run down to where the flight\n * pierces the plate has to be open, because the flight is above the plate there; so does the stretch\n * below it where the soffit is still within head height. Past that the flight is under the floor,\n * which is where the cupboard under the stairs comes from — and why punching the whole of an\n * eight-metre escalator through every storey it passes opened a trench in each of them. */\nfunction headroomBand(run: FlightRun, rise: number, height: number, width: number): Ring {\n const steep = rise > 0.01 ? run.incline / rise : run.incline;\n const end = Math.min(run.footT, Math.max(run.topT + 0.6, run.footT - height * steep + HEADROOM * steep));\n const depth = end - run.topT;\n return closeRing(rectangle(run.at(run.topT + depth / 2), width, depth, run.rotation));\n}\n\n/** The one twin that stands for the shaft: the lowest, so the flights count upward from the bottom.\n * A shaft described the modern way — one object — is trivially its own primary. */\nexport function primaryShafts(project: ProjectDocument): Set<string> {\n const lowest = new Map<string, SiteObject>();\n for (const o of project.objects) {\n if (!isVertical(o.kind)) continue;\n const key = shaftKey(o);\n const rival = lowest.get(key);\n const at = (x: SiteObject) => project.floors.find(f => f.id === x.floorId)?.elevation ?? 0;\n if (!rival || at(o) < at(rival)) lowest.set(key, o);\n }\n return new Set([...lowest.values()].map(o => o.id));\n}\n\n/** Does something come up through this level's floor? The shaft starts below it and reaches it or\n * goes past — so a flight arrives here and the plate it arrives through has to be open.\n *\n * This is one half of what `passesThrough` used to answer for both. Requiring a served level above\n * as well meant the flight arriving at the top of a run was drawn under an uncut slab: on Stockmann's\n * ninth floor all twelve shafts arrive and not one of them cut a hole, so the last flight of every\n * stair in the building was invisible from the storey it lands on. */\nexport function arrivesAt(project: ProjectDocument, object: SiteObject, floorId: string): boolean {\n const level = project.floors.find(f => f.id === floorId);\n if (!level) return false;\n const levels = servedFloors(project, object);\n return (\n levels.some(f => f.elevation < level.elevation - 0.01) && levels.some(f => f.elevation >= level.elevation - 0.01)\n );\n}\n\n/** The other half: does something climb out through this level's ceiling? The shaft stands on this\n * level or lower and carries on above it, so the lid a walked storey gets has to be open over it —\n * which the bottom of a run needs just as much as the middle of one. */\nexport function departsFrom(project: ProjectDocument, object: SiteObject, floorId: string): boolean {\n const level = project.floors.find(f => f.id === floorId);\n if (!level) return false;\n const levels = servedFloors(project, object);\n return (\n levels.some(f => f.elevation <= level.elevation + 0.01) && levels.some(f => f.elevation > level.elevation + 0.01)\n );\n}\n\n/** Does this shaft pass THROUGH a level — arriving from below and carrying on above? Both halves at\n * once, which is the question a level in the middle of a run answers yes to and no end of one does. */\nexport const passesThrough = (project: ProjectDocument, object: SiteObject, floorId: string): boolean =>\n arrivesAt(project, object, floorId) && departsFrom(project, object, floorId);\n\nexport interface VoidOptions {\n /** `floor` (the default) cuts what arrives from below; `ceiling` cuts what departs upward. */\n through?: 'floor' | 'ceiling';\n /** Ignore flights that set off below this elevation. An above-ground view draws nothing under\n * grade, and a hole with nothing beneath it is worse than no hole at all. */\n lowest?: number;\n}\n\n/** What to cut out of a level's floor plate, or out of the lid over a walked storey.\n *\n * A stairwell is a hole. Drawing the slab whole and the stair beneath it puts a lid over the flight,\n * so from the landing above you see a plate where the opening should be and the stair appears to\n * climb into the ceiling. The hole is only as big as the flight needs, though: a lift or a turning\n * core is a shaft and takes its whole footprint, but a straight flight or an escalator lies under\n * the floor for most of its length and wants opening only where it comes up — see headroomBand. */\nexport function shaftVoids(\n project: ProjectDocument,\n floorId: string,\n primary?: Set<string>,\n options: VoidOptions = {},\n): Ring[] {\n const level = project.floors.find(f => f.id === floorId);\n if (!level) return [];\n const ceiling = options.through === 'ceiling';\n const lowest = options.lowest ?? -Infinity;\n const out: Ring[] = [];\n const groups = new Map<string, SiteObject[]>();\n for (const o of project.objects) {\n if (!isVertical(o.kind) || (primary && !primary.has(o.id))) continue;\n const all = flights(project, o);\n // The one climb this plate is in the way of: the flight arriving through it, or the flight\n // setting off up through it. They are different flights on a level in the middle of a run, and\n // on a criss-cross bank they arrive at opposite ends of the same box.\n const index = ceiling\n ? all.findIndex(f => f.from.elevation <= level.elevation + 0.01 && f.to.elevation > level.elevation + 0.01)\n : all.findIndex(f => f.from.elevation < level.elevation - 0.01 && f.to.elevation >= level.elevation - 0.01);\n if (index < 0) continue;\n const flight = all[index];\n if (flight.from.elevation < lowest) continue;\n if (o.kind === 'stairs' && o.wellGroup) {\n const members = groups.get(o.wellGroup) ?? [];\n members.push(o);\n groups.set(o.wellGroup, members);\n continue;\n }\n const model = stairModel(project, o);\n if (o.kind === 'stairs' && (model === 'straight' || model === 'escalator')) {\n const run = flightRun(project, o, flight.rise, model, index);\n const height = level.elevation + (ceiling ? level.height : 0) - flight.from.elevation;\n out.push(headroomBand(run, flight.rise, height, o.width));\n continue;\n }\n const ring = footprint(o)[0];\n // A touch proud of the shaft itself, so the slab does not leave a hairline of itself behind.\n if (ring?.length) out.push(closeRing(ring.map(p => [p[0], p[1]] as Point)));\n }\n for (const members of groups.values()) {\n const rotation = objectRotation(project, members[0]);\n const points = members.flatMap(o => footprint(o)[0]).map(p => rotate(p, -rotation));\n const xs = points.map(p => p[0]),\n ys = points.map(p => p[1]);\n const loX = Math.min(...xs),\n hiX = Math.max(...xs),\n loY = Math.min(...ys),\n hiY = Math.max(...ys);\n out.push(rectangle(rotate([(loX + hiX) / 2, (loY + hiY) / 2], rotation), hiX - loX, hiY - loY, rotation));\n }\n return out;\n}\n"],"names":["isVertical","kind","shaftKey","object","servedFloors","project","key","ids","o","_a","f","id","a","b","flights","levels","out","i","reaches","floorId","flightsAt","all","span","PITCH","runFor","rise","pitch","pitchOf","run","MAX_GOING","RISER","HEADROOM","treads","steps","stairModel","first","flightReversed","model","index","flightRun","position","objectPosition","rotation","objectRotation","rad","ux","uy","at","t","length","half","pad","incline","going","headroomBand","height","width","steep","depth","closeRing","rectangle","primaryShafts","lowest","rival","x","arrivesAt","level","departsFrom","passesThrough","shaftVoids","primary","options","ceiling","groups","flight","members","ring","footprint","p","points","rotate","xs","ys","loX","hiX","loY","hiY"],"mappings":"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"}
|