@pascal-app/core 0.5.1 → 0.6.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/dist/events/bus.d.ts +37 -3
- package/dist/events/bus.d.ts.map +1 -1
- package/dist/events/bus.js +1 -1
- package/dist/hooks/spatial-grid/spatial-grid.d.ts +2 -0
- package/dist/hooks/spatial-grid/spatial-grid.d.ts.map +1 -1
- package/dist/hooks/spatial-grid/spatial-grid.js +43 -20
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/lib/polygon-geometry.d.ts +3 -0
- package/dist/lib/polygon-geometry.d.ts.map +1 -0
- package/dist/lib/polygon-geometry.js +90 -0
- package/dist/lib/space-detection.d.ts +10 -17
- package/dist/lib/space-detection.d.ts.map +1 -1
- package/dist/lib/space-detection.js +666 -453
- package/dist/material-library.d.ts +18 -0
- package/dist/material-library.d.ts.map +1 -0
- package/dist/material-library.js +603 -0
- package/dist/schema/index.d.ts +9 -4
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +5 -4
- package/dist/schema/material.d.ts +109 -0
- package/dist/schema/material.d.ts.map +1 -1
- package/dist/schema/material.js +52 -0
- package/dist/schema/nodes/ceiling.d.ts +10 -0
- package/dist/schema/nodes/ceiling.d.ts.map +1 -1
- package/dist/schema/nodes/ceiling.js +6 -0
- package/dist/schema/nodes/door.d.ts +1 -0
- package/dist/schema/nodes/door.d.ts.map +1 -1
- package/dist/schema/nodes/fence.d.ts +34 -0
- package/dist/schema/nodes/fence.d.ts.map +1 -1
- package/dist/schema/nodes/fence.js +5 -0
- package/dist/schema/nodes/item.d.ts +2 -2
- package/dist/schema/nodes/roof-segment.d.ts +2 -0
- package/dist/schema/nodes/roof-segment.d.ts.map +1 -1
- package/dist/schema/nodes/roof-segment.js +1 -0
- package/dist/schema/nodes/roof.d.ts +108 -0
- package/dist/schema/nodes/roof.d.ts.map +1 -1
- package/dist/schema/nodes/roof.js +58 -2
- package/dist/schema/nodes/site.d.ts +1 -1
- package/dist/schema/nodes/slab.d.ts +10 -0
- package/dist/schema/nodes/slab.d.ts.map +1 -1
- package/dist/schema/nodes/slab.js +7 -0
- package/dist/schema/nodes/stair-segment.d.ts +2 -0
- package/dist/schema/nodes/stair-segment.d.ts.map +1 -1
- package/dist/schema/nodes/stair-segment.js +1 -0
- package/dist/schema/nodes/stair.d.ts +122 -2
- package/dist/schema/nodes/stair.d.ts.map +1 -1
- package/dist/schema/nodes/stair.js +72 -2
- package/dist/schema/nodes/surface-hole-metadata.d.ts +10 -0
- package/dist/schema/nodes/surface-hole-metadata.d.ts.map +1 -0
- package/dist/schema/nodes/surface-hole-metadata.js +5 -0
- package/dist/schema/nodes/wall.d.ts +87 -1
- package/dist/schema/nodes/wall.d.ts.map +1 -1
- package/dist/schema/nodes/wall.js +45 -4
- package/dist/schema/nodes/window.d.ts +1 -0
- package/dist/schema/nodes/window.d.ts.map +1 -1
- package/dist/schema/types.d.ts +343 -5
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/store/actions/node-actions.d.ts +1 -1
- package/dist/store/actions/node-actions.d.ts.map +1 -1
- package/dist/store/actions/node-actions.js +175 -0
- package/dist/store/history-control.d.ts +14 -0
- package/dist/store/history-control.d.ts.map +1 -0
- package/dist/store/history-control.js +22 -0
- package/dist/store/use-scene.d.ts.map +1 -1
- package/dist/store/use-scene.js +248 -2
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +7 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -1
- package/dist/systems/fence/fence-system.js +106 -39
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +31 -1
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +45 -8
- package/dist/systems/stair/stair-opening-sync.d.ts +6 -0
- package/dist/systems/stair/stair-opening-sync.d.ts.map +1 -0
- package/dist/systems/stair/stair-opening-sync.js +515 -0
- package/dist/systems/stair/stair-system.d.ts.map +1 -1
- package/dist/systems/stair/stair-system.js +119 -2
- package/dist/systems/wall/wall-curve.d.ts +43 -0
- package/dist/systems/wall/wall-curve.d.ts.map +1 -0
- package/dist/systems/wall/wall-curve.js +176 -0
- package/dist/systems/wall/wall-footprint.d.ts.map +1 -1
- package/dist/systems/wall/wall-footprint.js +16 -2
- package/dist/systems/wall/wall-mitering.d.ts +7 -0
- package/dist/systems/wall/wall-mitering.d.ts.map +1 -1
- package/dist/systems/wall/wall-mitering.js +76 -3
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +202 -2
- package/package.json +3 -3
|
@@ -1,6 +1,151 @@
|
|
|
1
|
+
import { getEffectiveWallSurfaceMaterial, getWallSurfaceMaterialSignature, } from '../../schema';
|
|
1
2
|
// Track pending RAF for updateNodesAction to prevent multiple queued callbacks
|
|
2
3
|
let pendingRafId = null;
|
|
3
4
|
let pendingUpdates = new Set();
|
|
5
|
+
function pointsEqual(a, b, tolerance = 1e-6) {
|
|
6
|
+
const dx = a[0] - b[0];
|
|
7
|
+
const dz = a[1] - b[1];
|
|
8
|
+
return dx * dx + dz * dz <= tolerance * tolerance;
|
|
9
|
+
}
|
|
10
|
+
function wallLength(wall) {
|
|
11
|
+
return Math.hypot(wall.end[0] - wall.start[0], wall.end[1] - wall.start[1]);
|
|
12
|
+
}
|
|
13
|
+
function getWallEndpointAtPoint(wall, point) {
|
|
14
|
+
if (pointsEqual(wall.start, point))
|
|
15
|
+
return 'start';
|
|
16
|
+
if (pointsEqual(wall.end, point))
|
|
17
|
+
return 'end';
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function getWallFreeEndpoint(wall, sharedPoint) {
|
|
21
|
+
return pointsEqual(wall.start, sharedPoint) ? wall.end : wall.start;
|
|
22
|
+
}
|
|
23
|
+
function areWallStylesCompatible(a, b) {
|
|
24
|
+
const aInterior = getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(a, 'interior'));
|
|
25
|
+
const bInterior = getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(b, 'interior'));
|
|
26
|
+
const aExterior = getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(a, 'exterior'));
|
|
27
|
+
const bExterior = getWallSurfaceMaterialSignature(getEffectiveWallSurfaceMaterial(b, 'exterior'));
|
|
28
|
+
return ((a.parentId ?? null) === (b.parentId ?? null) &&
|
|
29
|
+
Math.abs((a.curveOffset ?? 0) - (b.curveOffset ?? 0)) <= 1e-6 &&
|
|
30
|
+
Math.abs((a.thickness ?? 0.2) - (b.thickness ?? 0.2)) <= 1e-6 &&
|
|
31
|
+
Math.abs((a.height ?? 2.5) - (b.height ?? 2.5)) <= 1e-6 &&
|
|
32
|
+
aInterior === bInterior &&
|
|
33
|
+
aExterior === bExterior &&
|
|
34
|
+
a.frontSide === b.frontSide &&
|
|
35
|
+
a.backSide === b.backSide &&
|
|
36
|
+
a.visible === b.visible);
|
|
37
|
+
}
|
|
38
|
+
function areWallsCollinearAcrossPoint(a, b, sharedPoint) {
|
|
39
|
+
const freeA = getWallFreeEndpoint(a, sharedPoint);
|
|
40
|
+
const freeB = getWallFreeEndpoint(b, sharedPoint);
|
|
41
|
+
const ax = freeA[0] - sharedPoint[0];
|
|
42
|
+
const az = freeA[1] - sharedPoint[1];
|
|
43
|
+
const bx = freeB[0] - sharedPoint[0];
|
|
44
|
+
const bz = freeB[1] - sharedPoint[1];
|
|
45
|
+
const lenA = Math.hypot(ax, az);
|
|
46
|
+
const lenB = Math.hypot(bx, bz);
|
|
47
|
+
if (lenA < 1e-6 || lenB < 1e-6)
|
|
48
|
+
return false;
|
|
49
|
+
const cross = (ax * bz - az * bx) / (lenA * lenB);
|
|
50
|
+
const dot = (ax * bx + az * bz) / (lenA * lenB);
|
|
51
|
+
return Math.abs(cross) <= 1e-4 && dot < -0.999;
|
|
52
|
+
}
|
|
53
|
+
function resolveMergedWallEndpoints(primary, secondary, sharedPoint) {
|
|
54
|
+
const primaryEndpoint = getWallEndpointAtPoint(primary, sharedPoint);
|
|
55
|
+
const secondaryEndpoint = getWallEndpointAtPoint(secondary, sharedPoint);
|
|
56
|
+
if (primaryEndpoint === 'end' && secondaryEndpoint === 'start') {
|
|
57
|
+
return { start: primary.start, end: secondary.end };
|
|
58
|
+
}
|
|
59
|
+
if (primaryEndpoint === 'start' && secondaryEndpoint === 'end') {
|
|
60
|
+
return { start: secondary.start, end: primary.end };
|
|
61
|
+
}
|
|
62
|
+
if (primaryEndpoint === 'start' && secondaryEndpoint === 'start') {
|
|
63
|
+
return { start: primary.end, end: secondary.end };
|
|
64
|
+
}
|
|
65
|
+
return { start: primary.start, end: secondary.start };
|
|
66
|
+
}
|
|
67
|
+
function buildMergedWallAttachmentUpdates(primary, secondary, mergedWallId, mergedStart, mergedEnd, nodes) {
|
|
68
|
+
const mergedLength = Math.max(Math.hypot(mergedEnd[0] - mergedStart[0], mergedEnd[1] - mergedStart[1]), 1e-6);
|
|
69
|
+
const tangentX = (mergedEnd[0] - mergedStart[0]) / mergedLength;
|
|
70
|
+
const tangentZ = (mergedEnd[1] - mergedStart[1]) / mergedLength;
|
|
71
|
+
const updates = [];
|
|
72
|
+
const wallChildren = [...(primary.children ?? []), ...(secondary.children ?? [])];
|
|
73
|
+
for (const childId of wallChildren) {
|
|
74
|
+
const child = nodes[childId];
|
|
75
|
+
if (!child || !('position' in child) || !Array.isArray(child.position)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const sourceWall = child.parentId === secondary.id ? secondary : primary;
|
|
79
|
+
const sourceLength = Math.max(wallLength(sourceWall), 1e-6);
|
|
80
|
+
const localX = typeof child.position[0] === 'number' ? child.position[0] : 0;
|
|
81
|
+
const worldX = sourceWall.start[0] + ((sourceWall.end[0] - sourceWall.start[0]) * localX) / sourceLength;
|
|
82
|
+
const worldZ = sourceWall.start[1] + ((sourceWall.end[1] - sourceWall.start[1]) * localX) / sourceLength;
|
|
83
|
+
const nextLocalX = Math.max(0, Math.min(mergedLength, (worldX - mergedStart[0]) * tangentX + (worldZ - mergedStart[1]) * tangentZ));
|
|
84
|
+
updates.push({
|
|
85
|
+
id: childId,
|
|
86
|
+
data: {
|
|
87
|
+
parentId: mergedWallId,
|
|
88
|
+
wallId: mergedWallId,
|
|
89
|
+
position: [nextLocalX, child.position[1], child.position[2]],
|
|
90
|
+
...('wallT' in child ? { wallT: nextLocalX / mergedLength } : {}),
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return updates;
|
|
95
|
+
}
|
|
96
|
+
function buildWallMergePlans(nodes, idsToDelete) {
|
|
97
|
+
const deletedWalls = idsToDelete
|
|
98
|
+
.map((id) => nodes[id])
|
|
99
|
+
.filter((node) => node?.type === 'wall');
|
|
100
|
+
const skippedWallIds = new Set(idsToDelete);
|
|
101
|
+
const usedWallIds = new Set();
|
|
102
|
+
const mergePlans = [];
|
|
103
|
+
for (const deletedWall of deletedWalls) {
|
|
104
|
+
const junctions = [deletedWall.start, deletedWall.end];
|
|
105
|
+
for (const junction of junctions) {
|
|
106
|
+
const candidates = Object.values(nodes).filter((node) => {
|
|
107
|
+
if (node?.type !== 'wall')
|
|
108
|
+
return false;
|
|
109
|
+
if (skippedWallIds.has(node.id) || usedWallIds.has(node.id))
|
|
110
|
+
return false;
|
|
111
|
+
if ((node.parentId ?? null) !== (deletedWall.parentId ?? null))
|
|
112
|
+
return false;
|
|
113
|
+
return pointsEqual(node.start, junction) || pointsEqual(node.end, junction);
|
|
114
|
+
});
|
|
115
|
+
if (candidates.length !== 2) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const sortedCandidates = [...candidates].sort((a, b) => {
|
|
119
|
+
const attachmentDiff = (b.children?.length ?? 0) - (a.children?.length ?? 0);
|
|
120
|
+
if (attachmentDiff !== 0) {
|
|
121
|
+
return attachmentDiff;
|
|
122
|
+
}
|
|
123
|
+
return a.id.localeCompare(b.id);
|
|
124
|
+
});
|
|
125
|
+
const [primary, secondary] = sortedCandidates;
|
|
126
|
+
if (!primary ||
|
|
127
|
+
!secondary ||
|
|
128
|
+
!areWallStylesCompatible(primary, secondary) ||
|
|
129
|
+
!areWallsCollinearAcrossPoint(primary, secondary, junction)) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const { start, end } = resolveMergedWallEndpoints(primary, secondary, junction);
|
|
133
|
+
const mergedChildren = Array.from(new Set([...(primary.children ?? []), ...(secondary.children ?? [])]));
|
|
134
|
+
const attachmentUpdates = buildMergedWallAttachmentUpdates(primary, secondary, primary.id, start, end, nodes);
|
|
135
|
+
mergePlans.push({
|
|
136
|
+
primaryWallId: primary.id,
|
|
137
|
+
secondaryWallId: secondary.id,
|
|
138
|
+
mergedStart: start,
|
|
139
|
+
mergedEnd: end,
|
|
140
|
+
mergedChildren,
|
|
141
|
+
attachmentUpdates,
|
|
142
|
+
});
|
|
143
|
+
usedWallIds.add(primary.id);
|
|
144
|
+
usedWallIds.add(secondary.id);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return mergePlans;
|
|
148
|
+
}
|
|
4
149
|
export const createNodesAction = (set, get, ops) => {
|
|
5
150
|
if (get().readOnly)
|
|
6
151
|
return;
|
|
@@ -102,6 +247,8 @@ export const deleteNodesAction = (set, get, ids) => {
|
|
|
102
247
|
if (get().readOnly)
|
|
103
248
|
return;
|
|
104
249
|
const parentsToMarkDirty = new Set();
|
|
250
|
+
const nodesToMarkDirty = new Set();
|
|
251
|
+
const mergePlans = buildWallMergePlans(get().nodes, ids);
|
|
105
252
|
set((state) => {
|
|
106
253
|
const nextNodes = { ...state.nodes };
|
|
107
254
|
const nextCollections = { ...state.collections };
|
|
@@ -121,6 +268,31 @@ export const deleteNodesAction = (set, get, ids) => {
|
|
|
121
268
|
};
|
|
122
269
|
for (const id of ids)
|
|
123
270
|
collect(id);
|
|
271
|
+
for (const plan of mergePlans) {
|
|
272
|
+
allIds.add(plan.secondaryWallId);
|
|
273
|
+
}
|
|
274
|
+
for (const plan of mergePlans) {
|
|
275
|
+
const primaryWall = nextNodes[plan.primaryWallId];
|
|
276
|
+
if (!(primaryWall && primaryWall.type === 'wall') || allIds.has(plan.primaryWallId)) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
nextNodes[plan.primaryWallId] = {
|
|
280
|
+
...primaryWall,
|
|
281
|
+
start: plan.mergedStart,
|
|
282
|
+
end: plan.mergedEnd,
|
|
283
|
+
children: plan.mergedChildren,
|
|
284
|
+
};
|
|
285
|
+
nodesToMarkDirty.add(plan.primaryWallId);
|
|
286
|
+
for (const update of plan.attachmentUpdates) {
|
|
287
|
+
if (allIds.has(update.id))
|
|
288
|
+
continue;
|
|
289
|
+
const child = nextNodes[update.id];
|
|
290
|
+
if (!child)
|
|
291
|
+
continue;
|
|
292
|
+
nextNodes[update.id] = { ...child, ...update.data };
|
|
293
|
+
nodesToMarkDirty.add(update.id);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
124
296
|
for (const id of allIds) {
|
|
125
297
|
const node = nextNodes[id];
|
|
126
298
|
if (!node)
|
|
@@ -164,4 +336,7 @@ export const deleteNodesAction = (set, get, ids) => {
|
|
|
164
336
|
}
|
|
165
337
|
}
|
|
166
338
|
});
|
|
339
|
+
nodesToMarkDirty.forEach((id) => {
|
|
340
|
+
get().markDirty(id);
|
|
341
|
+
});
|
|
167
342
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type TemporalStoreLike = {
|
|
2
|
+
temporal: {
|
|
3
|
+
getState(): {
|
|
4
|
+
pause(): void;
|
|
5
|
+
resume(): void;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export declare function pauseSceneHistory(sceneStore: TemporalStoreLike): void;
|
|
10
|
+
export declare function resumeSceneHistory(sceneStore: TemporalStoreLike): void;
|
|
11
|
+
export declare function getSceneHistoryPauseDepth(): number;
|
|
12
|
+
export declare function resetSceneHistoryPauseDepth(): void;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=history-control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history-control.d.ts","sourceRoot":"","sources":["../../src/store/history-control.ts"],"names":[],"mappings":"AAEA,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE;QACR,QAAQ,IAAI;YACV,KAAK,IAAI,IAAI,CAAA;YACb,MAAM,IAAI,IAAI,CAAA;SACf,CAAA;KACF,CAAA;CACF,CAAA;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAKrE;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAStE;AAED,wBAAgB,yBAAyB,IAAI,MAAM,CAElD;AAED,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
let sceneHistoryPauseDepth = 0;
|
|
2
|
+
export function pauseSceneHistory(sceneStore) {
|
|
3
|
+
if (sceneHistoryPauseDepth === 0) {
|
|
4
|
+
sceneStore.temporal.getState().pause();
|
|
5
|
+
}
|
|
6
|
+
sceneHistoryPauseDepth += 1;
|
|
7
|
+
}
|
|
8
|
+
export function resumeSceneHistory(sceneStore) {
|
|
9
|
+
if (sceneHistoryPauseDepth === 0) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
sceneHistoryPauseDepth -= 1;
|
|
13
|
+
if (sceneHistoryPauseDepth === 0) {
|
|
14
|
+
sceneStore.temporal.getState().resume();
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function getSceneHistoryPauseDepth() {
|
|
18
|
+
return sceneHistoryPauseDepth;
|
|
19
|
+
}
|
|
20
|
+
export function resetSceneHistoryPauseDepth() {
|
|
21
|
+
sceneHistoryPauseDepth = 0;
|
|
22
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-scene.d.ts","sourceRoot":"","sources":["../../src/store/use-scene.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EAAU,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAA;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"use-scene.d.ts","sourceRoot":"","sources":["../../src/store/use-scene.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAE1C,OAAO,EAAU,KAAK,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,SAAS,CAAA;AAEnE,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AAMrE,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAiVzD,MAAM,MAAM,UAAU,GAAG;IAEvB,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA;IAGjC,WAAW,EAAE,SAAS,EAAE,CAAA;IAGxB,UAAU,EAAE,GAAG,CAAC,SAAS,CAAC,CAAA;IAG1B,WAAW,EAAE,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;IAG7C,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAGxC,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,UAAU,EAAE,MAAM,IAAI,CAAA;IACtB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IAE/E,SAAS,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IAClC,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IAEnC,UAAU,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,SAAS,KAAK,IAAI,CAAA;IACzD,WAAW,EAAE,CAAC,GAAG,EAAE;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;KAAE,EAAE,KAAK,IAAI,CAAA;IAErE,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;IAC3D,WAAW,EAAE,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;KAAE,EAAE,KAAK,IAAI,CAAA;IAE3E,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAA;IACnC,WAAW,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,IAAI,CAAA;IAGvC,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK,YAAY,CAAA;IACvE,gBAAgB,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,CAAA;IAC5C,gBAAgB,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,CAAA;IACnF,eAAe,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;IAC9D,oBAAoB,EAAE,CAAC,EAAE,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAA;CACpE,CAAA;AAID,KAAK,aAAa,GAAG,aAAa,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,GAAG;IACzD,QAAQ,EAAE,QAAQ,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,CAAA;CAC7F,CAAA;AAED,QAAA,MAAM,QAAQ,EAAE,aA8Nf,CAAA;AAED,eAAe,QAAQ,CAAA;AAOvB,wBAAgB,iBAAiB,SAMhC"}
|
package/dist/store/use-scene.js
CHANGED
|
@@ -5,7 +5,233 @@ import { BuildingNode } from '../schema';
|
|
|
5
5
|
import { generateCollectionId } from '../schema/collections';
|
|
6
6
|
import { LevelNode } from '../schema/nodes/level';
|
|
7
7
|
import { SiteNode } from '../schema/nodes/site';
|
|
8
|
+
import { StairNode as StairNodeSchema } from '../schema/nodes/stair';
|
|
9
|
+
import { StairSegmentNode as StairSegmentNodeSchema } from '../schema/nodes/stair-segment';
|
|
10
|
+
import { resetSceneHistoryPauseDepth } from './history-control';
|
|
8
11
|
import * as nodeActions from './actions/node-actions';
|
|
12
|
+
function getFiniteNumber(value, fallback) {
|
|
13
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
14
|
+
}
|
|
15
|
+
function getBoolean(value, fallback) {
|
|
16
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
17
|
+
}
|
|
18
|
+
function getEnumValue(value, allowed, fallback) {
|
|
19
|
+
return typeof value === 'string' && allowed.includes(value) ? value : fallback;
|
|
20
|
+
}
|
|
21
|
+
function getNullableString(value) {
|
|
22
|
+
return typeof value === 'string' ? value : null;
|
|
23
|
+
}
|
|
24
|
+
function getStringArray(value) {
|
|
25
|
+
return Array.isArray(value)
|
|
26
|
+
? value.filter((entry) => typeof entry === 'string')
|
|
27
|
+
: [];
|
|
28
|
+
}
|
|
29
|
+
function getVector3(value, fallback) {
|
|
30
|
+
if (!Array.isArray(value) || value.length < 3) {
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
return [
|
|
34
|
+
getFiniteNumber(value[0], fallback[0]),
|
|
35
|
+
getFiniteNumber(value[1], fallback[1]),
|
|
36
|
+
getFiniteNumber(value[2], fallback[2]),
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
function normalizeStairNode(node) {
|
|
40
|
+
const sanitized = {
|
|
41
|
+
...node,
|
|
42
|
+
position: getVector3(node.position, [0, 0, 0]),
|
|
43
|
+
rotation: getFiniteNumber(node.rotation, 0),
|
|
44
|
+
stairType: getEnumValue(node.stairType, ['straight', 'curved', 'spiral'], 'straight'),
|
|
45
|
+
fromLevelId: getNullableString(node.fromLevelId),
|
|
46
|
+
toLevelId: getNullableString(node.toLevelId),
|
|
47
|
+
slabOpeningMode: getEnumValue(node.slabOpeningMode, ['none', 'destination'], 'none'),
|
|
48
|
+
openingOffset: getFiniteNumber(node.openingOffset, 0),
|
|
49
|
+
width: getFiniteNumber(node.width, 1),
|
|
50
|
+
totalRise: getFiniteNumber(node.totalRise, 2.5),
|
|
51
|
+
stepCount: getFiniteNumber(node.stepCount, 10),
|
|
52
|
+
thickness: getFiniteNumber(node.thickness, 0.25),
|
|
53
|
+
fillToFloor: getBoolean(node.fillToFloor, true),
|
|
54
|
+
innerRadius: getFiniteNumber(node.innerRadius, 0.9),
|
|
55
|
+
sweepAngle: getFiniteNumber(node.sweepAngle, Math.PI / 2),
|
|
56
|
+
topLandingMode: getEnumValue(node.topLandingMode, ['none', 'integrated'], 'none'),
|
|
57
|
+
topLandingDepth: getFiniteNumber(node.topLandingDepth, 0.9),
|
|
58
|
+
showCenterColumn: getBoolean(node.showCenterColumn, true),
|
|
59
|
+
showStepSupports: getBoolean(node.showStepSupports, true),
|
|
60
|
+
railingMode: getEnumValue(node.railingMode, ['none', 'left', 'right', 'both'], 'none'),
|
|
61
|
+
railingHeight: getFiniteNumber(node.railingHeight, 0.92),
|
|
62
|
+
children: getStringArray(node.children),
|
|
63
|
+
};
|
|
64
|
+
const parsed = StairNodeSchema.safeParse(sanitized);
|
|
65
|
+
return parsed.success ? parsed.data : null;
|
|
66
|
+
}
|
|
67
|
+
function normalizeStairSegmentNode(node) {
|
|
68
|
+
const sanitized = {
|
|
69
|
+
...node,
|
|
70
|
+
position: getVector3(node.position, [0, 0, 0]),
|
|
71
|
+
rotation: getFiniteNumber(node.rotation, 0),
|
|
72
|
+
segmentType: getEnumValue(node.segmentType, ['stair', 'landing'], 'stair'),
|
|
73
|
+
width: getFiniteNumber(node.width, 1),
|
|
74
|
+
length: getFiniteNumber(node.length, 3),
|
|
75
|
+
height: getFiniteNumber(node.height, 2.5),
|
|
76
|
+
stepCount: getFiniteNumber(node.stepCount, 10),
|
|
77
|
+
attachmentSide: getEnumValue(node.attachmentSide, ['front', 'left', 'right'], 'front'),
|
|
78
|
+
fillToFloor: getBoolean(node.fillToFloor, true),
|
|
79
|
+
thickness: getFiniteNumber(node.thickness, 0.25),
|
|
80
|
+
};
|
|
81
|
+
const parsed = StairSegmentNodeSchema.safeParse(sanitized);
|
|
82
|
+
return parsed.success ? parsed.data : null;
|
|
83
|
+
}
|
|
84
|
+
function migrateWallSurfaceMaterials(node) {
|
|
85
|
+
const hasInterior = node.interiorMaterial !== undefined || typeof node.interiorMaterialPreset === 'string';
|
|
86
|
+
const hasExterior = node.exteriorMaterial !== undefined || typeof node.exteriorMaterialPreset === 'string';
|
|
87
|
+
const legacyFinish = {
|
|
88
|
+
material: node.material,
|
|
89
|
+
materialPreset: typeof node.materialPreset === 'string' ? node.materialPreset : undefined,
|
|
90
|
+
};
|
|
91
|
+
if (!hasInterior && !hasExterior) {
|
|
92
|
+
if (legacyFinish.material === undefined && legacyFinish.materialPreset === undefined) {
|
|
93
|
+
return node;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
...node,
|
|
97
|
+
interiorMaterial: legacyFinish.material,
|
|
98
|
+
interiorMaterialPreset: legacyFinish.materialPreset,
|
|
99
|
+
exteriorMaterial: legacyFinish.material,
|
|
100
|
+
exteriorMaterialPreset: legacyFinish.materialPreset,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (!hasInterior) {
|
|
104
|
+
return {
|
|
105
|
+
...node,
|
|
106
|
+
interiorMaterial: node.exteriorMaterial,
|
|
107
|
+
interiorMaterialPreset: node.exteriorMaterialPreset,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (!hasExterior) {
|
|
111
|
+
return {
|
|
112
|
+
...node,
|
|
113
|
+
exteriorMaterial: node.interiorMaterial,
|
|
114
|
+
exteriorMaterialPreset: node.interiorMaterialPreset,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return node;
|
|
118
|
+
}
|
|
119
|
+
function migrateStairSurfaceMaterials(node) {
|
|
120
|
+
const hasRailing = node.railingMaterial !== undefined || typeof node.railingMaterialPreset === 'string';
|
|
121
|
+
const hasTread = node.treadMaterial !== undefined || typeof node.treadMaterialPreset === 'string';
|
|
122
|
+
const hasSide = node.sideMaterial !== undefined || typeof node.sideMaterialPreset === 'string';
|
|
123
|
+
const legacyFinish = {
|
|
124
|
+
material: node.material,
|
|
125
|
+
materialPreset: typeof node.materialPreset === 'string' ? node.materialPreset : undefined,
|
|
126
|
+
};
|
|
127
|
+
const resolveBodyFallback = () => {
|
|
128
|
+
if (node.treadMaterial !== undefined || typeof node.treadMaterialPreset === 'string') {
|
|
129
|
+
return {
|
|
130
|
+
material: node.treadMaterial,
|
|
131
|
+
materialPreset: typeof node.treadMaterialPreset === 'string' ? node.treadMaterialPreset : undefined,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (node.sideMaterial !== undefined || typeof node.sideMaterialPreset === 'string') {
|
|
135
|
+
return {
|
|
136
|
+
material: node.sideMaterial,
|
|
137
|
+
materialPreset: typeof node.sideMaterialPreset === 'string' ? node.sideMaterialPreset : undefined,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
return legacyFinish;
|
|
141
|
+
};
|
|
142
|
+
if (!hasRailing && !hasTread && !hasSide) {
|
|
143
|
+
if (legacyFinish.material === undefined && legacyFinish.materialPreset === undefined) {
|
|
144
|
+
return node;
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
...node,
|
|
148
|
+
railingMaterial: legacyFinish.material,
|
|
149
|
+
railingMaterialPreset: legacyFinish.materialPreset,
|
|
150
|
+
treadMaterial: legacyFinish.material,
|
|
151
|
+
treadMaterialPreset: legacyFinish.materialPreset,
|
|
152
|
+
sideMaterial: legacyFinish.material,
|
|
153
|
+
sideMaterialPreset: legacyFinish.materialPreset,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
const next = { ...node };
|
|
157
|
+
if (!hasTread) {
|
|
158
|
+
const fallback = node.sideMaterial !== undefined || typeof node.sideMaterialPreset === 'string'
|
|
159
|
+
? {
|
|
160
|
+
material: node.sideMaterial,
|
|
161
|
+
materialPreset: typeof node.sideMaterialPreset === 'string' ? node.sideMaterialPreset : undefined,
|
|
162
|
+
}
|
|
163
|
+
: resolveBodyFallback();
|
|
164
|
+
next.treadMaterial = fallback.material;
|
|
165
|
+
next.treadMaterialPreset = fallback.materialPreset;
|
|
166
|
+
}
|
|
167
|
+
if (!hasSide) {
|
|
168
|
+
const fallback = node.treadMaterial !== undefined || typeof node.treadMaterialPreset === 'string'
|
|
169
|
+
? {
|
|
170
|
+
material: node.treadMaterial,
|
|
171
|
+
materialPreset: typeof node.treadMaterialPreset === 'string' ? node.treadMaterialPreset : undefined,
|
|
172
|
+
}
|
|
173
|
+
: resolveBodyFallback();
|
|
174
|
+
next.sideMaterial = fallback.material;
|
|
175
|
+
next.sideMaterialPreset = fallback.materialPreset;
|
|
176
|
+
}
|
|
177
|
+
if (!hasRailing) {
|
|
178
|
+
const fallback = resolveBodyFallback();
|
|
179
|
+
next.railingMaterial = fallback.material;
|
|
180
|
+
next.railingMaterialPreset = fallback.materialPreset;
|
|
181
|
+
}
|
|
182
|
+
return next;
|
|
183
|
+
}
|
|
184
|
+
function migrateRoofSurfaceMaterials(node) {
|
|
185
|
+
const hasTop = node.topMaterial !== undefined || typeof node.topMaterialPreset === 'string';
|
|
186
|
+
const hasEdge = node.edgeMaterial !== undefined || typeof node.edgeMaterialPreset === 'string';
|
|
187
|
+
const hasWall = node.wallMaterial !== undefined || typeof node.wallMaterialPreset === 'string';
|
|
188
|
+
const legacyFinish = {
|
|
189
|
+
material: node.material,
|
|
190
|
+
materialPreset: typeof node.materialPreset === 'string' ? node.materialPreset : undefined,
|
|
191
|
+
};
|
|
192
|
+
if (!hasTop && !hasEdge && !hasWall) {
|
|
193
|
+
if (legacyFinish.material === undefined && legacyFinish.materialPreset === undefined) {
|
|
194
|
+
return node;
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
...node,
|
|
198
|
+
topMaterial: legacyFinish.material,
|
|
199
|
+
topMaterialPreset: legacyFinish.materialPreset,
|
|
200
|
+
edgeMaterial: legacyFinish.material,
|
|
201
|
+
edgeMaterialPreset: legacyFinish.materialPreset,
|
|
202
|
+
wallMaterial: legacyFinish.material,
|
|
203
|
+
wallMaterialPreset: legacyFinish.materialPreset,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const next = { ...node };
|
|
207
|
+
if (!hasTop) {
|
|
208
|
+
next.topMaterial = legacyFinish.material;
|
|
209
|
+
next.topMaterialPreset = legacyFinish.materialPreset;
|
|
210
|
+
}
|
|
211
|
+
if (!hasEdge) {
|
|
212
|
+
if (node.wallMaterial !== undefined || typeof node.wallMaterialPreset === 'string') {
|
|
213
|
+
next.edgeMaterial = node.wallMaterial;
|
|
214
|
+
next.edgeMaterialPreset =
|
|
215
|
+
typeof node.wallMaterialPreset === 'string' ? node.wallMaterialPreset : undefined;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
next.edgeMaterial = legacyFinish.material;
|
|
219
|
+
next.edgeMaterialPreset = legacyFinish.materialPreset;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (!hasWall) {
|
|
223
|
+
if (node.edgeMaterial !== undefined || typeof node.edgeMaterialPreset === 'string') {
|
|
224
|
+
next.wallMaterial = node.edgeMaterial;
|
|
225
|
+
next.wallMaterialPreset =
|
|
226
|
+
typeof node.edgeMaterialPreset === 'string' ? node.edgeMaterialPreset : undefined;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
next.wallMaterial = legacyFinish.material;
|
|
230
|
+
next.wallMaterialPreset = legacyFinish.materialPreset;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return next;
|
|
234
|
+
}
|
|
9
235
|
function migrateNodes(nodes) {
|
|
10
236
|
const patchedNodes = { ...nodes };
|
|
11
237
|
for (const [id, node] of Object.entries(patchedNodes)) {
|
|
@@ -43,6 +269,24 @@ function migrateNodes(nodes) {
|
|
|
43
269
|
children: [segmentId],
|
|
44
270
|
};
|
|
45
271
|
}
|
|
272
|
+
if (node.type === 'stair') {
|
|
273
|
+
const normalized = normalizeStairNode(migrateStairSurfaceMaterials(node));
|
|
274
|
+
if (normalized) {
|
|
275
|
+
patchedNodes[id] = normalized;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (node.type === 'stair-segment') {
|
|
279
|
+
const normalized = normalizeStairSegmentNode(node);
|
|
280
|
+
if (normalized) {
|
|
281
|
+
patchedNodes[id] = normalized;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (node.type === 'wall') {
|
|
285
|
+
patchedNodes[id] = migrateWallSurfaceMaterials(patchedNodes[id]);
|
|
286
|
+
}
|
|
287
|
+
if (node.type === 'roof') {
|
|
288
|
+
patchedNodes[id] = migrateRoofSurfaceMaterials(patchedNodes[id]);
|
|
289
|
+
}
|
|
46
290
|
}
|
|
47
291
|
return patchedNodes;
|
|
48
292
|
}
|
|
@@ -246,6 +490,7 @@ let prevFutureLength = 0;
|
|
|
246
490
|
let prevNodesSnapshot = null;
|
|
247
491
|
export function clearSceneHistory() {
|
|
248
492
|
useScene.temporal.getState().clear();
|
|
493
|
+
resetSceneHistoryPauseDepth();
|
|
249
494
|
prevPastLength = 0;
|
|
250
495
|
prevFutureLength = 0;
|
|
251
496
|
prevNodesSnapshot = null;
|
|
@@ -261,8 +506,9 @@ useScene.temporal.subscribe((state) => {
|
|
|
261
506
|
if (didUndo || didRedo) {
|
|
262
507
|
// Capture the previous snapshot before RAF fires
|
|
263
508
|
const snapshotBefore = prevNodesSnapshot;
|
|
264
|
-
//
|
|
265
|
-
|
|
509
|
+
// Defer to a microtask so the scene store has settled before we diff,
|
|
510
|
+
// but still mark walls/items dirty before the next paint.
|
|
511
|
+
queueMicrotask(() => {
|
|
266
512
|
const currentNodes = useScene.getState().nodes;
|
|
267
513
|
const { markDirty } = useScene.getState();
|
|
268
514
|
if (snapshotBefore) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B,OAAO,KAAK,EAAa,WAAW,EAAE,MAAM,cAAc,CAAA;AAc1D,eAAO,MAAM,aAAa,YAuBzB,CAAA;AAqBD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,WAAW,GAAG,KAAK,CAAC,cAAc,CAgDtF"}
|
|
@@ -2,6 +2,12 @@ import { useFrame } from '@react-three/fiber';
|
|
|
2
2
|
import * as THREE from 'three';
|
|
3
3
|
import { sceneRegistry } from '../../hooks/scene-registry/scene-registry';
|
|
4
4
|
import useScene from '../../store/use-scene';
|
|
5
|
+
function ensureUv2Attribute(geometry) {
|
|
6
|
+
const uv = geometry.getAttribute('uv');
|
|
7
|
+
if (!uv)
|
|
8
|
+
return;
|
|
9
|
+
geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(Array.from(uv.array), 2));
|
|
10
|
+
}
|
|
5
11
|
// ============================================================================
|
|
6
12
|
// CEILING SYSTEM
|
|
7
13
|
// ============================================================================
|
|
@@ -81,5 +87,6 @@ export function generateCeilingGeometry(ceilingNode) {
|
|
|
81
87
|
// Rotate so the shape lies flat in X-Z plane
|
|
82
88
|
geometry.rotateX(-Math.PI / 2);
|
|
83
89
|
geometry.computeVertexNormals();
|
|
90
|
+
ensureUv2Attribute(geometry);
|
|
84
91
|
return geometry;
|
|
85
92
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fence-system.d.ts","sourceRoot":"","sources":["../../../src/systems/fence/fence-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"fence-system.d.ts","sourceRoot":"","sources":["../../../src/systems/fence/fence-system.tsx"],"names":[],"mappings":"AAyQA,eAAO,MAAM,WAAW,YAiBvB,CAAA"}
|