@pascal-app/core 1.0.0-beta.4 → 1.0.0
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 +23 -0
- package/dist/capture/index.d.ts +3 -0
- package/dist/capture/index.d.ts.map +1 -0
- package/dist/capture/index.js +2 -0
- package/dist/capture/schema.d.ts +417 -0
- package/dist/capture/schema.d.ts.map +1 -0
- package/dist/capture/schema.js +348 -0
- package/dist/capture/source.d.ts +60 -0
- package/dist/capture/source.d.ts.map +1 -0
- package/dist/capture/source.js +254 -0
- package/dist/events/bus.d.ts +55 -7
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/hidden-wall-pointer-hold.d.ts +28 -0
- package/dist/events/hidden-wall-pointer-hold.d.ts.map +1 -0
- package/dist/events/hidden-wall-pointer-hold.js +37 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts +12 -0
- package/dist/hooks/spatial-grid/spatial-grid-manager.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-manager.js +39 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts +2785 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid-sync.js +132 -19
- package/dist/hooks/spatial-grid/support-host-patch.d.ts +45 -1
- package/dist/hooks/spatial-grid/support-host-patch.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/support-host-patch.js +171 -6
- package/dist/index.d.ts +15 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -5
- package/dist/lib/asset-storage.d.ts.map +1 -1
- package/dist/lib/asset-storage.js +4 -1
- package/dist/lib/polygon-union.d.ts +4 -0
- package/dist/lib/polygon-union.d.ts.map +1 -0
- package/dist/lib/polygon-union.js +306 -0
- package/dist/lib/roof-overlap.d.ts +32 -0
- package/dist/lib/roof-overlap.d.ts.map +1 -0
- package/dist/lib/roof-overlap.js +72 -0
- package/dist/lib/room-topology-index.d.ts +50 -0
- package/dist/lib/room-topology-index.d.ts.map +1 -0
- package/dist/lib/room-topology-index.js +237 -0
- package/dist/lib/slab-polygon.d.ts +35 -2
- package/dist/lib/slab-polygon.d.ts.map +1 -1
- package/dist/lib/slab-polygon.js +133 -27
- package/dist/lib/space-detection.d.ts +40 -3
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +890 -246
- package/dist/registry/handles.d.ts +24 -3
- package/dist/registry/handles.d.ts.map +1 -1
- package/dist/registry/index.d.ts +3 -3
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/registry/registry.d.ts +29 -6
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +111 -4
- package/dist/registry/scene-api.d.ts +16 -0
- package/dist/registry/scene-api.d.ts.map +1 -1
- package/dist/registry/scene-api.js +49 -1
- package/dist/registry/types.d.ts +261 -13
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry/use-registry-version.d.ts +11 -0
- package/dist/registry/use-registry-version.d.ts.map +1 -0
- package/dist/registry/use-registry-version.js +15 -0
- package/dist/schema/__bench__/node-parsers.bench.d.ts +24 -0
- package/dist/schema/__bench__/node-parsers.bench.d.ts.map +1 -0
- package/dist/schema/__bench__/node-parsers.bench.js +314 -0
- package/dist/schema/__fixtures__/node-fixtures.d.ts +28 -0
- package/dist/schema/__fixtures__/node-fixtures.d.ts.map +1 -0
- package/dist/schema/__fixtures__/node-fixtures.js +125 -0
- package/dist/schema/base.d.ts +1 -1
- package/dist/schema/base.d.ts.map +1 -1
- package/dist/schema/base.js +8 -1
- package/dist/schema/compiled-node-parsers.d.ts +40 -0
- package/dist/schema/compiled-node-parsers.d.ts.map +1 -0
- package/dist/schema/compiled-node-parsers.js +95 -0
- package/dist/schema/index.d.ts +20 -15
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +19 -12
- package/dist/schema/material.d.ts +2 -1
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +1 -0
- package/dist/schema/nodes/automatic-downspout.d.ts +17 -0
- package/dist/schema/nodes/automatic-downspout.d.ts.map +1 -0
- package/dist/schema/nodes/automatic-downspout.js +237 -0
- package/dist/schema/nodes/block.d.ts +107 -0
- package/dist/schema/nodes/block.d.ts.map +1 -0
- package/dist/schema/nodes/block.js +207 -0
- package/dist/schema/nodes/box-vent.d.ts +8 -2
- package/dist/schema/nodes/box-vent.d.ts.map +1 -1
- package/dist/schema/nodes/box-vent.js +2 -0
- package/dist/schema/nodes/building.d.ts +1 -1
- package/dist/schema/nodes/cabinet.d.ts +25 -3
- package/dist/schema/nodes/cabinet.d.ts.map +1 -1
- package/dist/schema/nodes/cabinet.js +26 -5
- package/dist/schema/nodes/ceiling.d.ts +1 -1
- package/dist/schema/nodes/chimney.d.ts +2 -2
- package/dist/schema/nodes/column.d.ts +6 -6
- package/dist/schema/nodes/construction-dimension.d.ts +4 -4
- package/dist/schema/nodes/cupola.d.ts +9 -1
- package/dist/schema/nodes/cupola.d.ts.map +1 -1
- package/dist/schema/nodes/cupola.js +2 -0
- package/dist/schema/nodes/door.d.ts +1 -1
- package/dist/schema/nodes/dormer.d.ts +44 -1
- package/dist/schema/nodes/dormer.d.ts.map +1 -1
- package/dist/schema/nodes/dormer.js +131 -7
- package/dist/schema/nodes/downspout.d.ts +11 -1
- package/dist/schema/nodes/downspout.d.ts.map +1 -1
- package/dist/schema/nodes/downspout.js +24 -0
- package/dist/schema/nodes/duct-fitting.d.ts +18 -1
- package/dist/schema/nodes/duct-fitting.d.ts.map +1 -1
- package/dist/schema/nodes/duct-fitting.js +25 -4
- package/dist/schema/nodes/duct-segment.d.ts +23 -1
- package/dist/schema/nodes/duct-segment.d.ts.map +1 -1
- package/dist/schema/nodes/duct-segment.js +20 -0
- package/dist/schema/nodes/duct-terminal.d.ts +1 -1
- package/dist/schema/nodes/elevator.d.ts +1 -1
- package/dist/schema/nodes/eyebrow-vent.d.ts +8 -2
- package/dist/schema/nodes/eyebrow-vent.d.ts.map +1 -1
- package/dist/schema/nodes/eyebrow-vent.js +2 -0
- package/dist/schema/nodes/fence.d.ts +1 -1
- package/dist/schema/nodes/guide.d.ts +1 -1
- package/dist/schema/nodes/gutter.d.ts +36 -2
- package/dist/schema/nodes/gutter.d.ts.map +1 -1
- package/dist/schema/nodes/gutter.js +308 -1
- package/dist/schema/nodes/hanger-overrides.d.ts +7 -0
- package/dist/schema/nodes/hanger-overrides.d.ts.map +1 -0
- package/dist/schema/nodes/hanger-overrides.js +6 -0
- package/dist/schema/nodes/hvac-equipment.d.ts +1 -1
- package/dist/schema/nodes/item.d.ts +4 -3
- package/dist/schema/nodes/item.d.ts.map +1 -1
- package/dist/schema/nodes/item.js +5 -0
- package/dist/schema/nodes/lean-to-extension.d.ts +212 -0
- package/dist/schema/nodes/lean-to-extension.d.ts.map +1 -0
- package/dist/schema/nodes/lean-to-extension.js +114 -0
- package/dist/schema/nodes/level.d.ts +5 -2
- package/dist/schema/nodes/level.d.ts.map +1 -1
- package/dist/schema/nodes/level.js +9 -0
- package/dist/schema/nodes/lineset.d.ts +1 -1
- package/dist/schema/nodes/liquid-line.d.ts +1 -1
- package/dist/schema/nodes/measurement.d.ts +1 -1
- package/dist/schema/nodes/pipe-fitting.d.ts +9 -1
- package/dist/schema/nodes/pipe-fitting.d.ts.map +1 -1
- package/dist/schema/nodes/pipe-fitting.js +6 -2
- package/dist/schema/nodes/pipe-segment.d.ts +23 -1
- package/dist/schema/nodes/pipe-segment.d.ts.map +1 -1
- package/dist/schema/nodes/pipe-segment.js +20 -0
- package/dist/schema/nodes/pipe-trap.d.ts +1 -1
- package/dist/schema/nodes/ridge-vent.d.ts +1 -1
- package/dist/schema/nodes/ridge-vent.d.ts.map +1 -1
- package/dist/schema/nodes/ridge-vent.js +4 -1
- package/dist/schema/nodes/roof-segment-shape.d.ts +4 -0
- package/dist/schema/nodes/roof-segment-shape.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment-shape.js +64 -1
- package/dist/schema/nodes/roof-segment-walls.d.ts +3 -1
- package/dist/schema/nodes/roof-segment-walls.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment-walls.js +1 -1
- package/dist/schema/nodes/roof-segment.d.ts +41 -1
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +79 -2
- package/dist/schema/nodes/roof.d.ts +22 -1
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +14 -0
- package/dist/schema/nodes/scan.d.ts +19 -2
- package/dist/schema/nodes/scan.d.ts.map +1 -1
- package/dist/schema/nodes/scan.js +11 -1
- package/dist/schema/nodes/shelf.d.ts +2 -2
- package/dist/schema/nodes/site.d.ts +1 -1
- package/dist/schema/nodes/skylight.d.ts +1 -1
- package/dist/schema/nodes/slab.d.ts +1 -1
- package/dist/schema/nodes/solar-panel.d.ts +1 -1
- package/dist/schema/nodes/spawn.d.ts +1 -1
- package/dist/schema/nodes/stair-segment.d.ts +1 -1
- package/dist/schema/nodes/stair.d.ts +1 -1
- package/dist/schema/nodes/structural-grid.d.ts +1 -1
- package/dist/schema/nodes/turbine-vent.d.ts +7 -1
- package/dist/schema/nodes/turbine-vent.d.ts.map +1 -1
- package/dist/schema/nodes/turbine-vent.js +2 -0
- package/dist/schema/nodes/wall.d.ts +4 -4
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +7 -1
- package/dist/schema/nodes/window.d.ts +10 -3
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/nodes/window.js +4 -0
- package/dist/schema/nodes/zone.d.ts +2 -2
- package/dist/schema/nodes/zone.js +1 -1
- package/dist/schema/types.d.ts +485 -109
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +27 -1
- package/dist/services/hosting.d.ts +2 -2
- package/dist/services/hosting.d.ts.map +1 -1
- package/dist/services/hosting.js +4 -4
- package/dist/services/index.d.ts +2 -2
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +2 -2
- package/dist/services/storey.d.ts +3 -10
- package/dist/services/storey.d.ts.map +1 -1
- package/dist/services/storey.js +45 -15
- package/dist/services/system-graph.d.ts +11 -0
- package/dist/services/system-graph.d.ts.map +1 -1
- package/dist/services/system-graph.js +35 -5
- package/dist/store/actions/node-actions.d.ts +15 -4
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +458 -19
- package/dist/store/history-control.d.ts +5 -0
- package/dist/store/history-control.d.ts.map +1 -1
- package/dist/store/history-control.js +68 -6
- package/dist/store/history-invalidation.d.ts +3 -0
- package/dist/store/history-invalidation.d.ts.map +1 -0
- package/dist/store/history-invalidation.js +72 -0
- package/dist/store/scene-hydration.d.ts +5 -0
- package/dist/store/scene-hydration.d.ts.map +1 -0
- package/dist/store/scene-hydration.js +57 -0
- package/dist/store/use-scene.d.ts +3 -0
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +407 -227
- package/dist/systems/elevator/elevator-service.d.ts.map +1 -1
- package/dist/systems/elevator/elevator-service.js +16 -11
- package/dist/systems/roof/roof-elevation-system.d.ts +3 -0
- package/dist/systems/roof/roof-elevation-system.d.ts.map +1 -0
- package/dist/systems/roof/roof-elevation-system.js +94 -0
- package/dist/systems/roof/roof-elevation.d.ts +4 -0
- package/dist/systems/roof/roof-elevation.d.ts.map +1 -0
- package/dist/systems/roof/roof-elevation.js +95 -0
- package/dist/systems/roof/roof-footprint.d.ts +17 -0
- package/dist/systems/roof/roof-footprint.d.ts.map +1 -0
- package/dist/systems/roof/roof-footprint.js +83 -0
- package/dist/systems/slab/slab-support.d.ts +0 -31
- package/dist/systems/slab/slab-support.d.ts.map +1 -1
- package/dist/systems/slab/slab-support.js +26 -4
- package/dist/systems/stair/stair-flight.d.ts +17 -0
- package/dist/systems/stair/stair-flight.d.ts.map +1 -0
- package/dist/systems/stair/stair-flight.js +27 -0
- package/dist/systems/stair/stair-opening-preview.d.ts +1556 -1328
- package/dist/systems/stair/stair-opening-preview.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-system.d.ts +1 -0
- package/dist/systems/stair/stair-opening-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-opening-system.js +93 -96
- package/dist/systems/stair/stair-rise.js +21 -20
- package/dist/systems/wall/wall-curve.d.ts +1 -0
- package/dist/systems/wall/wall-curve.d.ts.map +1 -1
- package/dist/systems/wall/wall-curve.js +73 -0
- package/dist/systems/wall/wall-footprint.d.ts +1 -0
- package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
- package/dist/systems/wall/wall-footprint.js +1 -0
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +88 -9
- package/dist/systems/wall/wall-topology.d.ts +53 -0
- package/dist/systems/wall/wall-topology.d.ts.map +1 -0
- package/dist/systems/wall/wall-topology.js +414 -0
- package/dist/utils/clone-scene-graph.d.ts +2 -0
- package/dist/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +39 -2
- package/dist/utils/scene-migrations.d.ts +1 -0
- package/dist/utils/scene-migrations.d.ts.map +1 -1
- package/dist/utils/scene-migrations.js +1 -0
- package/dist/utils/vertical-scene-migration.d.ts +13 -0
- package/dist/utils/vertical-scene-migration.d.ts.map +1 -0
- package/dist/utils/vertical-scene-migration.js +179 -0
- package/dist/validation/validate-build-json.d.ts +3 -0
- package/dist/validation/validate-build-json.d.ts.map +1 -1
- package/dist/validation/validate-build-json.js +49 -2
- package/package.json +10 -3
- package/dist/systems/elevator/elevator-runtime-system.d.ts +0 -2
- package/dist/systems/elevator/elevator-runtime-system.d.ts.map +0 -1
- package/dist/systems/elevator/elevator-runtime-system.js +0 -8
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { GROUND_SUPPORT_ID } from '../hooks/spatial-grid/support-host-id';
|
|
2
|
+
import { deriveLegacyLevelHeight } from '../services/level-height';
|
|
3
|
+
import { getCeilingClampBound } from '../services/storey';
|
|
4
|
+
import { computeWallSlabSupport } from '../systems/slab/slab-support';
|
|
5
|
+
import { DEFAULT_WALL_HEIGHT } from '../systems/wall/wall-footprint';
|
|
6
|
+
function getFiniteNumber(value, fallback) {
|
|
7
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
8
|
+
}
|
|
9
|
+
function getStringArray(value) {
|
|
10
|
+
return Array.isArray(value)
|
|
11
|
+
? value.filter((entry) => typeof entry === 'string')
|
|
12
|
+
: [];
|
|
13
|
+
}
|
|
14
|
+
// Walls whose top lands within this of the storey plane become plane-bound;
|
|
15
|
+
// ceilings whose stored height lands within this of their clamp bound become
|
|
16
|
+
// follows-mode. The strict comparison preserves intentional 0.20-short walls.
|
|
17
|
+
const PLANE_BOUND_EPSILON = 0.2;
|
|
18
|
+
// A ground-pinned wall counts as buried only when the elected slab's occupied
|
|
19
|
+
// interval strictly straddles the pinned base. The tolerance absorbs float
|
|
20
|
+
// drift in stamped offsets without capturing a slab that merely touches the
|
|
21
|
+
// base from above.
|
|
22
|
+
const BURIED_PIN_EPSILON = 1e-3;
|
|
23
|
+
/**
|
|
24
|
+
* Applies the vertical-model load migration to serialized scene nodes.
|
|
25
|
+
*
|
|
26
|
+
* This must remain pure, idempotent, and server-safe: the editor loader and
|
|
27
|
+
* hosted scene authority both call it so they compare and persist the same
|
|
28
|
+
* canonical fields during collaboration.
|
|
29
|
+
*/
|
|
30
|
+
export function migrateVerticalSceneNodes(sourceNodes) {
|
|
31
|
+
const nodes = { ...sourceNodes };
|
|
32
|
+
let changed = false;
|
|
33
|
+
const replaceNode = (id, node) => {
|
|
34
|
+
nodes[id] = node;
|
|
35
|
+
changed = true;
|
|
36
|
+
};
|
|
37
|
+
// A level without `height` marks a scene saved before the vertical model
|
|
38
|
+
// landed. Compute the gate before mutating anything so stair and ceiling
|
|
39
|
+
// intent on already-migrated scenes is never reclassified.
|
|
40
|
+
const isLegacyScene = Object.values(nodes).some((node) => node?.type === 'level' && !('height' in node));
|
|
41
|
+
// Ordinals are semantic and compact independently within each building.
|
|
42
|
+
const buildingNodes = Object.values(nodes).filter((node) => node?.type === 'building');
|
|
43
|
+
const levelsByBuilding = new Map();
|
|
44
|
+
for (const [id, node] of Object.entries(nodes)) {
|
|
45
|
+
if (node?.type !== 'level')
|
|
46
|
+
continue;
|
|
47
|
+
const buildingId = buildingNodes.find((building) => building.id === node.parentId)?.id ??
|
|
48
|
+
buildingNodes.find((building) => getStringArray(building.children).includes(id))?.id ??
|
|
49
|
+
null;
|
|
50
|
+
const bucket = levelsByBuilding.get(buildingId) ?? [];
|
|
51
|
+
bucket.push({ id, ordinal: getFiniteNumber(node.level, 0) });
|
|
52
|
+
levelsByBuilding.set(buildingId, bucket);
|
|
53
|
+
}
|
|
54
|
+
for (const bucket of levelsByBuilding.values()) {
|
|
55
|
+
const sorted = [...bucket].sort((a, b) => a.ordinal - b.ordinal);
|
|
56
|
+
const negativeCount = sorted.filter((entry) => entry.ordinal < 0).length;
|
|
57
|
+
sorted.forEach((entry, index) => {
|
|
58
|
+
const nextOrdinal = index - negativeCount;
|
|
59
|
+
const current = nodes[entry.id];
|
|
60
|
+
if (current.level !== nextOrdinal) {
|
|
61
|
+
replaceNode(entry.id, { ...current, level: nextOrdinal });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// Materialize exact legacy storey planes before classifying wall tops.
|
|
66
|
+
const legacyLevelIds = Object.entries(nodes)
|
|
67
|
+
.filter(([, node]) => node?.type === 'level' && !('height' in node))
|
|
68
|
+
.map(([id]) => id);
|
|
69
|
+
const derivedHeights = new Map();
|
|
70
|
+
for (const levelId of legacyLevelIds) {
|
|
71
|
+
derivedHeights.set(levelId, deriveLegacyLevelHeight(levelId, nodes));
|
|
72
|
+
}
|
|
73
|
+
for (const levelId of legacyLevelIds) {
|
|
74
|
+
const plane = derivedHeights.get(levelId);
|
|
75
|
+
const level = nodes[levelId];
|
|
76
|
+
replaceNode(levelId, { ...level, height: plane });
|
|
77
|
+
const children = getStringArray(level.children)
|
|
78
|
+
.map((childId) => nodes[childId])
|
|
79
|
+
.filter((child) => child !== undefined);
|
|
80
|
+
const slabs = children.filter((child) => child.type === 'slab');
|
|
81
|
+
const walls = children.filter((child) => child.type === 'wall');
|
|
82
|
+
for (const wall of walls) {
|
|
83
|
+
const electedBase = computeWallSlabSupport({
|
|
84
|
+
start: wall.start,
|
|
85
|
+
end: wall.end,
|
|
86
|
+
curveOffset: wall.curveOffset,
|
|
87
|
+
thickness: wall.thickness,
|
|
88
|
+
}, slabs, walls).elevation;
|
|
89
|
+
const effectiveHeight = wall.height ?? DEFAULT_WALL_HEIGHT;
|
|
90
|
+
const top = Math.max(0, electedBase) + effectiveHeight;
|
|
91
|
+
if (Math.abs(plane - top) < PLANE_BOUND_EPSILON) {
|
|
92
|
+
if ('height' in wall) {
|
|
93
|
+
const { height: _height, ...planeBound } = wall;
|
|
94
|
+
replaceNode(wall.id, planeBound);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (wall.height !== effectiveHeight) {
|
|
98
|
+
replaceNode(wall.id, { ...wall, height: effectiveHeight });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (isLegacyScene) {
|
|
103
|
+
for (const [id, node] of Object.entries(nodes)) {
|
|
104
|
+
if (node?.type !== 'stair' || node.totalRise !== 2.5)
|
|
105
|
+
continue;
|
|
106
|
+
const { totalRise: _totalRise, ...derivedRise } = node;
|
|
107
|
+
replaceNode(id, derivedRise);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Preserve the exact occupied interval of legacy slabs.
|
|
111
|
+
for (const [id, node] of Object.entries(nodes)) {
|
|
112
|
+
if (node?.type !== 'slab' || 'thickness' in node)
|
|
113
|
+
continue;
|
|
114
|
+
const elevation = getFiniteNumber(node.elevation, 0.05);
|
|
115
|
+
replaceNode(id, elevation < 0
|
|
116
|
+
? { ...node, thickness: 0.05, recessed: true }
|
|
117
|
+
: { ...node, thickness: elevation });
|
|
118
|
+
}
|
|
119
|
+
if (isLegacyScene) {
|
|
120
|
+
for (const [id, node] of Object.entries(nodes)) {
|
|
121
|
+
if (node?.type !== 'ceiling' || !('height' in node))
|
|
122
|
+
continue;
|
|
123
|
+
const dropHeight = () => {
|
|
124
|
+
const { height: _height, ...follows } = node;
|
|
125
|
+
replaceNode(id, follows);
|
|
126
|
+
};
|
|
127
|
+
if (node.autoFromWalls === true) {
|
|
128
|
+
dropHeight();
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (typeof node.parentId !== 'string')
|
|
132
|
+
continue;
|
|
133
|
+
const bound = getCeilingClampBound(node.parentId, nodes, Array.isArray(node.polygon) ? node.polygon : []);
|
|
134
|
+
const stored = getFiniteNumber(node.height, Number.NaN);
|
|
135
|
+
if (Number.isFinite(bound) && Math.abs(stored - bound) < PLANE_BOUND_EPSILON) {
|
|
136
|
+
dropHeight();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Terrain-sculpt-era drafting stamped `supportSlabId: 'ground'` onto walls
|
|
141
|
+
// drawn in 3D on flat scenes. The pin short-circuits slab election, so a
|
|
142
|
+
// wall whose feet sit inside a floor slab keeps its base at the level floor
|
|
143
|
+
// — buried in the slab, z-fighting its side faces — while the panel shows
|
|
144
|
+
// the base as automatic. Heal the pin only in that buried state: a deck
|
|
145
|
+
// hovering above an intentionally grounded wall must keep its pin (dropping
|
|
146
|
+
// it would lift the wall onto the deck). Scenes with sculpted terrain are
|
|
147
|
+
// skipped wholesale — a ground host is load-bearing there, and the live
|
|
148
|
+
// terrain field isn't visible to this pure pass.
|
|
149
|
+
const hasSculptedTerrain = Object.values(nodes).some((node) => node?.type === 'site' && node.terrain != null);
|
|
150
|
+
if (!hasSculptedTerrain) {
|
|
151
|
+
for (const [id, node] of Object.entries(nodes)) {
|
|
152
|
+
if (node?.type !== 'wall' || node.supportSlabId !== GROUND_SUPPORT_ID)
|
|
153
|
+
continue;
|
|
154
|
+
const levelId = typeof node.parentId === 'string' ? node.parentId : null;
|
|
155
|
+
if (!levelId || nodes[levelId]?.type !== 'level')
|
|
156
|
+
continue;
|
|
157
|
+
const siblings = Object.values(nodes).filter((sibling) => sibling != null && sibling.parentId === levelId);
|
|
158
|
+
const support = computeWallSlabSupport({
|
|
159
|
+
start: node.start,
|
|
160
|
+
end: node.end,
|
|
161
|
+
curveOffset: node.curveOffset,
|
|
162
|
+
thickness: node.thickness,
|
|
163
|
+
}, siblings.filter((sibling) => sibling.type === 'slab'), siblings.filter((sibling) => sibling.type === 'wall'));
|
|
164
|
+
const elected = support.electedSlabId ? nodes[support.electedSlabId] : null;
|
|
165
|
+
if (!elected)
|
|
166
|
+
continue;
|
|
167
|
+
const pinnedBase = getFiniteNumber(node.supportOffset, 0);
|
|
168
|
+
const electedTop = getFiniteNumber(elected.elevation, 0.05);
|
|
169
|
+
const electedBottom = electedTop - getFiniteNumber(elected.thickness, 0.05);
|
|
170
|
+
const buried = electedBottom <= pinnedBase + BURIED_PIN_EPSILON &&
|
|
171
|
+
electedTop > pinnedBase + BURIED_PIN_EPSILON;
|
|
172
|
+
if (!buried)
|
|
173
|
+
continue;
|
|
174
|
+
const { supportSlabId: _host, supportOffset: _offset, ...healed } = node;
|
|
175
|
+
replaceNode(id, healed);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return changed ? { changed, nodes } : { changed, nodes: sourceNodes };
|
|
179
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SceneMaterial } from '../schema/scene-material';
|
|
1
2
|
import { type AnyNodeType } from '../schema/types';
|
|
2
3
|
export type ValidationSeverity = 'error' | 'warning';
|
|
3
4
|
export type ValidationIssue = {
|
|
@@ -18,6 +19,8 @@ export type ParsedBuildJson = {
|
|
|
18
19
|
nodes: Record<string, unknown>;
|
|
19
20
|
rootNodeIds: string[];
|
|
20
21
|
installedPlugins?: string[];
|
|
22
|
+
/** Scene materials referenced by node `slots` (`scene:<id>`). */
|
|
23
|
+
materials?: Record<string, SceneMaterial>;
|
|
21
24
|
};
|
|
22
25
|
export type SchemaIssue = {
|
|
23
26
|
nodeId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-build-json.d.ts","sourceRoot":"","sources":["../../src/validation/validate-build-json.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"validate-build-json.d.ts","sourceRoot":"","sources":["../../src/validation/validate-build-json.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAW,KAAK,WAAW,EAAc,MAAM,iBAAiB,CAAA;AAGvE,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,SAAS,CAAA;AAEpD,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAA;IAC5C,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACpC,WAAW,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;CAC1C,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,eAAe,GAAG,IAAI,CAAA;IAC9B,KAAK,EAAE,UAAU,CAAA;IACjB,MAAM,EAAE,eAAe,EAAE,CAAA;IACzB,QAAQ,EAAE,eAAe,EAAE,CAAA;IAC3B,YAAY,EAAE,WAAW,EAAE,CAAA;IAC3B,gBAAgB,EAAE,MAAM,CAAA;CACzB,CAAA;AA4BD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,uBAAuB,CA4VzE"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { nodeRegistry } from '../registry';
|
|
2
|
-
import {
|
|
2
|
+
import { SceneMaterial } from '../schema/scene-material';
|
|
3
|
+
import { AnyNode, nodeKindOf } from '../schema/types';
|
|
3
4
|
import { healSceneNodes } from '../utils/heal-scene-graph';
|
|
4
|
-
const KNOWN_TYPES = new Set(AnyNode.options.map(
|
|
5
|
+
const KNOWN_TYPES = new Set(AnyNode.options.map(nodeKindOf));
|
|
5
6
|
function isPlainObject(value) {
|
|
6
7
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
8
|
}
|
|
@@ -61,6 +62,7 @@ export function validateBuildJson(input) {
|
|
|
61
62
|
const nodesRaw = input.nodes;
|
|
62
63
|
const rootNodeIdsRaw = input.rootNodeIds;
|
|
63
64
|
const installedPluginsRaw = input.installedPlugins;
|
|
65
|
+
const materialsRaw = input.materials;
|
|
64
66
|
if (!isPlainObject(nodesRaw)) {
|
|
65
67
|
errors.push({
|
|
66
68
|
severity: 'error',
|
|
@@ -102,6 +104,50 @@ export function validateBuildJson(input) {
|
|
|
102
104
|
message: 'Ignored invalid "installedPlugins" — expected an array of plugin IDs.',
|
|
103
105
|
});
|
|
104
106
|
}
|
|
107
|
+
// Scene materials ride along with the graph: nodes reference them by
|
|
108
|
+
// `scene:<id>` slot refs, so dropping the table here silently strips
|
|
109
|
+
// every custom finish from the imported scene. Invalid entries are
|
|
110
|
+
// skipped one by one — a bad material must not take the import down.
|
|
111
|
+
//
|
|
112
|
+
// DELIBERATE: `safeParse().data` NORMALIZES — defaults are injected
|
|
113
|
+
// and unknown keys dropped. That is the opposite of the API boundary
|
|
114
|
+
// (`apiGraphSchema` preserves unknown fields on purpose), and it is
|
|
115
|
+
// chosen here because import feeds the live scene store, which only
|
|
116
|
+
// understands schema-shaped materials; a hand-edited file with a
|
|
117
|
+
// half-formed material should land as something the renderer can
|
|
118
|
+
// draw, not round-trip garbage.
|
|
119
|
+
let materials;
|
|
120
|
+
if (isPlainObject(materialsRaw)) {
|
|
121
|
+
const skippedIds = [];
|
|
122
|
+
const kept = {};
|
|
123
|
+
for (const [id, value] of Object.entries(materialsRaw)) {
|
|
124
|
+
const result = SceneMaterial.safeParse(value);
|
|
125
|
+
if (result.success) {
|
|
126
|
+
kept[id] = result.data;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
skippedIds.push(id);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (Object.keys(kept).length > 0)
|
|
133
|
+
materials = kept;
|
|
134
|
+
if (skippedIds.length > 0) {
|
|
135
|
+
// Name the ids: the audience is hand-edited files, and a count
|
|
136
|
+
// alone leaves nothing to repair by.
|
|
137
|
+
warnings.push({
|
|
138
|
+
severity: 'warning',
|
|
139
|
+
code: 'invalid_materials',
|
|
140
|
+
message: `Ignored ${skippedIds.length} invalid scene material${skippedIds.length === 1 ? '' : 's'}: ${skippedIds.join(', ')}.`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else if (materialsRaw !== undefined) {
|
|
145
|
+
warnings.push({
|
|
146
|
+
severity: 'warning',
|
|
147
|
+
code: 'invalid_materials',
|
|
148
|
+
message: 'Ignored invalid "materials" — expected an object of id → material.',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
105
151
|
if (strippedChildRefs > 0 || droppedWallIds.length > 0) {
|
|
106
152
|
warnings.push({
|
|
107
153
|
severity: 'warning',
|
|
@@ -301,6 +347,7 @@ export function validateBuildJson(input) {
|
|
|
301
347
|
nodes,
|
|
302
348
|
rootNodeIds,
|
|
303
349
|
...(installedPlugins ? { installedPlugins } : {}),
|
|
350
|
+
...(materials ? { materials } : {}),
|
|
304
351
|
}
|
|
305
352
|
: null,
|
|
306
353
|
stats,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pascal-app/core",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Core library for Pascal 3D building editor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -21,6 +21,11 @@
|
|
|
21
21
|
"import": "./dist/utils/scene-migrations.js",
|
|
22
22
|
"default": "./dist/utils/scene-migrations.js"
|
|
23
23
|
},
|
|
24
|
+
"./capture": {
|
|
25
|
+
"types": "./dist/capture/index.d.ts",
|
|
26
|
+
"import": "./dist/capture/index.js",
|
|
27
|
+
"default": "./dist/capture/index.js"
|
|
28
|
+
},
|
|
24
29
|
"./registry": {
|
|
25
30
|
"types": "./dist/registry/index.d.ts",
|
|
26
31
|
"import": "./dist/registry/index.js",
|
|
@@ -66,20 +71,21 @@
|
|
|
66
71
|
"dev": "tsgo --build --watch",
|
|
67
72
|
"test": "bun test src",
|
|
68
73
|
"bench:registry": "bun run src/registry/__bench__/relations-resolver.bench.ts",
|
|
74
|
+
"bench:schema": "bun run src/schema/__bench__/node-parsers.bench.ts",
|
|
69
75
|
"prepublishOnly": "npm run build"
|
|
70
76
|
},
|
|
71
77
|
"peerDependencies": {
|
|
72
78
|
"@react-three/drei": "^10",
|
|
73
79
|
"@react-three/fiber": "^9",
|
|
74
80
|
"react": "^18 || ^19",
|
|
75
|
-
"three": "^0.
|
|
81
|
+
"three": "^0.186"
|
|
76
82
|
},
|
|
77
83
|
"dependencies": {
|
|
78
84
|
"dedent": "^1.7.1",
|
|
79
85
|
"idb-keyval": "^6.2.2",
|
|
80
86
|
"mitt": "^3.0.1",
|
|
81
87
|
"nanoid": "^5.1.6",
|
|
82
|
-
"zod": "
|
|
88
|
+
"zod": ">=4.5.4 <4.6",
|
|
83
89
|
"zundo": "^2.3.0",
|
|
84
90
|
"zustand": "^5"
|
|
85
91
|
},
|
|
@@ -88,6 +94,7 @@
|
|
|
88
94
|
"@types/bun": "^1.3.0",
|
|
89
95
|
"@types/react": "^19.2.2",
|
|
90
96
|
"@types/three": "^0.184.0",
|
|
97
|
+
"fake-indexeddb": "^6.2.5",
|
|
91
98
|
"typescript": "6.0.3"
|
|
92
99
|
},
|
|
93
100
|
"keywords": [
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"elevator-runtime-system.d.ts","sourceRoot":"","sources":["../../../src/systems/elevator/elevator-runtime-system.tsx"],"names":[],"mappings":"AAGA,wBAAgB,qBAAqB,SAMpC"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { useFrame } from '@react-three/fiber';
|
|
2
|
-
import { stepElevatorRuntimes } from './elevator-runtime';
|
|
3
|
-
export function ElevatorRuntimeSystem() {
|
|
4
|
-
useFrame(({ clock }, delta) => {
|
|
5
|
-
stepElevatorRuntimes(clock.getElapsedTime() * 1000, delta);
|
|
6
|
-
}, 2);
|
|
7
|
-
return null;
|
|
8
|
-
}
|