@pascal-app/core 1.0.0-beta.5 → 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-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 +14 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -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/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 +11 -0
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +42 -14
- package/dist/registry/handles.d.ts +24 -3
- package/dist/registry/handles.d.ts.map +1 -1
- package/dist/registry/index.d.ts +3 -4
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +1 -2
- package/dist/registry/registry.d.ts +13 -6
- package/dist/registry/registry.d.ts.map +1 -1
- package/dist/registry/registry.js +37 -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 +226 -13
- package/dist/registry/types.d.ts.map +1 -1
- 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 +19 -15
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +17 -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 +2 -2
- package/dist/schema/nodes/level.d.ts.map +1 -1
- 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 +484 -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 +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -1
- 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 +7 -0
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +429 -17
- 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 +332 -64
- 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/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 +1555 -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.d.ts.map +1 -1
- package/dist/systems/stair/stair-rise.js +20 -21
- 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/utils/clone-scene-graph.d.ts.map +1 -1
- package/dist/utils/clone-scene-graph.js +26 -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
|
@@ -108,6 +108,31 @@ class NodeRegistryImpl {
|
|
|
108
108
|
inspectorExtensionsByKind.clear();
|
|
109
109
|
notifyRegistryChanged();
|
|
110
110
|
}
|
|
111
|
+
// Test-only — captures the registry (definitions + plugin bookkeeping) and
|
|
112
|
+
// returns a restore function. The registry is a module singleton and bun
|
|
113
|
+
// runs a package's test files sequentially in ONE process, so a test that
|
|
114
|
+
// registers a throwaway kind (or `_reset()`s) without restoring leaks that
|
|
115
|
+
// state into every later test FILE — and file order varies by platform
|
|
116
|
+
// (macOS vs CI Linux), which turns the leak into an order-dependent flake.
|
|
117
|
+
// Wrap registry mutations in `const restore = nodeRegistry._snapshot()`
|
|
118
|
+
// + `restore()` in `afterEach`/`finally`.
|
|
119
|
+
_snapshot() {
|
|
120
|
+
const defs = new Map(this.defs);
|
|
121
|
+
const pluginIds = new Map(pluginIdsByKind);
|
|
122
|
+
const extensions = new Map(Array.from(inspectorExtensionsByKind, ([kind, list]) => [kind, [...list]]));
|
|
123
|
+
return () => {
|
|
124
|
+
this.defs.clear();
|
|
125
|
+
for (const [kind, def] of defs)
|
|
126
|
+
this.defs.set(kind, def);
|
|
127
|
+
pluginIdsByKind.clear();
|
|
128
|
+
for (const [kind, id] of pluginIds)
|
|
129
|
+
pluginIdsByKind.set(kind, id);
|
|
130
|
+
inspectorExtensionsByKind.clear();
|
|
131
|
+
for (const [kind, list] of extensions)
|
|
132
|
+
inspectorExtensionsByKind.set(kind, [...list]);
|
|
133
|
+
notifyRegistryChanged();
|
|
134
|
+
};
|
|
135
|
+
}
|
|
111
136
|
}
|
|
112
137
|
export const nodeRegistry = new NodeRegistryImpl();
|
|
113
138
|
export function registerNode(def) {
|
|
@@ -165,12 +190,20 @@ export function getSelectableKinds() {
|
|
|
165
190
|
export function isRegistrySelectable(kind) {
|
|
166
191
|
return nodeRegistry.get(kind)?.capabilities.selectable !== undefined;
|
|
167
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Whether the editor should apply its material-based selection highlight to a
|
|
195
|
+
* kind. Selection highlighting is enabled by default, including for legacy or
|
|
196
|
+
* unregistered kinds; definitions may explicitly opt out.
|
|
197
|
+
*/
|
|
198
|
+
export function isSelectionHighlightEnabled(kind) {
|
|
199
|
+
return nodeRegistry.get(kind)?.capabilities.selectionHighlight !== false;
|
|
200
|
+
}
|
|
168
201
|
/**
|
|
169
202
|
* Kinds whose `def.floorplanScope` matches the requested scope. Used by
|
|
170
|
-
* `FloorplanRegistryLayer` to discover building-scoped kinds
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
203
|
+
* `FloorplanRegistryLayer` to discover building- and site-scoped kinds
|
|
204
|
+
* without hardcoding kind names in the editor layer. `'level'` is the
|
|
205
|
+
* default, so `kindsWithFloorplanScope('level')` includes kinds that
|
|
206
|
+
* didn't set the field at all.
|
|
174
207
|
*/
|
|
175
208
|
export function kindsWithFloorplanScope(scope) {
|
|
176
209
|
const result = [];
|
|
@@ -16,10 +16,26 @@ export type SceneStoreLike = {
|
|
|
16
16
|
node: AnyNode;
|
|
17
17
|
parentId?: AnyNodeId;
|
|
18
18
|
}[]) => void;
|
|
19
|
+
applyNodeChanges?: (changes: {
|
|
20
|
+
create?: {
|
|
21
|
+
node: AnyNode;
|
|
22
|
+
parentId?: AnyNodeId;
|
|
23
|
+
}[];
|
|
24
|
+
update?: {
|
|
25
|
+
id: AnyNodeId;
|
|
26
|
+
data: Partial<AnyNode>;
|
|
27
|
+
}[];
|
|
28
|
+
delete?: AnyNodeId[];
|
|
29
|
+
}) => void;
|
|
19
30
|
updateNode: (id: AnyNodeId, data: Partial<AnyNode>) => void;
|
|
20
31
|
deleteNode: (id: AnyNodeId) => void;
|
|
21
32
|
markDirty: (id: AnyNodeId) => void;
|
|
22
33
|
};
|
|
34
|
+
subscribe?: (listener: (state: {
|
|
35
|
+
nodes: Record<AnyNodeId, AnyNode>;
|
|
36
|
+
}, previous: {
|
|
37
|
+
nodes: Record<AnyNodeId, AnyNode>;
|
|
38
|
+
}) => void) => () => void;
|
|
23
39
|
temporal: {
|
|
24
40
|
getState: () => {
|
|
25
41
|
pause: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scene-api.d.ts","sourceRoot":"","sources":["../../src/registry/scene-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"scene-api.d.ts","sourceRoot":"","sources":["../../src/registry/scene-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAWzD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM;QACd,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;QACjC,WAAW,EAAE,SAAS,EAAE,CAAA;QACxB,UAAU,EAAE,GAAG,CAAC,SAAS,CAAC,CAAA;QAC1B,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,IAAI,CAAA;QACzD,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;SAAE,EAAE,KAAK,IAAI,CAAA;QACtE,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE;YAC3B,MAAM,CAAC,EAAE;gBAAE,IAAI,EAAE,OAAO,CAAC;gBAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;aAAE,EAAE,CAAA;YAClD,MAAM,CAAC,EAAE;gBAAE,EAAE,EAAE,SAAS,CAAC;gBAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;aAAE,EAAE,CAAA;YACpD,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;SACrB,KAAK,IAAI,CAAA;QACV,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;QAC3D,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;QACnC,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;KACnC,CAAA;IACD,SAAS,CAAC,EAAE,CACV,QAAQ,EAAE,CACR,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;KAAE,EAC5C,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;KAAE,KAC5C,IAAI,KACN,MAAM,IAAI,CAAA;IACf,QAAQ,EAAE;QACR,QAAQ,EAAE,MAAM;YAAE,KAAK,EAAE,MAAM,IAAI,CAAC;YAAC,MAAM,EAAE,MAAM,IAAI,CAAA;SAAE,CAAA;KAC1D,CAAA;CACF,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,cAAc,GAAG,QAAQ,CAwI9D"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { pauseSceneHistory, resumeSceneHistory } from '../store/history-control';
|
|
1
|
+
import { activeSceneCommitNodeIds, pauseSceneHistory, resumeSceneHistory, } from '../store/history-control';
|
|
2
2
|
import { collectSubtree, cloneNodesInto as runCloneNodesInto, } from './subtree';
|
|
3
3
|
/**
|
|
4
4
|
* Creates a {@link SceneApi} backed by a store.
|
|
@@ -37,6 +37,54 @@ export function createSceneApi(store) {
|
|
|
37
37
|
store.getState().createNode(node, parentId);
|
|
38
38
|
return node.id;
|
|
39
39
|
},
|
|
40
|
+
createMany(ops) {
|
|
41
|
+
for (const op of ops)
|
|
42
|
+
captureIfNeeded(op.node.id);
|
|
43
|
+
const batch = store.getState().createNodes;
|
|
44
|
+
if (batch)
|
|
45
|
+
batch(ops);
|
|
46
|
+
else
|
|
47
|
+
for (const op of ops)
|
|
48
|
+
this.upsert(op.node, op.parentId);
|
|
49
|
+
},
|
|
50
|
+
applyChanges(changes) {
|
|
51
|
+
for (const op of changes.create ?? [])
|
|
52
|
+
captureIfNeeded(op.node.id);
|
|
53
|
+
for (const op of changes.update ?? [])
|
|
54
|
+
captureIfNeeded(op.id);
|
|
55
|
+
for (const id of changes.delete ?? [])
|
|
56
|
+
captureIfNeeded(id);
|
|
57
|
+
const batch = store.getState().applyNodeChanges;
|
|
58
|
+
if (batch) {
|
|
59
|
+
batch(changes);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
for (const op of changes.create ?? [])
|
|
63
|
+
this.upsert(op.node, op.parentId);
|
|
64
|
+
for (const op of changes.update ?? [])
|
|
65
|
+
this.update(op.id, op.data);
|
|
66
|
+
for (const id of changes.delete ?? [])
|
|
67
|
+
this.delete(id);
|
|
68
|
+
},
|
|
69
|
+
subscribeNodes(listener) {
|
|
70
|
+
return (store.subscribe?.((state, previous) => {
|
|
71
|
+
if (state.nodes === previous.nodes)
|
|
72
|
+
return;
|
|
73
|
+
const scopedIds = activeSceneCommitNodeIds();
|
|
74
|
+
const changedIds = new Set(scopedIds);
|
|
75
|
+
if (!scopedIds) {
|
|
76
|
+
for (const id of Object.keys(state.nodes)) {
|
|
77
|
+
if (state.nodes[id] !== previous.nodes[id])
|
|
78
|
+
changedIds.add(id);
|
|
79
|
+
}
|
|
80
|
+
for (const id of Object.keys(previous.nodes)) {
|
|
81
|
+
if (!(id in state.nodes))
|
|
82
|
+
changedIds.add(id);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
listener(state.nodes, previous.nodes, changedIds);
|
|
86
|
+
}) ?? (() => { }));
|
|
87
|
+
},
|
|
40
88
|
delete(id) {
|
|
41
89
|
captureIfNeeded(id);
|
|
42
90
|
store.getState().deleteNode(id);
|
package/dist/registry/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { ComponentType } from 'react';
|
|
|
2
2
|
import type { AnimationClip, BufferGeometry, Object3D, Ray } from 'three';
|
|
3
3
|
import type { ZodObject, z } from 'zod';
|
|
4
4
|
import type { MaterialSchema, MaterialTarget } from '../schema/material';
|
|
5
|
+
import type { AssetInput, ItemNode } from '../schema/nodes/item';
|
|
5
6
|
import type { MeasurementFeatureReference, MeasurementPoint } from '../schema/nodes/measurement';
|
|
6
7
|
import type { SceneMaterial, SceneMaterialId } from '../schema/scene-material';
|
|
7
8
|
import type { AnyNode, AnyNodeId } from '../schema/types';
|
|
@@ -60,6 +61,9 @@ export type GeometryContext = {
|
|
|
60
61
|
materials?: Record<SceneMaterialId, SceneMaterial>;
|
|
61
62
|
/** Opaque host/plugin context. Core never interprets extension values. */
|
|
62
63
|
extensions?: Readonly<Record<string, unknown>>;
|
|
64
|
+
/** Read-only scene snapshot for pure floor-plan builders that need to
|
|
65
|
+
* inspect cross-kind spatial relationships such as connected ports. */
|
|
66
|
+
sceneNodes?: Readonly<Record<AnyNodeId, AnyNode>>;
|
|
63
67
|
/**
|
|
64
68
|
* Optional view state — only populated for `def.floorplan` builders. The
|
|
65
69
|
* 2D floor-plan layer surfaces selection / hover here so kinds can vary
|
|
@@ -191,6 +195,8 @@ export type DimensionTextPosition = 'above' | 'centered';
|
|
|
191
195
|
export type FloorplanStyle = {
|
|
192
196
|
stroke?: string;
|
|
193
197
|
fill?: string;
|
|
198
|
+
/** Winding rule for compound paths. `evenodd` keeps nested contour rings hollow. */
|
|
199
|
+
fillRule?: 'nonzero' | 'evenodd';
|
|
194
200
|
strokeWidth?: number;
|
|
195
201
|
strokeDasharray?: string;
|
|
196
202
|
opacity?: number;
|
|
@@ -264,6 +270,8 @@ export type ToolHint = {
|
|
|
264
270
|
* so the HUD reflects reality. Omit for always-shown hints.
|
|
265
271
|
*/
|
|
266
272
|
minDraftVertices?: number;
|
|
273
|
+
/** Optional live predicate for hints that only apply in one tool sub-mode. */
|
|
274
|
+
visible?: ToolHintVisibility;
|
|
267
275
|
/**
|
|
268
276
|
* Render this hint as a live mode chip — like the snapping / continuation
|
|
269
277
|
* chips — instead of a static key row: the HUD shows the current value's
|
|
@@ -273,6 +281,12 @@ export type ToolHint = {
|
|
|
273
281
|
*/
|
|
274
282
|
chip?: ToolHintChip;
|
|
275
283
|
};
|
|
284
|
+
export type ToolHintVisibility = {
|
|
285
|
+
/** Subscribe to changes that may alter `value`. */
|
|
286
|
+
subscribe: (onChange: () => void) => () => void;
|
|
287
|
+
/** Whether the helper should render this hint now. */
|
|
288
|
+
value: () => boolean;
|
|
289
|
+
};
|
|
276
290
|
export type ToolHintChip = {
|
|
277
291
|
/** Subscribe to live value changes (Zustand-store-like); returns unsubscribe. */
|
|
278
292
|
subscribe: (onChange: () => void) => () => void;
|
|
@@ -288,6 +302,30 @@ export type ToolHintChip = {
|
|
|
288
302
|
/** Hover tooltip, e.g. 'Placement type — click or press I to cycle'. */
|
|
289
303
|
tooltip?: string;
|
|
290
304
|
};
|
|
305
|
+
export type FloorplanScope = 'level' | 'building' | 'site';
|
|
306
|
+
export type ToolOptionChoice = {
|
|
307
|
+
/** Value token, e.g. 'draw'. */
|
|
308
|
+
value: string;
|
|
309
|
+
/** Button label. Sentence case. */
|
|
310
|
+
label: string;
|
|
311
|
+
/** Helper line shown under the row while this choice is active. */
|
|
312
|
+
description?: string;
|
|
313
|
+
};
|
|
314
|
+
export type ToolOption = {
|
|
315
|
+
/** Stable row id within the kind, e.g. 'footprintSource'. */
|
|
316
|
+
id: string;
|
|
317
|
+
/** Row label. Sentence case, e.g. 'Create from'. */
|
|
318
|
+
label: string;
|
|
319
|
+
choices: readonly ToolOptionChoice[];
|
|
320
|
+
/** Subscribe to live value changes (Zustand-store-like); returns unsubscribe. */
|
|
321
|
+
subscribe: (onChange: () => void) => () => void;
|
|
322
|
+
/** Current value token. */
|
|
323
|
+
value: () => string;
|
|
324
|
+
/** Select a choice. Pure state write — arming the tool is the host's job. */
|
|
325
|
+
set: (value: string) => void;
|
|
326
|
+
/** Optional live predicate — e.g. the roof's 'Create from' hides for conical. */
|
|
327
|
+
visible?: ToolHintVisibility;
|
|
328
|
+
};
|
|
291
329
|
export type FloorplanGeometry = ({
|
|
292
330
|
kind: 'path';
|
|
293
331
|
d: string;
|
|
@@ -460,6 +498,7 @@ export type FloorplanGeometry = ({
|
|
|
460
498
|
| {
|
|
461
499
|
kind: 'midpoint-handle';
|
|
462
500
|
point: FloorplanPoint;
|
|
501
|
+
activation?: 'drag' | 'action';
|
|
463
502
|
affordance: string;
|
|
464
503
|
payload: unknown;
|
|
465
504
|
}
|
|
@@ -707,6 +746,8 @@ export type FloorplanAffordance<N> = {
|
|
|
707
746
|
initialPlanPoint: FloorplanAffordancePoint;
|
|
708
747
|
/** Active editor grid step in meters. */
|
|
709
748
|
gridSnapStep: number;
|
|
749
|
+
/** Injected mutation/read seam for kind-owned affordances. */
|
|
750
|
+
sceneApi?: SceneApi;
|
|
710
751
|
}): FloorplanAffordanceSession;
|
|
711
752
|
};
|
|
712
753
|
export type FloorplanMoveTargetSession = {
|
|
@@ -757,6 +798,7 @@ export type FloorplanMoveTargetSession = {
|
|
|
757
798
|
export type FloorplanMoveTarget<N> = (args: {
|
|
758
799
|
node: N;
|
|
759
800
|
nodes: Record<AnyNodeId, AnyNode>;
|
|
801
|
+
sceneApi?: SceneApi;
|
|
760
802
|
}) => FloorplanMoveTargetSession;
|
|
761
803
|
/**
|
|
762
804
|
* A plugin-contributed section for the floating node inspector card.
|
|
@@ -855,6 +897,13 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
855
897
|
* Kinds outside any distribution system leave this unset.
|
|
856
898
|
*/
|
|
857
899
|
distributionRole?: DistributionRole;
|
|
900
|
+
/** Optional behavior while the kind's click-to-click construction tool is active. */
|
|
901
|
+
drafting?: {
|
|
902
|
+
/** Raycast architectural hosts and emit their semantic surface data with grid events. */
|
|
903
|
+
surfaceQuery?: boolean;
|
|
904
|
+
/** Cancel the in-flight draft before applying an undo or redo history jump. */
|
|
905
|
+
cancelOnHistoryJump?: boolean;
|
|
906
|
+
};
|
|
858
907
|
/**
|
|
859
908
|
* When `distributionRole` is `'fitting'`, controls whether this fitting
|
|
860
909
|
* is dragged as a rigid follower when a connected run endpoint moves.
|
|
@@ -886,6 +935,27 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
886
935
|
dirtyTracking?: boolean;
|
|
887
936
|
/** GLB bake treatment for this kind (default `'static'`). See {@link BakePolicy}. */
|
|
888
937
|
bake?: BakePolicy;
|
|
938
|
+
/**
|
|
939
|
+
* Optional export-only geometry builder. The GLB exporter calls this against
|
|
940
|
+
* persisted scene data and replaces the registered node's cloned subtree
|
|
941
|
+
* with the returned local-space Object3D. The live editor object is never
|
|
942
|
+
* passed to the hook or mutated.
|
|
943
|
+
*
|
|
944
|
+
* Use this when the live geometry is unsuitable for a portable GLB (for
|
|
945
|
+
* example, a procedural NodeMaterial that masks a maximum candidate
|
|
946
|
+
* population on the GPU). The returned tree must be a complete static
|
|
947
|
+
* snapshot for this node and use exporter-supported Three.js materials.
|
|
948
|
+
*/
|
|
949
|
+
bakeGeometry?: BakeGeometryBuilder<z.infer<S>>;
|
|
950
|
+
/**
|
|
951
|
+
* Optional asynchronous export-only geometry builder for textured static artifacts.
|
|
952
|
+
* Export preparation awaits this exactly once in place of {@link bakeGeometry}.
|
|
953
|
+
* Synchronous geometry-only callers continue to use `bakeGeometry`.
|
|
954
|
+
*
|
|
955
|
+
* The returned tree follows the same ownership contract: it is detached,
|
|
956
|
+
* local-space, complete for the node, and owned by the export artifact.
|
|
957
|
+
*/
|
|
958
|
+
bakeGeometryAsync?: BakeGeometryAsyncBuilder<z.infer<S>>;
|
|
889
959
|
/**
|
|
890
960
|
* Renderer for this kind. Optional under the three-checkbox composition
|
|
891
961
|
* model (see `wiki/architecture/node-definitions.md`): when omitted, the
|
|
@@ -979,8 +1049,9 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
979
1049
|
/**
|
|
980
1050
|
* Pure 2D builder for floor-plan rendering. Mirrors `geometry` but emits
|
|
981
1051
|
* plain `FloorplanGeometry` data (SVG-renderable) rather than three.js
|
|
982
|
-
* Object3D.
|
|
983
|
-
*
|
|
1052
|
+
* Object3D. Level- and building-scoped builders emit building-local metres.
|
|
1053
|
+
* Site-scoped builders emit site-local metres; the floor-plan layer projects
|
|
1054
|
+
* their output into the active building's plan coordinates.
|
|
984
1055
|
*
|
|
985
1056
|
* Returns `null` when the kind shouldn't appear in floor plan (e.g. an
|
|
986
1057
|
* invisible utility node, or a kind that's 3D-only). Kinds that need
|
|
@@ -1005,8 +1076,11 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
1005
1076
|
* building). For `'building'`-scoped kinds the layer iterates every
|
|
1006
1077
|
* instance whose parent matches the active level's building, and
|
|
1007
1078
|
* synthesises a `GeometryContext` whose `parent` is the active level.
|
|
1079
|
+
* `'site'` discovers direct children of the active building's Site,
|
|
1080
|
+
* supplies the real Site as `ctx.parent`, and projects site-local output
|
|
1081
|
+
* into the active building's plan coordinates below level architecture.
|
|
1008
1082
|
*/
|
|
1009
|
-
floorplanScope?:
|
|
1083
|
+
floorplanScope?: FloorplanScope;
|
|
1010
1084
|
/**
|
|
1011
1085
|
* 2D drag affordances keyed by the string identifier emitted on
|
|
1012
1086
|
* `endpoint-handle` (and similar interactive floor-plan primitives) via
|
|
@@ -1170,6 +1244,12 @@ export type NodeDefinition<S extends ZodObject<any>> = {
|
|
|
1170
1244
|
* its bespoke helper component instead.
|
|
1171
1245
|
*/
|
|
1172
1246
|
toolHints?: ToolHint[];
|
|
1247
|
+
/**
|
|
1248
|
+
* Pick-one option rows for this kind's build tool, rendered by the shared
|
|
1249
|
+
* `<ToolOptionsPanel>` in whichever sidebar the host mounts it (see
|
|
1250
|
+
* `ToolOption`). E.g. the roof's 'Create from: Draw / Room'.
|
|
1251
|
+
*/
|
|
1252
|
+
toolOptions?: readonly ToolOption[];
|
|
1173
1253
|
/**
|
|
1174
1254
|
* Which snapping profile this kind uses, so the editor's contextual snapping
|
|
1175
1255
|
* HUD + snap math + force-place affordance are node-declared rather than
|
|
@@ -1285,6 +1365,8 @@ export type Presentation = {
|
|
|
1285
1365
|
icon: IconRef;
|
|
1286
1366
|
/** Tool palette section. Defaults to `category` when omitted. */
|
|
1287
1367
|
paletteSection?: 'site' | 'structure' | 'furnish';
|
|
1368
|
+
/** Optional presentation-only subgroup used by palette surfaces. */
|
|
1369
|
+
paletteGroup?: string;
|
|
1288
1370
|
/** Sort key within a palette section; lower numbers come first. */
|
|
1289
1371
|
paletteOrder?: number;
|
|
1290
1372
|
/** Set true for kinds that exist but should NOT appear in the palette
|
|
@@ -1293,6 +1375,9 @@ export type Presentation = {
|
|
|
1293
1375
|
/** Set false when selection is edited directly through in-scene affordances
|
|
1294
1376
|
* and the generic floating action menu would duplicate or conflict with them. */
|
|
1295
1377
|
actionMenu?: boolean;
|
|
1378
|
+
/** Set false to drop the "Find in catalog" action for this kind — for nodes
|
|
1379
|
+
* that are placed through a plugin panel rather than a browsable catalog. */
|
|
1380
|
+
findInCatalog?: boolean;
|
|
1296
1381
|
};
|
|
1297
1382
|
export type IconRef =
|
|
1298
1383
|
/** Iconify identifier, e.g. `lucide:square`. Matches the @iconify-react
|
|
@@ -1352,6 +1437,8 @@ export type BakeReplaceRenderer<N> = {
|
|
|
1352
1437
|
}>;
|
|
1353
1438
|
}>;
|
|
1354
1439
|
};
|
|
1440
|
+
export type BakeGeometryBuilder<N> = (node: N, ctx: GeometryContext) => Object3D;
|
|
1441
|
+
export type BakeGeometryAsyncBuilder<N> = (node: N, ctx: GeometryContext) => Promise<Object3D>;
|
|
1355
1442
|
export type AssetRef = {
|
|
1356
1443
|
id: string;
|
|
1357
1444
|
src: string;
|
|
@@ -1392,10 +1479,18 @@ export type Capabilities = {
|
|
|
1392
1479
|
cuttable?: CuttableConfig;
|
|
1393
1480
|
snappable?: SnappableConfig;
|
|
1394
1481
|
surfaces?: SurfacesConfig;
|
|
1482
|
+
faceHost?: FaceHostCapability<any>;
|
|
1395
1483
|
duplicable?: boolean | DuplicableConfig;
|
|
1396
1484
|
deletable?: boolean;
|
|
1397
1485
|
groupable?: boolean;
|
|
1398
1486
|
selectable?: SelectableConfig;
|
|
1487
|
+
/**
|
|
1488
|
+
* Whether selecting this kind should replace its rendered mesh materials
|
|
1489
|
+
* with the editor's selection tint. Defaults to `true`. Set to `false` for
|
|
1490
|
+
* hidden interaction nodes whose rendered geometry must retain its authored
|
|
1491
|
+
* materials while the node remains selected (for example, paint layers).
|
|
1492
|
+
*/
|
|
1493
|
+
selectionHighlight?: boolean;
|
|
1399
1494
|
interactive?: boolean;
|
|
1400
1495
|
floorPlaced?: FloorPlacedConfig;
|
|
1401
1496
|
/**
|
|
@@ -1771,6 +1866,8 @@ export type CapabilityCtx = {
|
|
|
1771
1866
|
export type MovableConfig = {
|
|
1772
1867
|
axes: ReadonlyArray<'x' | 'y' | 'z'>;
|
|
1773
1868
|
gridSnap?: boolean;
|
|
1869
|
+
/** Allow an ordinary primary-button body drag to enter the move tool. */
|
|
1870
|
+
directDrag?: boolean;
|
|
1774
1871
|
/**
|
|
1775
1872
|
* Pin the dragged node to the cursor (absolute placement) instead of the
|
|
1776
1873
|
* default offset-preserving drag, where the node moves by the cursor's
|
|
@@ -1806,11 +1903,34 @@ export type MovableConfig = {
|
|
|
1806
1903
|
parentFrame?: MovableParentFrame;
|
|
1807
1904
|
/**
|
|
1808
1905
|
* Optional group-move snap for the generic multi-selection translate gizmo.
|
|
1809
|
-
* Returns an adjusted candidate position for this node when the moving
|
|
1810
|
-
* should magnetically settle onto a nearby feature
|
|
1811
|
-
* run snapping flush to a wall while the whole selected kitchen moves as one).
|
|
1906
|
+
* Returns an adjusted candidate position for this node when the moving
|
|
1907
|
+
* group should magnetically settle onto a nearby feature.
|
|
1812
1908
|
*/
|
|
1813
1909
|
groupMoveSnap?: (args: GroupMoveSnapArgs) => [number, number, number] | null;
|
|
1910
|
+
/**
|
|
1911
|
+
* Optional rotation-aware group-move snap. This is additive to the original
|
|
1912
|
+
* `groupMoveSnap` contract so existing v1 plugins remain valid.
|
|
1913
|
+
*/
|
|
1914
|
+
groupMoveSnapPose?: (args: GroupMoveSnapArgs) => GroupMoveSnapResult | null;
|
|
1915
|
+
/**
|
|
1916
|
+
* Optional kind-owned validity check for the final planar drag pose. This
|
|
1917
|
+
* complements `floorPlaced` collision checks for constraints that depend
|
|
1918
|
+
* on other scene geometry, such as a cabinet crossing a wall opening.
|
|
1919
|
+
*/
|
|
1920
|
+
isValidPosition?: (args: {
|
|
1921
|
+
node: AnyNode;
|
|
1922
|
+
position: readonly [number, number, number];
|
|
1923
|
+
rotation: number;
|
|
1924
|
+
levelId: AnyNodeId | null;
|
|
1925
|
+
nodes: Readonly<Record<string, AnyNode>>;
|
|
1926
|
+
}) => boolean;
|
|
1927
|
+
/**
|
|
1928
|
+
* Kind-owned grid resolver for a planar move. Unlike scalar grid snapping,
|
|
1929
|
+
* this receives the complete candidate pose so a kind can snap a visible
|
|
1930
|
+
* footprint edge (including a local bounds offset and rotation) rather than
|
|
1931
|
+
* blindly rounding its stored origin.
|
|
1932
|
+
*/
|
|
1933
|
+
gridSnapPosition?: (args: GridSnapPositionArgs) => [number, number, number];
|
|
1814
1934
|
override?: (ctx: CapabilityCtx) => MovableConfig | null;
|
|
1815
1935
|
};
|
|
1816
1936
|
export type MovableParentFrame = {
|
|
@@ -1837,6 +1957,24 @@ export type MovableParentFrame = {
|
|
|
1837
1957
|
magneticSnap?: (node: AnyNode, parent: AnyNode, local: readonly [number, number, number], nodes: Readonly<Record<string, AnyNode>>) => [number, number, number];
|
|
1838
1958
|
/** Optional snap-line matches for the parent-frame magnetic snap result. */
|
|
1839
1959
|
magneticSnapMatches?: (node: AnyNode, parent: AnyNode, local: readonly [number, number, number], snappedLocal: readonly [number, number, number], nodes: Readonly<Record<string, AnyNode>>) => ParentFrameSnapMatch[];
|
|
1960
|
+
/** Optional kind-owned live patches for derived nodes that follow the move. */
|
|
1961
|
+
previewOverrides?: (args: {
|
|
1962
|
+
node: AnyNode;
|
|
1963
|
+
parent: AnyNode;
|
|
1964
|
+
position: readonly [number, number, number];
|
|
1965
|
+
sceneApi: SceneApi;
|
|
1966
|
+
}) => ReadonlyArray<readonly [AnyNodeId, Partial<AnyNode>]>;
|
|
1967
|
+
/**
|
|
1968
|
+
* Optional live collision check for a child moving in the parent frame.
|
|
1969
|
+
* The generic move tool uses this to colour the drag bounds and reject an
|
|
1970
|
+
* invalid drop; the kind owns the actual domain rule.
|
|
1971
|
+
*/
|
|
1972
|
+
isValidPosition?: (args: {
|
|
1973
|
+
node: AnyNode;
|
|
1974
|
+
parent: AnyNode;
|
|
1975
|
+
position: readonly [number, number, number];
|
|
1976
|
+
nodes: Readonly<Record<string, AnyNode>>;
|
|
1977
|
+
}) => boolean;
|
|
1840
1978
|
/**
|
|
1841
1979
|
* Called after a move of the child commits, with the LIVE (post-commit)
|
|
1842
1980
|
* child and parent. Lets the kind run derived-state maintenance the
|
|
@@ -1860,10 +1998,19 @@ export type ParentFrameSnapMatch = {
|
|
|
1860
1998
|
export type GroupMoveSnapArgs = {
|
|
1861
1999
|
node: AnyNode;
|
|
1862
2000
|
candidatePosition: [number, number, number];
|
|
2001
|
+
candidateRotation?: number;
|
|
1863
2002
|
movingIds: readonly AnyNodeId[];
|
|
1864
2003
|
nodes: Readonly<Record<string, AnyNode>>;
|
|
1865
2004
|
levelId: AnyNodeId | null;
|
|
1866
2005
|
};
|
|
2006
|
+
export type GroupMoveSnapResult = {
|
|
2007
|
+
position: [number, number, number];
|
|
2008
|
+
rotation?: number;
|
|
2009
|
+
};
|
|
2010
|
+
export type GridSnapPositionArgs = Omit<GroupMoveSnapArgs, 'candidateRotation'> & {
|
|
2011
|
+
candidateRotation: number;
|
|
2012
|
+
gridStep: number;
|
|
2013
|
+
};
|
|
1867
2014
|
export type LiveTransformLike = {
|
|
1868
2015
|
position: [number, number, number];
|
|
1869
2016
|
rotation: number;
|
|
@@ -1897,7 +2044,9 @@ export type SnappableConfig = {
|
|
|
1897
2044
|
export type SnapPointKind = 'start' | 'end' | 'midpoint' | 'center' | 'corners';
|
|
1898
2045
|
export type SurfacesConfig = {
|
|
1899
2046
|
top?: {
|
|
1900
|
-
height: number | ((n: AnyNode
|
|
2047
|
+
height: number | ((n: AnyNode, context: {
|
|
2048
|
+
nodes: Record<string, AnyNode>;
|
|
2049
|
+
}) => number);
|
|
1901
2050
|
};
|
|
1902
2051
|
sides?: {
|
|
1903
2052
|
faces: 'all' | ReadonlyArray<readonly [number, number, number]>;
|
|
@@ -1909,6 +2058,43 @@ export type SurfacePoint = {
|
|
|
1909
2058
|
position: readonly [number, number, number];
|
|
1910
2059
|
normal: readonly [number, number, number];
|
|
1911
2060
|
};
|
|
2061
|
+
export type FaceHostPlacementArgs<N extends AnyNode = AnyNode> = {
|
|
2062
|
+
host: N;
|
|
2063
|
+
asset: AssetInput;
|
|
2064
|
+
draftItem: ItemNode | null;
|
|
2065
|
+
localPosition: readonly [number, number, number];
|
|
2066
|
+
faceIndex?: number;
|
|
2067
|
+
object: Object3D;
|
|
2068
|
+
currentFaceId?: string | null;
|
|
2069
|
+
rawDimensions: readonly [number, number, number];
|
|
2070
|
+
dimensions: readonly [number, number, number];
|
|
2071
|
+
snapScalar: (value: number) => number;
|
|
2072
|
+
};
|
|
2073
|
+
export type FaceHostStoredPlacementArgs<N extends AnyNode = AnyNode> = {
|
|
2074
|
+
host: N;
|
|
2075
|
+
item: ItemNode;
|
|
2076
|
+
position: readonly [number, number, number];
|
|
2077
|
+
};
|
|
2078
|
+
export type FaceHostStoredValidityArgs<N extends AnyNode = AnyNode> = {
|
|
2079
|
+
host: N;
|
|
2080
|
+
item: ItemNode;
|
|
2081
|
+
asset: AssetInput;
|
|
2082
|
+
};
|
|
2083
|
+
export type FaceHostPlacementResult = {
|
|
2084
|
+
faceId: string;
|
|
2085
|
+
nodeUpdate: Partial<ItemNode>;
|
|
2086
|
+
position: [number, number, number];
|
|
2087
|
+
rotation: [number, number, number];
|
|
2088
|
+
cursorPosition: [number, number, number];
|
|
2089
|
+
cursorRotation: [number, number, number];
|
|
2090
|
+
};
|
|
2091
|
+
export type FaceHostCapability<N extends AnyNode = AnyNode> = {
|
|
2092
|
+
currentFaceId: (item: ItemNode | null) => string | null;
|
|
2093
|
+
clearItemFields: readonly (keyof ItemNode)[];
|
|
2094
|
+
resolvePlacement: (args: FaceHostPlacementArgs<N>) => FaceHostPlacementResult | null;
|
|
2095
|
+
storedPlacementPatch: (args: FaceHostStoredPlacementArgs<N>) => Partial<ItemNode> | null;
|
|
2096
|
+
isStoredPlacementValid: (args: FaceHostStoredValidityArgs<N>) => boolean;
|
|
2097
|
+
};
|
|
1912
2098
|
export type SelectableConfig = {
|
|
1913
2099
|
hitVolume?: 'bbox' | 'mesh' | 'none';
|
|
1914
2100
|
override?: (ctx: CapabilityCtx) => SelectableConfig | null;
|
|
@@ -1998,7 +2184,7 @@ export type ParametricDescriptor<N> = {
|
|
|
1998
2184
|
* Direct store/MCP writes bypass it — keep real invariants in
|
|
1999
2185
|
* `invariants`.
|
|
2000
2186
|
*/
|
|
2001
|
-
derive?: (next: N, patch: Partial<N
|
|
2187
|
+
derive?: (next: N, patch: Partial<N>, previous?: N) => Partial<N>;
|
|
2002
2188
|
/**
|
|
2003
2189
|
* Cross-node companion to `derive`: after an inspector edit lands on
|
|
2004
2190
|
* this node, return patches for OTHER nodes that must follow to keep
|
|
@@ -2018,13 +2204,15 @@ export type ParametricDescriptor<N> = {
|
|
|
2018
2204
|
* auto-inserted elbow re-extends the duct runs it trimmed back onto the
|
|
2019
2205
|
* corner it replaced. Called with the node and the live scene `nodes`
|
|
2020
2206
|
* map BEFORE the deletion lands; patches targeting nodes also being
|
|
2021
|
-
* deleted are ignored.
|
|
2022
|
-
*
|
|
2207
|
+
* deleted are ignored. `pendingDeleteIds` includes cascaded companion
|
|
2208
|
+
* deletes, while `requestedDeleteIds` is the user's original selection.
|
|
2209
|
+
* Applied in the same `set` as the delete so it's one undo step. Fires
|
|
2210
|
+
* only on `deleteNodes` (user-intent deletes) —
|
|
2023
2211
|
* NOT on `applyNodeChanges`, whose deletes are internal re-routes that
|
|
2024
2212
|
* rewrite neighbours explicitly in the same batch and would fight a
|
|
2025
2213
|
* restore.
|
|
2026
2214
|
*/
|
|
2027
|
-
onDelete?: (node: N, nodes: Record<AnyNodeId, AnyNode>) => Array<{
|
|
2215
|
+
onDelete?: (node: N, nodes: Record<AnyNodeId, AnyNode>, pendingDeleteIds: ReadonlySet<AnyNodeId>, requestedDeleteIds: ReadonlySet<AnyNodeId>) => Array<{
|
|
2028
2216
|
id: AnyNodeId;
|
|
2029
2217
|
data: Partial<AnyNode>;
|
|
2030
2218
|
}>;
|
|
@@ -2035,9 +2223,10 @@ export type ParametricDescriptor<N> = {
|
|
|
2035
2223
|
* deletion; returned ids are recursively expanded through the normal
|
|
2036
2224
|
* descendant cascade. `pendingDeleteIds` holds every id already part of
|
|
2037
2225
|
* the gesture so "would my parent become empty?" checks see sibling
|
|
2038
|
-
* deletes from the same multi-select.
|
|
2226
|
+
* deletes from the same multi-select. `requestedDeleteIds` remains the
|
|
2227
|
+
* original selection while the pending set expands.
|
|
2039
2228
|
*/
|
|
2040
|
-
onDeleteCascade?: (node: N, nodes: Record<AnyNodeId, AnyNode>, pendingDeleteIds: ReadonlySet<AnyNodeId>) => AnyNodeId[];
|
|
2229
|
+
onDeleteCascade?: (node: N, nodes: Record<AnyNodeId, AnyNode>, pendingDeleteIds: ReadonlySet<AnyNodeId>, requestedDeleteIds: ReadonlySet<AnyNodeId>) => AnyNodeId[];
|
|
2041
2230
|
customPanel?: () => Promise<{
|
|
2042
2231
|
default: ComponentType<{
|
|
2043
2232
|
node: N;
|
|
@@ -2078,6 +2267,7 @@ export type ParamGroup<N> = {
|
|
|
2078
2267
|
};
|
|
2079
2268
|
export type ParamField<N> = {
|
|
2080
2269
|
key: keyof N;
|
|
2270
|
+
label?: string;
|
|
2081
2271
|
kind: 'number';
|
|
2082
2272
|
unit?: string;
|
|
2083
2273
|
min?: number;
|
|
@@ -2087,10 +2277,12 @@ export type ParamField<N> = {
|
|
|
2087
2277
|
customEditor?: ComponentType;
|
|
2088
2278
|
} | {
|
|
2089
2279
|
key: keyof N;
|
|
2280
|
+
label?: string;
|
|
2090
2281
|
kind: 'boolean';
|
|
2091
2282
|
visibleIf?: (n: N) => boolean;
|
|
2092
2283
|
} | {
|
|
2093
2284
|
key: keyof N;
|
|
2285
|
+
label?: string;
|
|
2094
2286
|
kind: 'enum';
|
|
2095
2287
|
options: readonly string[];
|
|
2096
2288
|
/** Defaults to 'select' (dropdown). 'segmented' renders the inline
|
|
@@ -2099,18 +2291,22 @@ export type ParamField<N> = {
|
|
|
2099
2291
|
visibleIf?: (n: N) => boolean;
|
|
2100
2292
|
} | {
|
|
2101
2293
|
key: keyof N;
|
|
2294
|
+
label?: string;
|
|
2102
2295
|
kind: 'vec3';
|
|
2103
2296
|
visibleIf?: (n: N) => boolean;
|
|
2104
2297
|
} | {
|
|
2105
2298
|
key: keyof N;
|
|
2299
|
+
label?: string;
|
|
2106
2300
|
kind: 'color';
|
|
2107
2301
|
visibleIf?: (n: N) => boolean;
|
|
2108
2302
|
} | {
|
|
2109
2303
|
key: keyof N;
|
|
2304
|
+
label?: string;
|
|
2110
2305
|
kind: 'material';
|
|
2111
2306
|
visibleIf?: (n: N) => boolean;
|
|
2112
2307
|
} | {
|
|
2113
2308
|
key: keyof N;
|
|
2309
|
+
label?: string;
|
|
2114
2310
|
kind: 'ref';
|
|
2115
2311
|
refKind: string;
|
|
2116
2312
|
visibleIf?: (n: N) => boolean;
|
|
@@ -2122,6 +2318,7 @@ export type ParamField<N> = {
|
|
|
2122
2318
|
* update logic. `key` here is just a stable React key/label. */
|
|
2123
2319
|
| {
|
|
2124
2320
|
key: string;
|
|
2321
|
+
label?: string;
|
|
2125
2322
|
kind: 'custom';
|
|
2126
2323
|
component: ComponentType<{
|
|
2127
2324
|
node: N;
|
|
@@ -2180,6 +2377,22 @@ export type SceneApi = {
|
|
|
2180
2377
|
nodes: () => Readonly<Record<AnyNodeId, AnyNode>>;
|
|
2181
2378
|
update: (id: AnyNodeId, patch: Partial<AnyNode>) => void;
|
|
2182
2379
|
upsert: (node: AnyNode, parentId?: AnyNodeId) => AnyNodeId;
|
|
2380
|
+
createMany?: (ops: {
|
|
2381
|
+
node: AnyNode;
|
|
2382
|
+
parentId?: AnyNodeId;
|
|
2383
|
+
}[]) => void;
|
|
2384
|
+
applyChanges?: (changes: {
|
|
2385
|
+
create?: {
|
|
2386
|
+
node: AnyNode;
|
|
2387
|
+
parentId?: AnyNodeId;
|
|
2388
|
+
}[];
|
|
2389
|
+
update?: {
|
|
2390
|
+
id: AnyNodeId;
|
|
2391
|
+
data: Partial<AnyNode>;
|
|
2392
|
+
}[];
|
|
2393
|
+
delete?: AnyNodeId[];
|
|
2394
|
+
}) => void;
|
|
2395
|
+
subscribeNodes?: (listener: (nodes: Readonly<Record<AnyNodeId, AnyNode>>, previous: Readonly<Record<AnyNodeId, AnyNode>>, changedIds: ReadonlySet<AnyNodeId>) => void) => () => void;
|
|
2183
2396
|
delete: (id: AnyNodeId) => void;
|
|
2184
2397
|
restore: (id: AnyNodeId) => void;
|
|
2185
2398
|
restoreAll: () => void;
|