@kerros/schema 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 +25 -2
- package/dist/index.js +3735 -0
- package/dist/index.js.map +1 -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/factory.d.ts +5 -0
- package/dist/types/model/geometry.d.ts +99 -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/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/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/schema/index.d.ts +25 -0
- package/package.json +39 -4
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Point, ProjectDocument, Ring } from './types';
|
|
2
|
+
/** Ignore double-click duplicates; reject a crossing while the last good draft is still editable. */
|
|
3
|
+
export declare function appendAreaPoint(draft: Ring, point: Point): {
|
|
4
|
+
points: Ring;
|
|
5
|
+
error?: string;
|
|
6
|
+
};
|
|
7
|
+
/** The wall/partition tool: divide along the endpoints actually used after welding. */
|
|
8
|
+
export declare function drawBarrier(project: ProjectDocument, floorId: string | null, a: Point, b: Point, kind?: 'wall' | 'fence'): import("./types").Barrier | undefined;
|
|
9
|
+
/** A completed stroke stops chaining when its tip meets an existing boundary, including an
|
|
10
|
+
* earlier segment of the same outline. Test before adding the segment: afterwards every tip
|
|
11
|
+
* belongs to a boundary. Use the model's joining tolerance, not the pointer's snap reach. */
|
|
12
|
+
export declare function endsBoundaryStroke(project: ProjectDocument, floorId: string | null, a: Point, b: Point, kind?: 'wall' | 'fence' | 'boundary'): boolean;
|
|
13
|
+
/** The enclose tool: creating and re-fitting use exactly the same generated outline. */
|
|
14
|
+
export declare function encloseRoom(project: ProjectDocument, floorId: string | null, point: Point): {
|
|
15
|
+
room: import("./types").SiteObject;
|
|
16
|
+
existing: boolean;
|
|
17
|
+
} | null;
|
|
18
|
+
export type GeometryDrag = {
|
|
19
|
+
kind: 'junction';
|
|
20
|
+
id: string;
|
|
21
|
+
point: Point;
|
|
22
|
+
} | {
|
|
23
|
+
kind: 'barrier';
|
|
24
|
+
id: string;
|
|
25
|
+
point: Point;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'ring';
|
|
28
|
+
id: string;
|
|
29
|
+
point: Point;
|
|
30
|
+
ringIndex: number;
|
|
31
|
+
index: number;
|
|
32
|
+
};
|
|
33
|
+
/** Apply one requested drop to a transaction draft. The editor previews snapping alone and calls
|
|
34
|
+
* this once on release; transact either accepts the entire move or leaves the original intact. */
|
|
35
|
+
export declare function applyGeometryDrag(project: ProjectDocument, move: GeometryDrag): void;
|
|
36
|
+
/** Optional constrained authoring operation for hosts and scripted drawing: find a valid position
|
|
37
|
+
* along a requested movement. Interactive editor drags use applyGeometryDrag on release instead. */
|
|
38
|
+
export declare function dragGeometry(project: ProjectDocument, _floorId: string | null, move: GeometryDrag): ProjectDocument;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Barrier, BoundaryUse, Point, ProjectDocument, Ring, SiteObject, VirtualBoundary } from './types';
|
|
2
|
+
export type BoundaryEdge = Barrier | VirtualBoundary;
|
|
3
|
+
export interface BoundaryRegion {
|
|
4
|
+
loops: BoundaryUse[][];
|
|
5
|
+
/** Centreline polygon, including holes. Net footprints also subtract the physical wall bodies. */
|
|
6
|
+
rings: Ring[];
|
|
7
|
+
}
|
|
8
|
+
export declare const boundaryEdges: (p: ProjectDocument) => BoundaryEdge[];
|
|
9
|
+
export declare const followsBoundaries: (o: SiteObject) => boolean;
|
|
10
|
+
/** Keep every reference correct when a physical/virtual edge is split or replaced. */
|
|
11
|
+
export declare function replaceBoundaryUses(p: ProjectDocument, edgeId: string, replacement: BoundaryUse[]): void;
|
|
12
|
+
export declare function boundaryRings(p: ProjectDocument, loops: BoundaryUse[][], floorId: string | null): Ring[];
|
|
13
|
+
/** Planarize a floor: one junction at each intersection, with overlaps represented once.
|
|
14
|
+
* Exact calculated intersections are shared; the input grid is not applied again here. */
|
|
15
|
+
export declare function normalizeBoundaries(p: ProjectDocument, floorId: string | null): void;
|
|
16
|
+
/** Bounded faces of the shared graph. Bridges do not divide a face; nested components form holes. */
|
|
17
|
+
export declare function boundaryRegions(p: ProjectDocument, floorId: string | null): BoundaryRegion[];
|
|
18
|
+
export declare function derivedSpaceRings(p: ProjectDocument, o: SiteObject): Ring[];
|
|
19
|
+
export declare function bindSpaceToRegion(p: ProjectDocument, o: SiteObject, region: BoundaryRegion): void;
|
|
20
|
+
export declare function boundaryRegionAt(p: ProjectDocument, floorId: string | null, point: Point): BoundaryRegion | null;
|
|
21
|
+
export declare function addVirtualBoundary(p: ProjectDocument, floorId: string | null, a: Point, b: Point): string;
|
|
22
|
+
/** The drawing tool intentionally opens any collinear wall span. Ordinary addVirtualBoundary
|
|
23
|
+
* remains non-destructive for generated space outlines and model normalization. */
|
|
24
|
+
export declare function drawVirtualBoundary(p: ProjectDocument, floorId: string | null, a: Point, b: Point): string;
|
|
25
|
+
/** Explicit conversion: retain the drawn shape with shared virtual edges, or adopt its enclosing
|
|
26
|
+
* walls on request. Legacy documents remain independent until this operation is chosen. */
|
|
27
|
+
export declare function connectSpace(p: ProjectDocument, objectId: string, source?: 'outline' | 'walls'): void;
|
|
28
|
+
export declare function disconnectSpace(p: ProjectDocument, objectId: string): void;
|
|
29
|
+
export declare function addBoundaryHole(p: ProjectDocument, o: SiteObject, ring: Ring): void;
|
|
30
|
+
export declare function splitBoundarySpace(p: ProjectDocument, o: SiteObject, a: Point, b: Point, wall?: boolean): string;
|
|
31
|
+
/** Join centreline loops before deriving the net footprint, including the former wall's floor area. */
|
|
32
|
+
export declare function mergeBoundaryGeometry(p: ProjectDocument, keep: SiteObject, absorbed: SiteObject): boolean;
|
|
33
|
+
/** A removed wall leaves the semantic division as a virtual boundary. */
|
|
34
|
+
export declare function preserveBoundary(p: ProjectDocument, edge: BoundaryEdge): void;
|
|
35
|
+
/** Regenerate by explicit loops, subdividing identities only when a new boundary divides a space.
|
|
36
|
+
* No old/new footprint-overlap matching is involved in ordinary wall movements. */
|
|
37
|
+
export declare function refreshBoundarySpaces(p: ProjectDocument, topologyChanged?: Set<string | null>): string[];
|
|
38
|
+
/** One synchronization gate for UI gestures, host mutations, history and persistence. */
|
|
39
|
+
export declare function synchronizeGeometry(before: ProjectDocument, p: ProjectDocument): void;
|
|
40
|
+
/** Validate the explicit topology and its cache. Independent polygons retain their existing rules. */
|
|
41
|
+
export declare function validateSpaceBoundaries(p: ProjectDocument): void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ProjectDocument, type SiteObject } from './types';
|
|
2
|
+
export declare const DEFAULT_RANGE = 12;
|
|
3
|
+
export declare const DEFAULT_FOV = 70;
|
|
4
|
+
/** Ids of the spaces and openings inside `camera`'s cone with an unobstructed line to it.
|
|
5
|
+
*
|
|
6
|
+
* Sightlines stop at walls, with one exception: the opening being tested is itself a hole in the
|
|
7
|
+
* wall it sits on, so that wall cannot be what hides it. */
|
|
8
|
+
export declare function coverageOf(project: ProjectDocument, camera: SiteObject): string[];
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AssetRepository, ProjectDocument } from './types';
|
|
2
|
+
export declare function blobDataUrl(blob: Blob): Promise<string>;
|
|
3
|
+
export declare function exportProject(project: ProjectDocument, assets: AssetRepository): Promise<string>;
|
|
4
|
+
export declare function importProject(json: string, assets: AssetRepository): Promise<ProjectDocument>;
|
|
5
|
+
/** Display semantics: ids and updatedAt untouched, nothing written anywhere — unlike importProject, which rewrites both for import-for-editing. Unsupported embedded entries resolve to undefined at get time rather than throwing. */
|
|
6
|
+
export declare function parseExport(json: string): {
|
|
7
|
+
project: ProjectDocument;
|
|
8
|
+
assets: AssetRepository;
|
|
9
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ObjectKind, Origin, Point, ProjectDocument, SiteObject } from './types';
|
|
2
|
+
/** An empty, valid single-floor project anchored at an explicit EPSG:3067 origin (with optional site bearing). */
|
|
3
|
+
export declare function emptyProject(origin: Origin, name?: string, datum?: string): ProjectDocument;
|
|
4
|
+
/** Create a site object of the given kind at a position, seeded with sensible default dimensions. */
|
|
5
|
+
export declare function createObject(kind: ObjectKind, position: Point, floorId: string | null, name?: string): SiteObject;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { Barrier, Drawing, Origin, Point, ProjectDocument, Ring, SiteObject, Slope, VirtualBoundary } from './types';
|
|
2
|
+
export declare const distance: (a: Point, b: Point) => number;
|
|
3
|
+
export declare const rotate: (p: Point, degrees: number) => Point;
|
|
4
|
+
export declare const geoOrigin: (lngLat: Point, bearing?: number) => Origin;
|
|
5
|
+
/** Shift a site anchor by a distance on the ground, in metres east and north.
|
|
6
|
+
*
|
|
7
|
+
* Nudging a building into place is a thing you do in metres — "half a metre that way" — and the
|
|
8
|
+
* anchor is stored in degrees, where the useful step depends on latitude and differs between the
|
|
9
|
+
* two axes. Deliberately NOT rotated by the site bearing: this moves the site across the world,
|
|
10
|
+
* and the directions that make sense for that are the compass ones, not the plan's own grid. */
|
|
11
|
+
export declare const moveOrigin: (origin: Origin, east: number, north: number) => Origin;
|
|
12
|
+
export declare const toLocal: (lngLat: Point, origin: Origin) => Point;
|
|
13
|
+
export declare const toLngLat: (p: Point, origin: Origin) => Point;
|
|
14
|
+
export declare const add: (a: Point, b: Point) => Point;
|
|
15
|
+
export declare const closeRing: (ring: Ring) => Ring;
|
|
16
|
+
export declare const openRing: (ring: Ring) => Ring;
|
|
17
|
+
export declare const ringArea: (ring: Ring) => number;
|
|
18
|
+
export declare const objectArea: (object: SiteObject) => number;
|
|
19
|
+
export declare const centroid: (ring: Ring) => Point;
|
|
20
|
+
export declare const rectangle: (center: Point, width: number, depth: number, rotation?: number) => Ring;
|
|
21
|
+
/** Do the two open segments cross? Used for line of sight: a wall between two points blocks it.
|
|
22
|
+
* Touching at an endpoint does not count — walls meet at shared junctions, and a sightline grazing
|
|
23
|
+
* such a corner is not obstructed by it. */
|
|
24
|
+
export declare function segmentsCross(a: Point, b: Point, c: Point, d: Point): boolean;
|
|
25
|
+
export declare function segmentProjection(p: Point, a: Point, b: Point): {
|
|
26
|
+
point: Point;
|
|
27
|
+
t: number;
|
|
28
|
+
distance: number;
|
|
29
|
+
length: number;
|
|
30
|
+
};
|
|
31
|
+
/** Elevation of a sloped area at a point: project onto the slope axis and lerp high → low. Points
|
|
32
|
+
* beyond either end clamp, so a footprint wider than its axis keeps flat aprons at both ends. */
|
|
33
|
+
export declare function slopeElevation(slope: Slope, point: Point): number;
|
|
34
|
+
/** The ground an object covers. Most areas carry an explicit outline; anything placed by size falls
|
|
35
|
+
* back to the rectangle its width, depth and rotation describe. One definition, so the model and the
|
|
36
|
+
* renderer cannot disagree about where a thing is. */
|
|
37
|
+
export declare const footprint: (o: SiteObject) => Ring[];
|
|
38
|
+
export declare function barrierEnds(project: ProjectDocument, barrier: Barrier | VirtualBoundary): [Point, Point];
|
|
39
|
+
export declare function objectPosition(project: ProjectDocument, object: SiteObject): Point;
|
|
40
|
+
export declare function objectRotation(project: ProjectDocument, object: SiteObject): number;
|
|
41
|
+
export declare function pointInRing(point: Point, ring: Ring): boolean;
|
|
42
|
+
/** Whether segments a–b and c–d cross or touch, endpoints included. */
|
|
43
|
+
export declare function intersects(a: Point, b: Point, c: Point, d: Point): boolean;
|
|
44
|
+
export declare function containedBy(child: Ring[], parent: Ring[]): boolean;
|
|
45
|
+
export declare function snapPoint(project: ProjectDocument, floorId: string | null, point: Point, tolerance: number, previous?: Point, grid?: boolean,
|
|
46
|
+
/** Direction the segment from `previous` is held to, with its 15° multiples. Default 0 is the site
|
|
47
|
+
* grid; pass `referenceAxis(...).angle` to hold to the building or to a neighbouring wall instead. */
|
|
48
|
+
axis?: number): {
|
|
49
|
+
point: Point;
|
|
50
|
+
label: string;
|
|
51
|
+
};
|
|
52
|
+
export declare function holdAngle(previous: Point, point: Point, axis: number, tolerance: number): {
|
|
53
|
+
point: Point;
|
|
54
|
+
degrees: number;
|
|
55
|
+
quarter: number;
|
|
56
|
+
} | null;
|
|
57
|
+
/** One centimetre is the model's degeneracy guard, independent of the drawing grid and zoom.
|
|
58
|
+
* Short returns, jambs and staggered junctions are real geometry; their screen handles spread
|
|
59
|
+
* apart when necessary so they remain editable. */
|
|
60
|
+
export declare const MIN_SEGMENT = 0.01;
|
|
61
|
+
/** An opening needs a valid wall AND enough length for its actual width. Kept as an exported
|
|
62
|
+
* base limit for callers; there is no additional one-metre restriction on narrow doors/windows. */
|
|
63
|
+
export declare const OPENING_MIN_SEGMENT = 0.01;
|
|
64
|
+
/** Automatic joins only absorb sub-millimetre coordinate noise. Interactive snapping has its
|
|
65
|
+
* own screen-based reach, so a deliberate centimetre-scale feature survives with snapping off. */
|
|
66
|
+
/** Weld a connection near the end of a wall to that end. The snap preview and the actual
|
|
67
|
+
* junction use the same rule, so an ordinary T-junction cannot leave an uneditable stub. */
|
|
68
|
+
export declare function barrierJoinPoint(project: ProjectDocument, point: Point, floorId: string | null): Point;
|
|
69
|
+
/** A preview can check a connection without partially splitting any walls. */
|
|
70
|
+
export declare function junctionIssue(project: ProjectDocument, point: Point, floorId: string | null): string | null;
|
|
71
|
+
/** Preflight the complete stroke, including openings crossed between its endpoints. */
|
|
72
|
+
export declare function barrierStrokeIssue(project: ProjectDocument, a: Point, b: Point, floorId: string | null): string | null;
|
|
73
|
+
export declare function joinAt(project: ProjectDocument, point: Point, floorId: string | null): string;
|
|
74
|
+
/** Take a wall out of the document, with everything that only existed because it was there.
|
|
75
|
+
*
|
|
76
|
+
* A wall is never alone: doors and windows hang in it, junctions exist to hold its ends, and route
|
|
77
|
+
* nodes may be bound to those openings. Removing the barrier alone leaves each of those pointing at
|
|
78
|
+
* something that is gone, and the document refuses the whole edit — so the delete appears to do
|
|
79
|
+
* nothing at all, which is worse than either outcome. Everything that removes a wall goes through
|
|
80
|
+
* here so that cannot happen in one place and not another.
|
|
81
|
+
*
|
|
82
|
+
* A junction survives if any other wall still ends there; the ontology is pruned by the caller,
|
|
83
|
+
* which knows whether spaces are also being merged. */
|
|
84
|
+
export declare function removeBarrier(project: ProjectDocument, barrierId: string): void;
|
|
85
|
+
export declare function addBarrier(project: ProjectDocument, a: Point, b: Point, floorId: string | null, kind: 'wall' | 'fence'): Barrier | undefined;
|
|
86
|
+
export declare function splitRoom(project: ProjectDocument, roomId: string, pa: Point, pb: Point,
|
|
87
|
+
/** Build the partition wall along the cut. False when a wall already exists there — splitting a
|
|
88
|
+
* space *because* someone drew a wall must not draw a second one on top of it. */
|
|
89
|
+
wall?: boolean): string;
|
|
90
|
+
/** Delete a floor and everything anchored to it. The cascade is the whole job: a floor owns junctions,
|
|
91
|
+
* barriers, objects and drawings, and those are in turn referenced from elsewhere — openings hang off
|
|
92
|
+
* barriers, cameras watch objects, lifts serve floors, nested zones name a parent, the nav graph binds
|
|
93
|
+
* both, a building may roof this level, and the document may open on it. Anything missed here surfaces
|
|
94
|
+
* later as a validation failure on load, so it is all handled up front.
|
|
95
|
+
* Refuses to remove the last floor: a project must always have one. Returns the ids that were removed. */
|
|
96
|
+
export declare function removeFloor(project: ProjectDocument, floorId: string): string[];
|
|
97
|
+
export declare function duplicateFloor(project: ProjectDocument, floorId: string): string;
|
|
98
|
+
export declare function alignDrawing(imagePoints: [Point, Point], mapPoints: [Point, Point]): Pick<Drawing, 'origin' | 'scale' | 'rotation'>;
|
|
99
|
+
export declare const drawingCorners: (d: Drawing) => Point[];
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type Point, type Portal, type ProjectDocument, type SiteObject } from './types';
|
|
2
|
+
export { TRAVERSABLE } from './passages';
|
|
3
|
+
/** Infer the portals a plan already describes: for each opening, the space on either side of the wall
|
|
4
|
+
* it sits in. This is the move software without a plan cannot make — it has to be told which
|
|
5
|
+
* doors bound an area, because none of them has a floor plan to ask.
|
|
6
|
+
*
|
|
7
|
+
* Openings whose sides cannot be resolved are skipped rather than guessed at: an unbounded or
|
|
8
|
+
* open-plan side is a real modelling gap, and inventing a portal there would hide it. */
|
|
9
|
+
export declare function inferPortals(project: ProjectDocument): Portal[];
|
|
10
|
+
/** Infer the portals that have no door in them: two spaces that share a boundary with nothing built
|
|
11
|
+
* across it. Open-plan floors are made of these — a department opening onto a gallery, a lift car
|
|
12
|
+
* onto its lobby — and a model that only knows about door objects cannot see any of them.
|
|
13
|
+
*
|
|
14
|
+
* Shared virtual edges give exact connectivity. For independent outlines, walk the outline and
|
|
15
|
+
* accumulate stretches that can step across into a neighbour without crossing a wall. */
|
|
16
|
+
export declare function inferOpenBoundaries(project: ProjectDocument): Portal[];
|
|
17
|
+
/** Re-read the portals from the plan, keeping what a person has decided.
|
|
18
|
+
*
|
|
19
|
+
* Hand-authored portals (any id inference would not mint) survive wholesale. On re-inferred ones the
|
|
20
|
+
* geometry is the plan's to change — which spaces, through which opening — but the semantic fields a
|
|
21
|
+
* person may have set (`passage`, `attests`, `name`, `metadata`) are carried onto the replacement:
|
|
22
|
+
* sealing a doorway or marking it monitored must survive the plan being redrawn around it. A portal
|
|
23
|
+
* the plan no longer describes goes, and its edits go with it — they were about a way through that
|
|
24
|
+
* no longer exists. */
|
|
25
|
+
export declare function refreshPortals(project: ProjectDocument): number;
|
|
26
|
+
/** Spaces the segment a→b cuts clean across: it enters and leaves, so the space really is in two
|
|
27
|
+
* pieces. Touching a corner or stopping halfway inside does not divide anything. */
|
|
28
|
+
export declare function spacesDividedBy(project: ProjectDocument, floorId: string | null, a: Point, b: Point): SiteObject[];
|
|
29
|
+
/** Split every space a new wall cuts across, so the plan cannot claim two walled-apart halves are one
|
|
30
|
+
* place. Returns the ids of the pieces created.
|
|
31
|
+
*
|
|
32
|
+
* The wall is assumed to exist already — this is called *because* one was drawn — so no partition is
|
|
33
|
+
* built. splitRoom would otherwise lay a second wall along the first.
|
|
34
|
+
*
|
|
35
|
+
* Splitting is safe in a way that joining is not: one space becomes two and both inherit everything,
|
|
36
|
+
* where merging two spaces would have to destroy one identity — its name, its feed binding, whichever
|
|
37
|
+
* zones it had joined. That asymmetry is why this happens automatically and merging never will. */
|
|
38
|
+
export declare function divideSpaces(project: ProjectDocument, floorId: string | null, a: Point, b: Point): string[];
|
|
39
|
+
/** Spaces that a wall was the only thing keeping apart: remove it and they are one room again.
|
|
40
|
+
*
|
|
41
|
+
* Answered before the wall goes, because afterwards the evidence is gone. Two spaces qualify when
|
|
42
|
+
* they share a boundary that this barrier runs along, and nothing else is built across it. */
|
|
43
|
+
export declare function spacesRejoinedBy(project: ProjectDocument, barrierId: string): [SiteObject, SiteObject] | null;
|
|
44
|
+
/** Merge `absorbed` into `keep`, uniting their footprints. The survivor keeps its own name, colour and
|
|
45
|
+
* bindings; that is the whole reason this is never automatic — something is always discarded. */
|
|
46
|
+
export declare function mergeSpaces(project: ProjectDocument, keepId: string, absorbedId: string): boolean;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ProjectDocument } from './types';
|
|
2
|
+
export interface StatusMetrics {
|
|
3
|
+
occupancy?: number;
|
|
4
|
+
capacity?: number;
|
|
5
|
+
presence?: boolean;
|
|
6
|
+
co2?: number;
|
|
7
|
+
lux?: number;
|
|
8
|
+
temperature?: number;
|
|
9
|
+
}
|
|
10
|
+
/** Severity the viewer colours by, most-to-least urgent. Domain-neutral: `critical` is whatever the
|
|
11
|
+
* host deems an urgent condition (a security alarm, an offline sensor, an over-capacity space, …). */
|
|
12
|
+
export type StatusTone = 'critical' | 'warning' | 'normal' | 'unknown';
|
|
13
|
+
export interface StatusReading {
|
|
14
|
+
feedId: string;
|
|
15
|
+
/** How the viewer colours the bound object. */
|
|
16
|
+
tone: StatusTone;
|
|
17
|
+
/** Human-readable status text the viewer shows (host-authored). */
|
|
18
|
+
label: string;
|
|
19
|
+
/** Epoch-ms of the reading. Enables the viewer's staleness warning; omit to opt out. */
|
|
20
|
+
timestamp?: number;
|
|
21
|
+
/** Generic space/environment telemetry the viewer renders (occupancy rollups, etc.). */
|
|
22
|
+
metrics?: StatusMetrics;
|
|
23
|
+
/** For a door, gate or turnstile: whether the leaf is standing open right now. The plan draws it —
|
|
24
|
+
* an open leaf swings, a closed one sits in the frame. Distinct from `tone`, which says whether
|
|
25
|
+
* anyone should be worried: a door can be open and perfectly normal, or closed and in alarm.
|
|
26
|
+
* Omitted means unknown, and an unknown door is drawn with the architectural swing symbol. */
|
|
27
|
+
open?: boolean;
|
|
28
|
+
/** For a lift: the floor its car is standing at right now, as a floor id. The plan draws the car
|
|
29
|
+
* there and slides it when the value changes, which is the difference between a shaft and a lift
|
|
30
|
+
* you can watch. Omitted means unknown, and an unknown car rests at the lowest level it serves —
|
|
31
|
+
* where an idle lift really does wait. Sits beside `open`, which says whether its doors are
|
|
32
|
+
* standing open, for the same reason: both are what the thing is doing, not whether to worry. */
|
|
33
|
+
carFloorId?: string;
|
|
34
|
+
/** Destination while travelling; carFloorId remains the last arrived landing. */
|
|
35
|
+
targetFloorId?: string;
|
|
36
|
+
/** Expected travel duration for the current car movement, in seconds. Hosts can compress demo
|
|
37
|
+
* trips; absent means the renderer's ordinary 1.5 m/s. Arrival readings remain authoritative. */
|
|
38
|
+
carTravelSeconds?: number;
|
|
39
|
+
/** Travel or door movement is in progress; passengers cannot board or leave yet. */
|
|
40
|
+
moving?: boolean;
|
|
41
|
+
/** For an escalator: whether it is running right now. A stopped escalator is a stair, and the
|
|
42
|
+
* plan draws it as one — the steps stand still. Omitted means unknown and draws it running,
|
|
43
|
+
* because an escalator that is working is the ordinary case and a stopped one is the exception
|
|
44
|
+
* worth reporting. Sits beside `open` and `carFloorId` for the same reason: all three say what
|
|
45
|
+
* the thing is doing, not whether anyone should worry, which is `tone`. */
|
|
46
|
+
running?: boolean;
|
|
47
|
+
/** For an escalator that can be reversed: which way it is carrying people at this moment, which a
|
|
48
|
+
* station or a shopping centre flips between the morning and the evening peak. Overrides the
|
|
49
|
+
* object's authored `travel` for as long as the reading stands. Pass readings to findRoute's
|
|
50
|
+
* options to route with the current direction; the stored graph remains unchanged. */
|
|
51
|
+
travel?: 'up' | 'down';
|
|
52
|
+
/** Opaque host state, passed through untouched — the library never reads it. */
|
|
53
|
+
details?: Record<string, unknown>;
|
|
54
|
+
}
|
|
55
|
+
/** The live-status contract: a source of StatusReading snapshots keyed to SiteObjects by feedId.
|
|
56
|
+
* Deliberately minimal — just subscribe. Interactive concerns (commanding something, simulating
|
|
57
|
+
* traffic) are host-specific and belong to the host's own feed and UI, not this contract. */
|
|
58
|
+
export interface StatusFeed {
|
|
59
|
+
subscribe(project: ProjectDocument, listener: (statuses: StatusReading[]) => void): () => void;
|
|
60
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const EXTERIOR_PRESETS: {
|
|
2
|
+
readonly limestone: {
|
|
3
|
+
readonly name: "Limestone";
|
|
4
|
+
readonly material: "stone";
|
|
5
|
+
readonly color: "#e2e4e6";
|
|
6
|
+
};
|
|
7
|
+
readonly brick: {
|
|
8
|
+
readonly name: "Warm brick";
|
|
9
|
+
readonly material: "brick";
|
|
10
|
+
readonly color: "#9b705b";
|
|
11
|
+
};
|
|
12
|
+
readonly darkBrick: {
|
|
13
|
+
readonly name: "Dark brick";
|
|
14
|
+
readonly material: "brick";
|
|
15
|
+
readonly color: "#7a4636";
|
|
16
|
+
};
|
|
17
|
+
readonly plaster: {
|
|
18
|
+
readonly name: "Ivory render";
|
|
19
|
+
readonly material: "plaster";
|
|
20
|
+
readonly color: "#e0ddd4";
|
|
21
|
+
};
|
|
22
|
+
readonly timber: {
|
|
23
|
+
readonly name: "Cedar cladding";
|
|
24
|
+
readonly material: "timber";
|
|
25
|
+
readonly color: "#ab8b68";
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export type ExteriorPreset = keyof typeof EXTERIOR_PRESETS;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import type { Barrier, Building, Floor, NavEdge, NavNode, ObjectKind, Origin, Point, Portal, PortalGroup, ProjectDocument, Ring, SiteObject, Zone } from './types';
|
|
2
|
+
import { type GeometryDrag } from './authoring';
|
|
3
|
+
import { type TransactResult } from './validate';
|
|
4
|
+
/** Every mutation the schema can execute, as data. `kind` names the operation; the rest are its
|
|
5
|
+
* arguments, exactly as the underlying authoring function takes them. */
|
|
6
|
+
export type Mutation = {
|
|
7
|
+
kind: 'addBuilding';
|
|
8
|
+
building: Building;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'patchBuilding';
|
|
11
|
+
buildingId: string;
|
|
12
|
+
set: Partial<Omit<Building, 'id'>>;
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'addFloor';
|
|
15
|
+
floor: Floor;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'patchFloor';
|
|
18
|
+
floorId: string;
|
|
19
|
+
set: Partial<Omit<Floor, 'id' | 'buildingId'>>;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'removeFloor';
|
|
22
|
+
floorId: string;
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'duplicateFloor';
|
|
25
|
+
floorId: string;
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'patchProject';
|
|
28
|
+
set: Partial<Pick<ProjectDocument, 'name' | 'description' | 'datum' | 'initialFloorId'>>;
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'patchBarrier';
|
|
31
|
+
barrierId: string;
|
|
32
|
+
set: Partial<Omit<Barrier, 'id' | 'startId' | 'endId' | 'floorId'>>;
|
|
33
|
+
} | {
|
|
34
|
+
kind: 'removeBarrier';
|
|
35
|
+
barrierId: string;
|
|
36
|
+
} | {
|
|
37
|
+
kind: 'moveGeometry';
|
|
38
|
+
move: GeometryDrag;
|
|
39
|
+
} | {
|
|
40
|
+
kind: 'drawBoundary';
|
|
41
|
+
a: Point;
|
|
42
|
+
b: Point;
|
|
43
|
+
floorId: string | null;
|
|
44
|
+
} | {
|
|
45
|
+
kind: 'drawBarrier';
|
|
46
|
+
a: Point;
|
|
47
|
+
b: Point;
|
|
48
|
+
floorId: string | null;
|
|
49
|
+
barrierKind?: 'wall' | 'fence';
|
|
50
|
+
} | {
|
|
51
|
+
kind: 'encloseRoom';
|
|
52
|
+
floorId: string | null;
|
|
53
|
+
point: Point;
|
|
54
|
+
name?: string;
|
|
55
|
+
} | {
|
|
56
|
+
kind: 'addHole';
|
|
57
|
+
objectId: string;
|
|
58
|
+
ring: Ring;
|
|
59
|
+
} | {
|
|
60
|
+
kind: 'patchZone';
|
|
61
|
+
zoneId: string;
|
|
62
|
+
set: Partial<Pick<Zone, 'name' | 'purpose' | 'connects' | 'metadata'>>;
|
|
63
|
+
} | {
|
|
64
|
+
kind: 'addPortal';
|
|
65
|
+
portal: Omit<Portal, 'id'>;
|
|
66
|
+
} | {
|
|
67
|
+
kind: 'removePortal';
|
|
68
|
+
portalId: string;
|
|
69
|
+
} | {
|
|
70
|
+
kind: 'setNavigation';
|
|
71
|
+
nodes: NavNode[];
|
|
72
|
+
edges: NavEdge[];
|
|
73
|
+
} | {
|
|
74
|
+
kind: 'addZone';
|
|
75
|
+
name: string;
|
|
76
|
+
spaceIds: string[];
|
|
77
|
+
purpose?: string;
|
|
78
|
+
} | {
|
|
79
|
+
kind: 'removeZone';
|
|
80
|
+
zoneId: string;
|
|
81
|
+
} | {
|
|
82
|
+
kind: 'setZoneMembers';
|
|
83
|
+
zoneId: string;
|
|
84
|
+
spaceIds: string[];
|
|
85
|
+
member: boolean;
|
|
86
|
+
} | {
|
|
87
|
+
kind: 'nestZone';
|
|
88
|
+
parentId: string;
|
|
89
|
+
childId: string;
|
|
90
|
+
} | {
|
|
91
|
+
kind: 'addPortalGroup';
|
|
92
|
+
name: string;
|
|
93
|
+
portalIds: string[];
|
|
94
|
+
} | {
|
|
95
|
+
kind: 'removePortalGroup';
|
|
96
|
+
groupId: string;
|
|
97
|
+
} | {
|
|
98
|
+
kind: 'setPortalGroupMembers';
|
|
99
|
+
groupId: string;
|
|
100
|
+
portalIds: string[];
|
|
101
|
+
member: boolean;
|
|
102
|
+
} | {
|
|
103
|
+
kind: 'patchPortal';
|
|
104
|
+
portalId: string;
|
|
105
|
+
set: Partial<Omit<Portal, 'id' | 'a' | 'b' | 'openingId'>>;
|
|
106
|
+
} | {
|
|
107
|
+
kind: 'patchObject';
|
|
108
|
+
objectId: string;
|
|
109
|
+
set: Partial<Omit<SiteObject, 'id' | 'kind'>>;
|
|
110
|
+
} | {
|
|
111
|
+
kind: 'addObject';
|
|
112
|
+
objectKind: ObjectKind;
|
|
113
|
+
name: string;
|
|
114
|
+
position: Point;
|
|
115
|
+
floorId: string | null;
|
|
116
|
+
set?: Partial<Omit<SiteObject, 'id' | 'kind'>>;
|
|
117
|
+
} | {
|
|
118
|
+
kind: 'removeObjects';
|
|
119
|
+
ids: string[];
|
|
120
|
+
} | {
|
|
121
|
+
kind: 'refreshPortals';
|
|
122
|
+
} | {
|
|
123
|
+
kind: 'pruneOntology';
|
|
124
|
+
} | {
|
|
125
|
+
kind: 'addBarrier';
|
|
126
|
+
a: Point;
|
|
127
|
+
b: Point;
|
|
128
|
+
floorId: string | null;
|
|
129
|
+
barrierKind: 'wall' | 'fence';
|
|
130
|
+
} | {
|
|
131
|
+
kind: 'addBoundary';
|
|
132
|
+
a: Point;
|
|
133
|
+
b: Point;
|
|
134
|
+
floorId: string | null;
|
|
135
|
+
} | {
|
|
136
|
+
kind: 'connectSpace';
|
|
137
|
+
objectId: string;
|
|
138
|
+
source?: 'outline' | 'walls';
|
|
139
|
+
} | {
|
|
140
|
+
kind: 'disconnectSpace';
|
|
141
|
+
objectId: string;
|
|
142
|
+
} | {
|
|
143
|
+
kind: 'splitRoom';
|
|
144
|
+
roomId: string;
|
|
145
|
+
a: Point;
|
|
146
|
+
b: Point;
|
|
147
|
+
wall?: boolean;
|
|
148
|
+
} | {
|
|
149
|
+
kind: 'divideSpaces';
|
|
150
|
+
floorId: string | null;
|
|
151
|
+
a: Point;
|
|
152
|
+
b: Point;
|
|
153
|
+
} | {
|
|
154
|
+
kind: 'mergeSpaces';
|
|
155
|
+
keepId: string;
|
|
156
|
+
absorbedId: string;
|
|
157
|
+
}
|
|
158
|
+
/** Move or turn the whole site. The geometry is untouched — local metres stay what they were; only
|
|
159
|
+
* where the frame is pinned and which way it faces change. A plan carries no heading, so this is
|
|
160
|
+
* how an imported one is squared onto the street it actually stands on. */
|
|
161
|
+
| {
|
|
162
|
+
kind: 'setOrigin';
|
|
163
|
+
origin: Origin;
|
|
164
|
+
};
|
|
165
|
+
/** What a mutation reports back, beyond the new document: the entity it created, or whether it did
|
|
166
|
+
* anything at all. Read it off the result rather than hunting the document for what changed.
|
|
167
|
+
* Entity outcomes are views INTO the frozen result — read them freely, but editing one throws;
|
|
168
|
+
* the next change goes through the next transaction like every other. */
|
|
169
|
+
export type MutationOutcome = Zone | PortalGroup | string[] | string | boolean | number | undefined;
|
|
170
|
+
export type MutationResult = TransactResult & {
|
|
171
|
+
outcomes?: MutationOutcome[];
|
|
172
|
+
};
|
|
173
|
+
/** Execute one mutation atomically. Sugar over `applyMutations` with a single step. */
|
|
174
|
+
export declare const applyMutation: (project: ProjectDocument, mutation: Mutation) => MutationResult;
|
|
175
|
+
/** Execute a sequence of mutations as ONE transaction: all of them apply and the result passes
|
|
176
|
+
* every rule, or none of them do and `error` says why. `outcomes` reports what each step returned
|
|
177
|
+
* (created zones, split-off ids), in order — present only on success. */
|
|
178
|
+
export declare function applyMutations(project: ProjectDocument, mutations: Mutation[]): MutationResult;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { NavEdge, NavEdgeKind, NavNode, Point, ProjectDocument, SiteObject } from './types';
|
|
2
|
+
import type { StatusReading } from './live';
|
|
3
|
+
export declare const ELEVATOR_BASE = 25, ELEVATOR_PER_METRE = 1, STAIR_CLIMB_FACTOR = 5, DOOR_COST = 1.5;
|
|
4
|
+
/** addNavNode welds onto any existing node within this range (mirrors joinAt's junction reuse). */
|
|
5
|
+
export declare const NAV_WELD = 0.25;
|
|
6
|
+
export declare const navNodes: (p: ProjectDocument) => NavNode[];
|
|
7
|
+
export declare const navEdges: (p: ProjectDocument) => NavEdge[];
|
|
8
|
+
/** Find-or-create a route node at a point on a floor, welding onto any node within NAV_WELD. */
|
|
9
|
+
export declare function addNavNode(p: ProjectDocument, floorId: string | null, position: Point, objectId?: string): NavNode;
|
|
10
|
+
/** Connect two nodes, reusing an existing edge of the same kind between the same pair. */
|
|
11
|
+
export declare function addNavEdge(p: ProjectDocument, kind: NavEdgeKind, a: NavNode, b: NavNode, objectId?: string): NavEdge;
|
|
12
|
+
/** Draw a chain of walk edges through the given points, welding onto existing nodes en route. */
|
|
13
|
+
export declare function navPath(p: ProjectDocument, floorId: string | null, points: Point[]): NavNode[];
|
|
14
|
+
/** Thread a vertical connection through an elevator/stairs object: one node per served floor at the object's position. Stairs chain adjacent served floors (you pass every level); an elevator gets an edge per floor pair (a ride is direct, so ELEVATOR_BASE is paid once). Per-floor twin objects share a name (see the demo lifts); pass any one of them and each edge binds the twin on its upper floor. */
|
|
15
|
+
export declare function chainVertical(p: ProjectDocument, object: SiteObject): NavNode[];
|
|
16
|
+
export interface RouteLeg {
|
|
17
|
+
edge: NavEdge;
|
|
18
|
+
from: NavNode;
|
|
19
|
+
to: NavNode;
|
|
20
|
+
cost: number;
|
|
21
|
+
}
|
|
22
|
+
export interface RouteStep {
|
|
23
|
+
kind: 'depart' | 'walk' | 'door' | 'stairs' | 'elevator' | 'arrive';
|
|
24
|
+
text: string;
|
|
25
|
+
floorId: string | null;
|
|
26
|
+
nodeIds: string[];
|
|
27
|
+
objectId?: string;
|
|
28
|
+
distance?: number;
|
|
29
|
+
}
|
|
30
|
+
/** Either end of a route: an object in the document by id, or a point on a floor — where the
|
|
31
|
+
* walker is standing, which is the start most people want and no object describes. */
|
|
32
|
+
export type RouteEnd = string | {
|
|
33
|
+
name?: string;
|
|
34
|
+
floorId: string | null;
|
|
35
|
+
position: Point;
|
|
36
|
+
};
|
|
37
|
+
/** What a route knows about its ends: enough to name them and place them, whether they came from
|
|
38
|
+
* an object or a point. */
|
|
39
|
+
export type RoutePlace = Pick<SiteObject, 'id' | 'kind' | 'name' | 'floorId' | 'position'>;
|
|
40
|
+
/** The id a host uses for "where the walker is" in a picker; findRoute() never sees it. */
|
|
41
|
+
export declare const HERE = "@here";
|
|
42
|
+
export interface Route {
|
|
43
|
+
from: RoutePlace;
|
|
44
|
+
to: RoutePlace;
|
|
45
|
+
nodes: NavNode[];
|
|
46
|
+
legs: RouteLeg[];
|
|
47
|
+
steps: RouteStep[];
|
|
48
|
+
distance: number;
|
|
49
|
+
cost: number;
|
|
50
|
+
}
|
|
51
|
+
/** Metres-equivalent cost of traversing an edge; authored weight wins over the derived cost. */
|
|
52
|
+
export declare function edgeCost(p: ProjectDocument, edge: NavEdge, a: NavNode, b: NavNode): number;
|
|
53
|
+
/** Walking metres of an edge (horizontal run, plus the rise on vertical edges) — for display, not costing. */
|
|
54
|
+
export declare function edgeLength(p: ProjectDocument, edge: NavEdge, a: NavNode, b: NavNode): number;
|
|
55
|
+
/** Candidate graph entry points for a room/door/poi: nodes bound to the object at cost 0, else nodes inside an area's outer ring, else the nearest node on the object's floor. Cost is the straight-line approach in metres. */
|
|
56
|
+
export declare function routeAnchors(p: ProjectDocument, object: SiteObject): {
|
|
57
|
+
node: NavNode;
|
|
58
|
+
cost: number;
|
|
59
|
+
}[];
|
|
60
|
+
/** Shortest route between two ends — objects (rooms, doors, POIs, …) or points on a floor — over
|
|
61
|
+
* the authored graph, with human step instructions. Returns null when either end has no anchor or
|
|
62
|
+
* no path exists. */
|
|
63
|
+
export interface RouteOptions {
|
|
64
|
+
statuses?: ReadonlyMap<string, StatusReading>;
|
|
65
|
+
}
|
|
66
|
+
export declare function findRoute(p: ProjectDocument, fromEnd: RouteEnd, toEnd: RouteEnd, options?: RouteOptions): Route | null;
|
|
67
|
+
/** Human name of a level: "Menswear & denim (level 3)" via Floor.code, "outside" for the site level. */
|
|
68
|
+
export declare function floorPhrase(p: ProjectDocument, floorId: string | null): string;
|
|
69
|
+
/** Build the human step list: walking grouped per floor, vertical rides merged per lift/stair name, doors called out — "Exit through Aleksanterinkatu entrance", "Take Lift A to Offices · buying & admin (level 8)". */
|
|
70
|
+
export declare function routeSteps(p: ProjectDocument, from: RoutePlace, to: RoutePlace, path: NavNode[], legs: RouteLeg[]): RouteStep[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Portal, type PortalGroup, type ProjectDocument, type Zone } from './types';
|
|
2
|
+
/** Which way you must cross `portal` to arrive in `spaceId`, or null if you cannot get there that way.
|
|
3
|
+
* Direction is asked, never stored: a door's entry side is relative to the area you mean. */
|
|
4
|
+
export declare function entryInto(portal: Portal, spaceId: string): 'a-to-b' | 'b-to-a' | null;
|
|
5
|
+
/** Every space in `zone`, including those reached through nested zones. */
|
|
6
|
+
export declare const zoneSpaces: (project: ProjectDocument, zone: Zone) => string[];
|
|
7
|
+
/** Portals on the boundary of `zone` — one side in, one side out. These are the ways in, and the
|
|
8
|
+
* thing software without a floor plan to ask makes you enumerate by hand. Derived, so it cannot go
|
|
9
|
+
* stale when the building is redrawn. */
|
|
10
|
+
export declare function perimeter(project: ProjectDocument, zone: Zone): Portal[];
|
|
11
|
+
/** Portals wholly inside `zone`. Not decorative: anything tracking where somebody ended up needs to
|
|
12
|
+
* know that crossing one of these does not change which zone you are in. */
|
|
13
|
+
export declare function captive(project: ProjectDocument, zone: Zone): Portal[];
|
|
14
|
+
/** Create a zone over the given spaces. Ids that are not spaces are dropped rather than rejected —
|
|
15
|
+
* a selection usually contains a door or a fixture alongside the rooms, and losing the whole gesture
|
|
16
|
+
* over that would be tiresome. */
|
|
17
|
+
export declare function addZone(project: ProjectDocument, name: string, spaceIds: string[], purpose?: string): Zone;
|
|
18
|
+
/** Remove a zone, and any nesting reference to it. A dangling childZoneIds entry fails validation. */
|
|
19
|
+
export declare function removeZone(project: ProjectDocument, zoneId: string): void;
|
|
20
|
+
/** Add or remove spaces on a zone. Membership is a set: adding twice is not an error, and neither is
|
|
21
|
+
* removing something that was never there. */
|
|
22
|
+
export declare function setZoneMembers(project: ProjectDocument, zoneId: string, spaceIds: string[], member: boolean): void;
|
|
23
|
+
/** Nest one zone inside another, refusing anything that would make a cycle. Zones form a DAG, and a
|
|
24
|
+
* cycle is the one shape the validator will not accept. */
|
|
25
|
+
export declare function nestZone(project: ProjectDocument, parentId: string, childId: string): boolean;
|
|
26
|
+
/** Create a portal group over the given portals. The escape hatch for sets that are not a zone
|
|
27
|
+
* boundary — "all loading-bay shutters", "every door on the night circuit". Where a set *is* a zone
|
|
28
|
+
* boundary, derive it with `perimeter` instead; a derived list cannot go stale. Ids that are not
|
|
29
|
+
* portals are dropped rather than rejected, mirroring addZone. */
|
|
30
|
+
export declare function addPortalGroup(project: ProjectDocument, name: string, portalIds: string[]): PortalGroup;
|
|
31
|
+
/** Remove a portal group. Nothing references groups, so nothing needs cleaning up after one. */
|
|
32
|
+
export declare function removePortalGroup(project: ProjectDocument, groupId: string): void;
|
|
33
|
+
/** Add or remove portals on a group. Membership is a set, exactly as setZoneMembers. */
|
|
34
|
+
export declare function setPortalGroupMembers(project: ProjectDocument, groupId: string, portalIds: string[], member: boolean): void;
|
|
35
|
+
/** Drop ontology references to things that no longer exist.
|
|
36
|
+
*
|
|
37
|
+
* Every reference here is validated, so a delete that skips this step produces an invalid document
|
|
38
|
+
* and gets refused — from the outside that looks like the delete silently doing nothing, which is
|
|
39
|
+
* the worst way for a bug to present. Call it after removing objects, from every path that can.
|
|
40
|
+
*
|
|
41
|
+
* A portal whose opening is gone goes with it: the door was the way through, and keeping a portal
|
|
42
|
+
* that no longer has one would quietly turn a closed doorway into an open boundary. */
|
|
43
|
+
export declare function pruneOntology(project: ProjectDocument): void;
|