@pascal-app/viewer 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/error-boundary.d.ts +6 -0
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/error-boundary.js +6 -0
- package/dist/components/renderers/node-renderer.d.ts +5 -1
- package/dist/components/renderers/node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/node-renderer.js +5 -2
- package/dist/components/renderers/parametric-node-renderer.d.ts.map +1 -1
- package/dist/components/renderers/parametric-node-renderer.js +7 -4
- package/dist/components/viewer/bvh-ecctrl.d.ts +77 -0
- package/dist/components/viewer/bvh-ecctrl.d.ts.map +1 -0
- package/dist/components/viewer/bvh-ecctrl.js +552 -0
- package/dist/components/viewer/frame-limiter.d.ts.map +1 -1
- package/dist/components/viewer/frame-limiter.js +39 -4
- package/dist/components/viewer/glb-interactive.d.ts +41 -0
- package/dist/components/viewer/glb-interactive.d.ts.map +1 -0
- package/dist/components/viewer/glb-interactive.js +394 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts +29 -0
- package/dist/components/viewer/glb-reference-nodes.d.ts.map +1 -0
- package/dist/components/viewer/glb-reference-nodes.js +80 -0
- package/dist/components/viewer/glb-replace-instances.d.ts +18 -0
- package/dist/components/viewer/glb-replace-instances.d.ts.map +1 -0
- package/dist/components/viewer/glb-replace-instances.js +61 -0
- package/dist/components/viewer/glb-scene.d.ts +54 -0
- package/dist/components/viewer/glb-scene.d.ts.map +1 -0
- package/dist/components/viewer/glb-scene.js +920 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts +12 -0
- package/dist/components/viewer/glb-walkthrough-controller.d.ts.map +1 -0
- package/dist/components/viewer/glb-walkthrough-controller.js +357 -0
- package/dist/components/viewer/index.d.ts +19 -2
- package/dist/components/viewer/index.d.ts.map +1 -1
- package/dist/components/viewer/index.js +104 -12
- package/dist/components/viewer/lights.d.ts.map +1 -1
- package/dist/components/viewer/lights.js +36 -5
- package/dist/components/viewer/perf-monitor.d.ts.map +1 -1
- package/dist/components/viewer/perf-monitor.js +17 -2
- package/dist/components/viewer/post-processing.d.ts +7 -1
- package/dist/components/viewer/post-processing.d.ts.map +1 -1
- package/dist/components/viewer/post-processing.js +179 -24
- package/dist/components/viewer/registered-systems.d.ts.map +1 -1
- package/dist/components/viewer/registered-systems.js +6 -4
- package/dist/components/viewer/scene-bvh.d.ts +2 -1
- package/dist/components/viewer/scene-bvh.d.ts.map +1 -1
- package/dist/components/viewer/scene-bvh.js +3 -0
- package/dist/components/viewer/scene-bvh.test.d.ts +2 -0
- package/dist/components/viewer/scene-bvh.test.d.ts.map +1 -0
- package/dist/components/viewer/scene-bvh.test.js +13 -0
- package/dist/components/viewer/scene-environment.d.ts +2 -0
- package/dist/components/viewer/scene-environment.d.ts.map +1 -0
- package/dist/components/viewer/scene-environment.js +79 -0
- package/dist/components/viewer/selection-manager.d.ts.map +1 -1
- package/dist/components/viewer/selection-manager.js +18 -10
- package/dist/components/viewer/walkthrough-controls.d.ts +1 -0
- package/dist/components/viewer/walkthrough-controls.d.ts.map +1 -1
- package/dist/components/viewer/walkthrough-controls.js +20 -0
- package/dist/hooks/use-gltf-ktx2.d.ts.map +1 -1
- package/dist/hooks/use-gltf-ktx2.js +3 -22
- package/dist/hooks/use-node-events.d.ts +1 -1
- package/dist/hooks/use-node-events.d.ts.map +1 -1
- package/dist/hooks/use-node-events.js +22 -14
- package/dist/index.d.ts +19 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -8
- package/dist/lib/backdrop.d.ts +33 -0
- package/dist/lib/backdrop.d.ts.map +1 -0
- package/dist/lib/backdrop.js +92 -0
- package/dist/lib/box-uv.d.ts +13 -0
- package/dist/lib/box-uv.d.ts.map +1 -0
- package/dist/lib/box-uv.js +32 -0
- package/dist/lib/csg-utils.d.ts +3 -9
- package/dist/lib/csg-utils.d.ts.map +1 -1
- package/dist/lib/csg-utils.js +65 -5
- package/dist/lib/csg-utils.test.d.ts +2 -0
- package/dist/lib/csg-utils.test.d.ts.map +1 -0
- package/dist/lib/csg-utils.test.js +45 -0
- package/dist/lib/edge-style.d.ts +1 -0
- package/dist/lib/edge-style.d.ts.map +1 -1
- package/dist/lib/edge-style.js +26 -6
- package/dist/lib/ink-edges.d.ts +1 -1
- package/dist/lib/ink-edges.d.ts.map +1 -1
- package/dist/lib/ink-edges.js +13 -7
- package/dist/lib/ktx2-loader.d.ts +43 -0
- package/dist/lib/ktx2-loader.d.ts.map +1 -0
- package/dist/lib/ktx2-loader.js +136 -0
- package/dist/lib/layers.d.ts +10 -0
- package/dist/lib/layers.d.ts.map +1 -1
- package/dist/lib/layers.js +10 -0
- package/dist/lib/materials.d.ts +14 -1
- package/dist/lib/materials.d.ts.map +1 -1
- package/dist/lib/materials.js +103 -26
- package/dist/lib/scene-themes.d.ts +1 -0
- package/dist/lib/scene-themes.d.ts.map +1 -1
- package/dist/lib/scene-themes.js +25 -16
- package/dist/lib/shadow-only.d.ts +4 -0
- package/dist/lib/shadow-only.d.ts.map +1 -0
- package/dist/lib/shadow-only.js +36 -0
- package/dist/lib/tsl-compat.d.ts +12 -0
- package/dist/lib/tsl-compat.d.ts.map +1 -0
- package/dist/lib/tsl-compat.js +13 -0
- package/dist/store/use-viewer.d.ts +60 -18
- package/dist/store/use-viewer.d.ts.map +1 -1
- package/dist/store/use-viewer.js +175 -3
- package/dist/store/use-viewer.test.d.ts +2 -0
- package/dist/store/use-viewer.test.d.ts.map +1 -0
- package/dist/store/use-viewer.test.js +42 -0
- package/dist/systems/ceiling/ceiling-system.d.ts.map +1 -1
- package/dist/systems/ceiling/ceiling-system.js +4 -1
- package/dist/systems/door/door-animation.test.d.ts +2 -0
- package/dist/systems/door/door-animation.test.d.ts.map +1 -0
- package/dist/systems/door/door-animation.test.js +51 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts +2 -0
- package/dist/systems/door/door-floor-alignment.test.d.ts.map +1 -0
- package/dist/systems/door/door-floor-alignment.test.js +50 -0
- package/dist/systems/door/door-system.d.ts +24 -0
- package/dist/systems/door/door-system.d.ts.map +1 -1
- package/dist/systems/door/door-system.js +494 -161
- package/dist/systems/fence/fence-system.d.ts +17 -0
- package/dist/systems/fence/fence-system.d.ts.map +1 -1
- package/dist/systems/fence/fence-system.js +221 -53
- package/dist/systems/geometry/geometry-system.d.ts +7 -0
- package/dist/systems/geometry/geometry-system.d.ts.map +1 -1
- package/dist/systems/geometry/geometry-system.js +95 -26
- package/dist/systems/geometry/geometry-system.test.d.ts +2 -0
- package/dist/systems/geometry/geometry-system.test.d.ts.map +1 -0
- package/dist/systems/geometry/geometry-system.test.js +30 -0
- package/dist/systems/interactive/control-widget.d.ts +10 -0
- package/dist/systems/interactive/control-widget.d.ts.map +1 -0
- package/dist/systems/interactive/control-widget.js +35 -0
- package/dist/systems/interactive/interactive-system.d.ts.map +1 -1
- package/dist/systems/interactive/interactive-system.js +55 -56
- package/dist/systems/item/item-system.d.ts.map +1 -1
- package/dist/systems/item/item-system.js +20 -6
- package/dist/systems/level/level-system.d.ts.map +1 -1
- package/dist/systems/level/level-system.js +28 -4
- package/dist/systems/level/level-utils.d.ts +0 -3
- package/dist/systems/level/level-utils.d.ts.map +1 -1
- package/dist/systems/level/level-utils.js +2 -41
- package/dist/systems/roof/roof-materials.d.ts.map +1 -1
- package/dist/systems/roof/roof-materials.js +29 -8
- package/dist/systems/roof/roof-system.d.ts +21 -8
- package/dist/systems/roof/roof-system.d.ts.map +1 -1
- package/dist/systems/roof/roof-system.js +1079 -221
- package/dist/systems/slab/slab-system.d.ts +5 -3
- package/dist/systems/slab/slab-system.d.ts.map +1 -1
- package/dist/systems/slab/slab-system.js +41 -23
- package/dist/systems/slab/slab-system.test.js +3 -2
- package/dist/systems/stair/stair-system.js +6 -3
- package/dist/systems/wall/opening-cutout-geometry.d.ts +31 -0
- package/dist/systems/wall/opening-cutout-geometry.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.js +167 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts +2 -0
- package/dist/systems/wall/opening-cutout-geometry.test.d.ts.map +1 -0
- package/dist/systems/wall/opening-cutout-geometry.test.js +172 -0
- package/dist/systems/wall/wall-cutout.d.ts.map +1 -1
- package/dist/systems/wall/wall-cutout.js +46 -17
- package/dist/systems/wall/wall-materials.d.ts +11 -7
- package/dist/systems/wall/wall-materials.d.ts.map +1 -1
- package/dist/systems/wall/wall-materials.js +244 -35
- package/dist/systems/wall/wall-opening-cutout.test.d.ts +2 -0
- package/dist/systems/wall/wall-opening-cutout.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-opening-cutout.test.js +59 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts +2 -0
- package/dist/systems/wall/wall-support-extension.test.d.ts.map +1 -0
- package/dist/systems/wall/wall-support-extension.test.js +48 -0
- package/dist/systems/wall/wall-system.d.ts +2 -2
- package/dist/systems/wall/wall-system.d.ts.map +1 -1
- package/dist/systems/wall/wall-system.js +329 -174
- package/dist/systems/window/window-animation-system.d.ts +12 -0
- package/dist/systems/window/window-animation-system.d.ts.map +1 -1
- package/dist/systems/window/window-animation-system.js +16 -5
- package/dist/systems/window/window-system.d.ts +7 -0
- package/dist/systems/window/window-system.d.ts.map +1 -1
- package/dist/systems/window/window-system.js +240 -14
- package/package.json +4 -4
- package/dist/components/viewer/ground-occluder.d.ts +0 -2
- package/dist/components/viewer/ground-occluder.d.ts.map +0 -1
- package/dist/components/viewer/ground-occluder.js +0 -67
|
@@ -27,6 +27,23 @@ type ViewerState = {
|
|
|
27
27
|
setSceneTheme: (id: string) => void;
|
|
28
28
|
renderContext: RenderContext;
|
|
29
29
|
setRenderContext: (context: RenderContext) => void;
|
|
30
|
+
/** True during a GLB bake/export pass. Renderers that normally draw via a
|
|
31
|
+
* collective InstancedMesh (`def.system`) and mount only an invisible per-node
|
|
32
|
+
* proxy can watch this to emit real, visible geometry so the exporter — which
|
|
33
|
+
* clones only the `scene-renderer` subtree — captures them. Transient (never
|
|
34
|
+
* persisted). */
|
|
35
|
+
isExporting: boolean;
|
|
36
|
+
setExporting: (value: boolean) => void;
|
|
37
|
+
/** Item model loads that exhausted their retries — nodeId → asset URL. The
|
|
38
|
+
* scene renders without these items (they settle as skipped); a bake host
|
|
39
|
+
* can persist the map onto the artifact's metadata so a missing item is
|
|
40
|
+
* queryable instead of silently absent. Transient (never persisted). */
|
|
41
|
+
itemLoadFailures: Record<string, string>;
|
|
42
|
+
reportItemLoadFailure: (nodeId: string, url: string) => void;
|
|
43
|
+
clearItemLoadFailure: (nodeId: string) => void;
|
|
44
|
+
/** Suspend the render loop while the canvas is fully covered (e.g. studio gallery). */
|
|
45
|
+
renderPaused: boolean;
|
|
46
|
+
setRenderPaused: (value: boolean) => void;
|
|
30
47
|
shading: RenderShading;
|
|
31
48
|
shadingByContext: Partial<Record<RenderContext, RenderShading>>;
|
|
32
49
|
setShading: (shading: RenderShading) => void;
|
|
@@ -40,26 +57,43 @@ type ViewerState = {
|
|
|
40
57
|
setShadows: (shadows: boolean) => void;
|
|
41
58
|
unit: 'metric' | 'imperial';
|
|
42
59
|
setUnit: (unit: 'metric' | 'imperial') => void;
|
|
60
|
+
/** True once the user explicitly picked a unit. Until then `unit` is a
|
|
61
|
+
* locale-derived default and is not persisted, so the default can keep
|
|
62
|
+
* tracking the browser locale across sessions. */
|
|
63
|
+
unitExplicit: boolean;
|
|
43
64
|
levelMode: 'stacked' | 'exploded' | 'solo' | 'manual';
|
|
44
65
|
setLevelMode: (mode: 'stacked' | 'exploded' | 'solo' | 'manual') => void;
|
|
45
|
-
wallMode: 'up' | 'cutaway' | 'down';
|
|
46
|
-
setWallMode: (mode: 'up' | 'cutaway' | 'down') => void;
|
|
66
|
+
wallMode: 'up' | 'cutaway' | 'down' | 'translucent';
|
|
67
|
+
setWallMode: (mode: 'up' | 'cutaway' | 'down' | 'translucent') => void;
|
|
47
68
|
showScans: boolean;
|
|
48
69
|
setShowScans: (show: boolean) => void;
|
|
49
70
|
showGuides: boolean;
|
|
50
71
|
setShowGuides: (show: boolean) => void;
|
|
51
72
|
showGrid: boolean;
|
|
52
73
|
setShowGrid: (show: boolean) => void;
|
|
74
|
+
showMeasurements: boolean;
|
|
75
|
+
setShowMeasurements: (show: boolean) => void;
|
|
76
|
+
showZones: boolean;
|
|
77
|
+
setShowZones: (show: boolean) => void;
|
|
78
|
+
transparentBackground: boolean;
|
|
79
|
+
setTransparentBackground: (transparent: boolean) => void;
|
|
80
|
+
inkOpacity: number | null;
|
|
81
|
+
setInkOpacity: (opacity: number | null) => void;
|
|
53
82
|
projectId: string | null;
|
|
54
83
|
setProjectId: (id: string | null) => void;
|
|
55
84
|
projectPreferences: Record<string, {
|
|
56
85
|
showScans?: boolean;
|
|
57
86
|
showGuides?: boolean;
|
|
58
87
|
showGrid?: boolean;
|
|
88
|
+
showMeasurements?: boolean;
|
|
59
89
|
}>;
|
|
60
90
|
setSelection: (updates: Partial<SelectionPath>) => void;
|
|
61
91
|
resetSelection: () => void;
|
|
62
92
|
outliner: Outliner;
|
|
93
|
+
/** Bumped by GeometrySystem after each rebuild pass so selection/outline
|
|
94
|
+
* effects can re-apply to the freshly swapped meshes. */
|
|
95
|
+
geometryRevision: number;
|
|
96
|
+
bumpGeometryRevision: () => void;
|
|
63
97
|
exportScene: ((format?: 'glb' | 'stl' | 'obj') => Promise<void>) | null;
|
|
64
98
|
setExportScene: (fn: ((format?: 'glb' | 'stl' | 'obj') => Promise<void>) | null) => void;
|
|
65
99
|
debugColors: boolean;
|
|
@@ -86,6 +120,15 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
86
120
|
setState(state: ViewerState | ((state: ViewerState) => ViewerState), replace: true): unknown;
|
|
87
121
|
persist: {
|
|
88
122
|
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<ViewerState, {
|
|
123
|
+
levelMode: "manual" | "stacked" | "exploded" | "solo";
|
|
124
|
+
wallMode: "up" | "down" | "cutaway" | "translucent";
|
|
125
|
+
projectPreferences: Record<string, {
|
|
126
|
+
showScans?: boolean;
|
|
127
|
+
showGuides?: boolean;
|
|
128
|
+
showGrid?: boolean;
|
|
129
|
+
showMeasurements?: boolean;
|
|
130
|
+
}>;
|
|
131
|
+
unit?: "metric" | "imperial" | undefined;
|
|
89
132
|
cameraMode: "perspective" | "orthographic";
|
|
90
133
|
sceneTheme: string;
|
|
91
134
|
shadingByContext: Partial<Record<RenderContext, RenderShading>>;
|
|
@@ -93,14 +136,6 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
93
136
|
colorPreset: ColorPreset;
|
|
94
137
|
edges: EdgeMode;
|
|
95
138
|
shadows: boolean;
|
|
96
|
-
unit: "metric" | "imperial";
|
|
97
|
-
levelMode: "manual" | "stacked" | "exploded" | "solo";
|
|
98
|
-
wallMode: "up" | "down" | "cutaway";
|
|
99
|
-
projectPreferences: Record<string, {
|
|
100
|
-
showScans?: boolean;
|
|
101
|
-
showGuides?: boolean;
|
|
102
|
-
showGrid?: boolean;
|
|
103
|
-
}>;
|
|
104
139
|
}, unknown>>) => void;
|
|
105
140
|
clearStorage: () => void;
|
|
106
141
|
rehydrate: () => Promise<void> | void;
|
|
@@ -108,6 +143,15 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
108
143
|
onHydrate: (fn: (state: ViewerState) => void) => () => void;
|
|
109
144
|
onFinishHydration: (fn: (state: ViewerState) => void) => () => void;
|
|
110
145
|
getOptions: () => Partial<import("zustand/middleware").PersistOptions<ViewerState, {
|
|
146
|
+
levelMode: "manual" | "stacked" | "exploded" | "solo";
|
|
147
|
+
wallMode: "up" | "down" | "cutaway" | "translucent";
|
|
148
|
+
projectPreferences: Record<string, {
|
|
149
|
+
showScans?: boolean;
|
|
150
|
+
showGuides?: boolean;
|
|
151
|
+
showGrid?: boolean;
|
|
152
|
+
showMeasurements?: boolean;
|
|
153
|
+
}>;
|
|
154
|
+
unit?: "metric" | "imperial" | undefined;
|
|
111
155
|
cameraMode: "perspective" | "orthographic";
|
|
112
156
|
sceneTheme: string;
|
|
113
157
|
shadingByContext: Partial<Record<RenderContext, RenderShading>>;
|
|
@@ -115,16 +159,14 @@ declare const useViewer: import("zustand").UseBoundStore<Omit<import("zustand").
|
|
|
115
159
|
colorPreset: ColorPreset;
|
|
116
160
|
edges: EdgeMode;
|
|
117
161
|
shadows: boolean;
|
|
118
|
-
unit: "metric" | "imperial";
|
|
119
|
-
levelMode: "manual" | "stacked" | "exploded" | "solo";
|
|
120
|
-
wallMode: "up" | "down" | "cutaway";
|
|
121
|
-
projectPreferences: Record<string, {
|
|
122
|
-
showScans?: boolean;
|
|
123
|
-
showGuides?: boolean;
|
|
124
|
-
showGrid?: boolean;
|
|
125
|
-
}>;
|
|
126
162
|
}, unknown>>;
|
|
127
163
|
};
|
|
128
164
|
}>;
|
|
165
|
+
/** Apply an authoritative country code (e.g. IP-derived by the host app) as
|
|
166
|
+
* the unit default. Stronger signal than the timezone heuristic used at store
|
|
167
|
+
* creation, but still a default: it never overrides an explicit user choice
|
|
168
|
+
* and is not persisted (the unit only sticks once the user touches the
|
|
169
|
+
* toggle). */
|
|
170
|
+
export declare function applyCountryUnitDefault(country: string | null | undefined): void;
|
|
129
171
|
export default useViewer;
|
|
130
172
|
//# sourceMappingURL=use-viewer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC5F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAIrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"use-viewer.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.ts"],"names":[],"mappings":"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"}
|
package/dist/store/use-viewer.js
CHANGED
|
@@ -1,6 +1,123 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { create } from 'zustand';
|
|
3
3
|
import { persist } from 'zustand/middleware';
|
|
4
|
+
import { SCENE_THEME_IDS } from '../lib/scene-themes';
|
|
5
|
+
const CAMERA_MODES = ['perspective', 'orthographic'];
|
|
6
|
+
const RENDER_SHADINGS = ['solid', 'rendered'];
|
|
7
|
+
const COLOR_PRESETS = ['clay', 'white', 'mono', 'blueprint'];
|
|
8
|
+
const EDGE_MODES = ['off', 'soft', 'strong'];
|
|
9
|
+
const UNITS = ['metric', 'imperial'];
|
|
10
|
+
const LEVEL_MODES = ['stacked', 'exploded', 'solo', 'manual'];
|
|
11
|
+
const WALL_MODES = ['up', 'cutaway', 'down', 'translucent'];
|
|
12
|
+
// Countries still on imperial/US customary units: United States, Liberia, Myanmar.
|
|
13
|
+
const IMPERIAL_REGIONS = ['US', 'LR', 'MM'];
|
|
14
|
+
// IANA zones for those countries. The timezone tracks the OS clock (actual
|
|
15
|
+
// location), unlike navigator.language where en-US is a common default for
|
|
16
|
+
// users far outside the US.
|
|
17
|
+
const IMPERIAL_TIMEZONES = new Set([
|
|
18
|
+
'America/New_York',
|
|
19
|
+
'America/Detroit',
|
|
20
|
+
'America/Kentucky/Louisville',
|
|
21
|
+
'America/Kentucky/Monticello',
|
|
22
|
+
'America/Indiana/Indianapolis',
|
|
23
|
+
'America/Indiana/Vincennes',
|
|
24
|
+
'America/Indiana/Winamac',
|
|
25
|
+
'America/Indiana/Marengo',
|
|
26
|
+
'America/Indiana/Petersburg',
|
|
27
|
+
'America/Indiana/Vevay',
|
|
28
|
+
'America/Indiana/Tell_City',
|
|
29
|
+
'America/Indiana/Knox',
|
|
30
|
+
'America/Chicago',
|
|
31
|
+
'America/Menominee',
|
|
32
|
+
'America/North_Dakota/Center',
|
|
33
|
+
'America/North_Dakota/New_Salem',
|
|
34
|
+
'America/North_Dakota/Beulah',
|
|
35
|
+
'America/Denver',
|
|
36
|
+
'America/Boise',
|
|
37
|
+
'America/Phoenix',
|
|
38
|
+
'America/Los_Angeles',
|
|
39
|
+
'America/Anchorage',
|
|
40
|
+
'America/Juneau',
|
|
41
|
+
'America/Sitka',
|
|
42
|
+
'America/Metlakatla',
|
|
43
|
+
'America/Yakutat',
|
|
44
|
+
'America/Nome',
|
|
45
|
+
'America/Adak',
|
|
46
|
+
'Pacific/Honolulu',
|
|
47
|
+
'America/Puerto_Rico',
|
|
48
|
+
'Pacific/Guam',
|
|
49
|
+
'Africa/Monrovia', // Liberia
|
|
50
|
+
'Asia/Yangon', // Myanmar
|
|
51
|
+
'Asia/Rangoon', // Myanmar (legacy alias)
|
|
52
|
+
]);
|
|
53
|
+
function detectDefaultUnit() {
|
|
54
|
+
if (typeof navigator === 'undefined')
|
|
55
|
+
return 'metric';
|
|
56
|
+
try {
|
|
57
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
58
|
+
if (timeZone)
|
|
59
|
+
return IMPERIAL_TIMEZONES.has(timeZone) ? 'imperial' : 'metric';
|
|
60
|
+
// No timezone available: fall back to an explicit locale region subtag
|
|
61
|
+
// only (never maximize() — it turns a bare "en" into region US).
|
|
62
|
+
const region = new Intl.Locale(navigator.language).region;
|
|
63
|
+
return region && IMPERIAL_REGIONS.includes(region) ? 'imperial' : 'metric';
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return 'metric';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function pickString(value, allowed, fallback) {
|
|
70
|
+
return typeof value === 'string' && allowed.includes(value) ? value : fallback;
|
|
71
|
+
}
|
|
72
|
+
function normalizeShadingByContext(value) {
|
|
73
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
74
|
+
return {};
|
|
75
|
+
const next = {};
|
|
76
|
+
for (const [context, shading] of Object.entries(value)) {
|
|
77
|
+
if (context !== 'editor' && context !== 'viewer')
|
|
78
|
+
continue;
|
|
79
|
+
next[context] = pickString(shading, RENDER_SHADINGS, 'rendered');
|
|
80
|
+
}
|
|
81
|
+
return next;
|
|
82
|
+
}
|
|
83
|
+
function normalizeProjectPreferences(value) {
|
|
84
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
85
|
+
return {};
|
|
86
|
+
const next = {};
|
|
87
|
+
for (const [projectId, preferences] of Object.entries(value)) {
|
|
88
|
+
if (!preferences || typeof preferences !== 'object' || Array.isArray(preferences))
|
|
89
|
+
continue;
|
|
90
|
+
const record = preferences;
|
|
91
|
+
next[projectId] = {
|
|
92
|
+
...(typeof record.showScans === 'boolean' ? { showScans: record.showScans } : {}),
|
|
93
|
+
...(typeof record.showGuides === 'boolean' ? { showGuides: record.showGuides } : {}),
|
|
94
|
+
...(typeof record.showGrid === 'boolean' ? { showGrid: record.showGrid } : {}),
|
|
95
|
+
...(typeof record.showMeasurements === 'boolean'
|
|
96
|
+
? { showMeasurements: record.showMeasurements }
|
|
97
|
+
: {}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return next;
|
|
101
|
+
}
|
|
102
|
+
function normalizePersistedViewerState(value) {
|
|
103
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
104
|
+
return {};
|
|
105
|
+
const state = value;
|
|
106
|
+
return {
|
|
107
|
+
cameraMode: pickString(state.cameraMode, CAMERA_MODES, 'perspective'),
|
|
108
|
+
sceneTheme: pickString(state.sceneTheme, SCENE_THEME_IDS, 'studio'),
|
|
109
|
+
shadingByContext: normalizeShadingByContext(state.shadingByContext),
|
|
110
|
+
textures: typeof state.textures === 'boolean' ? state.textures : true,
|
|
111
|
+
colorPreset: pickString(state.colorPreset, COLOR_PRESETS, 'clay'),
|
|
112
|
+
edges: pickString(state.edges, EDGE_MODES, 'soft'),
|
|
113
|
+
shadows: typeof state.shadows === 'boolean' ? state.shadows : true,
|
|
114
|
+
unit: pickString(state.unit, UNITS, detectDefaultUnit()),
|
|
115
|
+
unitExplicit: typeof state.unit === 'string' && UNITS.includes(state.unit),
|
|
116
|
+
levelMode: pickString(state.levelMode, LEVEL_MODES, 'stacked'),
|
|
117
|
+
wallMode: pickString(state.wallMode, WALL_MODES, 'up'),
|
|
118
|
+
projectPreferences: normalizeProjectPreferences(state.projectPreferences),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
4
121
|
const useViewer = create()(persist((set) => ({
|
|
5
122
|
selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] },
|
|
6
123
|
previewSelectedIds: [],
|
|
@@ -15,6 +132,21 @@ const useViewer = create()(persist((set) => ({
|
|
|
15
132
|
setSceneTheme: (id) => set({ sceneTheme: id }),
|
|
16
133
|
renderContext: 'editor',
|
|
17
134
|
setRenderContext: (context) => set({ renderContext: context }),
|
|
135
|
+
isExporting: false,
|
|
136
|
+
setExporting: (value) => set({ isExporting: value }),
|
|
137
|
+
itemLoadFailures: {},
|
|
138
|
+
reportItemLoadFailure: (nodeId, url) => set((state) => state.itemLoadFailures[nodeId] === url
|
|
139
|
+
? state
|
|
140
|
+
: { itemLoadFailures: { ...state.itemLoadFailures, [nodeId]: url } }),
|
|
141
|
+
clearItemLoadFailure: (nodeId) => set((state) => {
|
|
142
|
+
if (!(nodeId in state.itemLoadFailures))
|
|
143
|
+
return state;
|
|
144
|
+
const next = { ...state.itemLoadFailures };
|
|
145
|
+
delete next[nodeId];
|
|
146
|
+
return { itemLoadFailures: next };
|
|
147
|
+
}),
|
|
148
|
+
renderPaused: false,
|
|
149
|
+
setRenderPaused: (value) => set({ renderPaused: value }),
|
|
18
150
|
shading: 'rendered',
|
|
19
151
|
shadingByContext: {},
|
|
20
152
|
setShading: (shading) => set((state) => ({
|
|
@@ -29,8 +161,9 @@ const useViewer = create()(persist((set) => ({
|
|
|
29
161
|
setEdges: (edges) => set({ edges }),
|
|
30
162
|
shadows: true,
|
|
31
163
|
setShadows: (shadows) => set({ shadows }),
|
|
32
|
-
unit:
|
|
33
|
-
|
|
164
|
+
unit: detectDefaultUnit(),
|
|
165
|
+
unitExplicit: false,
|
|
166
|
+
setUnit: (unit) => set({ unit, unitExplicit: true }),
|
|
34
167
|
levelMode: 'stacked',
|
|
35
168
|
setLevelMode: (mode) => set({ levelMode: mode }),
|
|
36
169
|
wallMode: 'up',
|
|
@@ -68,6 +201,23 @@ const useViewer = create()(persist((set) => ({
|
|
|
68
201
|
}
|
|
69
202
|
return { showGrid: show, projectPreferences };
|
|
70
203
|
}),
|
|
204
|
+
showMeasurements: true,
|
|
205
|
+
setShowMeasurements: (show) => set((state) => {
|
|
206
|
+
const projectPreferences = { ...(state.projectPreferences || {}) };
|
|
207
|
+
if (state.projectId) {
|
|
208
|
+
projectPreferences[state.projectId] = {
|
|
209
|
+
...(projectPreferences[state.projectId] || {}),
|
|
210
|
+
showMeasurements: show,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return { showMeasurements: show, projectPreferences };
|
|
214
|
+
}),
|
|
215
|
+
showZones: true,
|
|
216
|
+
setShowZones: (show) => set({ showZones: show }),
|
|
217
|
+
transparentBackground: false,
|
|
218
|
+
setTransparentBackground: (transparent) => set({ transparentBackground: transparent }),
|
|
219
|
+
inkOpacity: null,
|
|
220
|
+
setInkOpacity: (opacity) => set({ inkOpacity: opacity }),
|
|
71
221
|
projectId: null,
|
|
72
222
|
setProjectId: (id) => set((state) => {
|
|
73
223
|
if (!id)
|
|
@@ -78,6 +228,7 @@ const useViewer = create()(persist((set) => ({
|
|
|
78
228
|
showScans: prefs.showScans ?? true,
|
|
79
229
|
showGuides: prefs.showGuides ?? true,
|
|
80
230
|
showGrid: prefs.showGrid ?? true,
|
|
231
|
+
showMeasurements: prefs.showMeasurements ?? true,
|
|
81
232
|
};
|
|
82
233
|
}),
|
|
83
234
|
projectPreferences: {},
|
|
@@ -114,6 +265,8 @@ const useViewer = create()(persist((set) => ({
|
|
|
114
265
|
previewSelectedIds: [],
|
|
115
266
|
}),
|
|
116
267
|
outliner: { selectedObjects: [], hoveredObjects: [] },
|
|
268
|
+
geometryRevision: 0,
|
|
269
|
+
bumpGeometryRevision: () => set((state) => ({ geometryRevision: state.geometryRevision + 1 })),
|
|
117
270
|
exportScene: null,
|
|
118
271
|
setExportScene: (fn) => set({ exportScene: fn }),
|
|
119
272
|
debugColors: false,
|
|
@@ -126,6 +279,10 @@ const useViewer = create()(persist((set) => ({
|
|
|
126
279
|
setInputDragging: (dragging) => set({ inputDragging: dragging }),
|
|
127
280
|
}), {
|
|
128
281
|
name: 'viewer-preferences',
|
|
282
|
+
merge: (persistedState, currentState) => ({
|
|
283
|
+
...currentState,
|
|
284
|
+
...normalizePersistedViewerState(persistedState),
|
|
285
|
+
}),
|
|
129
286
|
partialize: (state) => ({
|
|
130
287
|
cameraMode: state.cameraMode,
|
|
131
288
|
sceneTheme: state.sceneTheme,
|
|
@@ -134,10 +291,25 @@ const useViewer = create()(persist((set) => ({
|
|
|
134
291
|
colorPreset: state.colorPreset,
|
|
135
292
|
edges: state.edges,
|
|
136
293
|
shadows: state.shadows,
|
|
137
|
-
unit: state.unit,
|
|
294
|
+
...(state.unitExplicit ? { unit: state.unit } : {}),
|
|
138
295
|
levelMode: state.levelMode,
|
|
139
296
|
wallMode: state.wallMode,
|
|
140
297
|
projectPreferences: state.projectPreferences,
|
|
141
298
|
}),
|
|
142
299
|
}));
|
|
300
|
+
/** Apply an authoritative country code (e.g. IP-derived by the host app) as
|
|
301
|
+
* the unit default. Stronger signal than the timezone heuristic used at store
|
|
302
|
+
* creation, but still a default: it never overrides an explicit user choice
|
|
303
|
+
* and is not persisted (the unit only sticks once the user touches the
|
|
304
|
+
* toggle). */
|
|
305
|
+
export function applyCountryUnitDefault(country) {
|
|
306
|
+
if (!country)
|
|
307
|
+
return;
|
|
308
|
+
const state = useViewer.getState();
|
|
309
|
+
if (state.unitExplicit)
|
|
310
|
+
return;
|
|
311
|
+
const unit = IMPERIAL_REGIONS.includes(country.toUpperCase()) ? 'imperial' : 'metric';
|
|
312
|
+
if (state.unit !== unit)
|
|
313
|
+
useViewer.setState({ unit });
|
|
314
|
+
}
|
|
143
315
|
export default useViewer;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-viewer.test.d.ts","sourceRoot":"","sources":["../../src/store/use-viewer.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
|
|
2
|
+
// include Bun ambient types in its production declaration build.
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
4
|
+
import useViewer from './use-viewer';
|
|
5
|
+
const resetMeasurementPreferences = () => {
|
|
6
|
+
useViewer.setState({
|
|
7
|
+
projectId: null,
|
|
8
|
+
projectPreferences: {},
|
|
9
|
+
showMeasurements: true,
|
|
10
|
+
unit: 'metric',
|
|
11
|
+
});
|
|
12
|
+
};
|
|
13
|
+
beforeEach(resetMeasurementPreferences);
|
|
14
|
+
afterEach(resetMeasurementPreferences);
|
|
15
|
+
describe('measurement display preferences', () => {
|
|
16
|
+
test('stores global visibility per project and defaults new projects to visible', () => {
|
|
17
|
+
const viewer = useViewer.getState();
|
|
18
|
+
viewer.setProjectId('project-a');
|
|
19
|
+
viewer.setShowMeasurements(false);
|
|
20
|
+
expect(useViewer.getState()).toMatchObject({
|
|
21
|
+
projectId: 'project-a',
|
|
22
|
+
showMeasurements: false,
|
|
23
|
+
projectPreferences: {
|
|
24
|
+
'project-a': { showMeasurements: false },
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
useViewer.getState().setProjectId('project-b');
|
|
28
|
+
expect(useViewer.getState().showMeasurements).toBe(true);
|
|
29
|
+
useViewer.getState().setProjectId('project-a');
|
|
30
|
+
expect(useViewer.getState().showMeasurements).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
test('changes display units without changing measurement visibility preferences', () => {
|
|
33
|
+
const viewer = useViewer.getState();
|
|
34
|
+
viewer.setProjectId('project-a');
|
|
35
|
+
viewer.setShowMeasurements(false);
|
|
36
|
+
const preferences = useViewer.getState().projectPreferences;
|
|
37
|
+
useViewer.getState().setUnit('imperial');
|
|
38
|
+
expect(useViewer.getState().unit).toBe('imperial');
|
|
39
|
+
expect(useViewer.getState().projectPreferences).toEqual(preferences);
|
|
40
|
+
expect(useViewer.getState().showMeasurements).toBe(false);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAMjB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAgB9B,eAAO,MAAM,aAAa,YA4BzB,CAAA;AA2DD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,UAAU,GAAE,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAM,GAC9C,KAAK,CAAC,cAAc,
|
|
1
|
+
{"version":3,"file":"ceiling-system.d.ts","sourceRoot":"","sources":["../../../src/systems/ceiling/ceiling-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAMjB,MAAM,kBAAkB,CAAA;AAEzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAgB9B,eAAO,MAAM,aAAa,YA4BzB,CAAA;AA2DD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,WAAW,EACxB,UAAU,GAAE,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAM,GAC9C,KAAK,CAAC,cAAc,CA6DtB"}
|
|
@@ -21,7 +21,7 @@ export const CeilingSystem = () => {
|
|
|
21
21
|
// Process dirty ceilings
|
|
22
22
|
dirtyNodes.forEach((id) => {
|
|
23
23
|
const node = nodes[id];
|
|
24
|
-
if (
|
|
24
|
+
if (node?.type !== 'ceiling')
|
|
25
25
|
return;
|
|
26
26
|
const mesh = sceneRegistry.nodes.get(id);
|
|
27
27
|
if (mesh) {
|
|
@@ -101,6 +101,9 @@ export function generateCeilingGeometry(ceilingNode, extraHoles = []) {
|
|
|
101
101
|
// the whole command encoder.
|
|
102
102
|
const degenerate = new THREE.BufferGeometry();
|
|
103
103
|
degenerate.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array(9), 3));
|
|
104
|
+
degenerate.setAttribute('normal', new THREE.Float32BufferAttribute(new Float32Array(9), 3));
|
|
105
|
+
degenerate.setAttribute('uv', new THREE.Float32BufferAttribute(new Float32Array(6), 2));
|
|
106
|
+
degenerate.setAttribute('uv2', new THREE.Float32BufferAttribute(new Float32Array(6), 2));
|
|
104
107
|
return degenerate;
|
|
105
108
|
}
|
|
106
109
|
// Create shape from polygon
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"door-animation.test.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-animation.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
|
|
2
|
+
// depend on @types/bun so the import type is unresolved at compile time.
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
import { DoorNode } from '@pascal-app/core';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import { buildDoorPreviewMesh, poseDoorMovingParts } from '../../index';
|
|
7
|
+
// The named moving group each operation-door builder emits at the closed pose.
|
|
8
|
+
const MOVING_GROUP = {
|
|
9
|
+
sliding: 'door-sliding-active',
|
|
10
|
+
pocket: 'door-pocket-leaf',
|
|
11
|
+
barn: 'door-barn-leaf',
|
|
12
|
+
'garage-tiltup': 'door-tiltup-leaf',
|
|
13
|
+
folding: 'door-fold-0',
|
|
14
|
+
'garage-sectional': 'door-sectional-0',
|
|
15
|
+
'garage-rollup': 'door-rollup-curtain',
|
|
16
|
+
};
|
|
17
|
+
function worldPosition(object) {
|
|
18
|
+
object.updateMatrixWorld(true);
|
|
19
|
+
return object.getWorldPosition(new THREE.Vector3());
|
|
20
|
+
}
|
|
21
|
+
describe('operation door open kinematics', () => {
|
|
22
|
+
// Each operation door builds its moving parts in a named group at the closed
|
|
23
|
+
// pose; `poseDoorMovingParts` then drives the open motion (the single source
|
|
24
|
+
// of truth shared by the live system and the GLB exporter). This guards the
|
|
25
|
+
// build-once + pose-at-t contract: the group exists, rests closed, and moves.
|
|
26
|
+
for (const doorType of Object.keys(MOVING_GROUP)) {
|
|
27
|
+
test(`${doorType} builds a moving group that opens`, () => {
|
|
28
|
+
const node = DoorNode.parse({ id: `door_${doorType}`, doorType, operationState: 0 });
|
|
29
|
+
const mesh = buildDoorPreviewMesh(node);
|
|
30
|
+
const group = mesh.getObjectByName(MOVING_GROUP[doorType]);
|
|
31
|
+
expect(group).toBeDefined();
|
|
32
|
+
// Closed snapshot.
|
|
33
|
+
poseDoorMovingParts(node, mesh, 0);
|
|
34
|
+
const closedPos = worldPosition(group);
|
|
35
|
+
const closedScaleY = group.scale.y;
|
|
36
|
+
const closedRotX = group.rotation.x;
|
|
37
|
+
const closedRotY = group.rotation.y;
|
|
38
|
+
// Open snapshot — at least one of translation / rotation / scale changes.
|
|
39
|
+
poseDoorMovingParts(node, mesh, 1);
|
|
40
|
+
const openPos = worldPosition(group);
|
|
41
|
+
const moved = closedPos.distanceTo(openPos) > 0.05 ||
|
|
42
|
+
Math.abs(closedScaleY - group.scale.y) > 0.1 ||
|
|
43
|
+
Math.abs(closedRotX - group.rotation.x) > 0.05 ||
|
|
44
|
+
Math.abs(closedRotY - group.rotation.y) > 0.05;
|
|
45
|
+
expect(moved).toBe(true);
|
|
46
|
+
// Posing back to closed is the identity of the rest pose.
|
|
47
|
+
poseDoorMovingParts(node, mesh, 0);
|
|
48
|
+
expect(worldPosition(group).distanceTo(closedPos)).toBeLessThan(1e-6);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"door-floor-alignment.test.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-floor-alignment.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// @ts-expect-error — bun:test is provided by the Bun runtime; viewer does not
|
|
2
|
+
// depend on @types/bun so the import type is unresolved at compile time.
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
import { DoorNode } from '@pascal-app/core';
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import { buildDoorPreviewMesh } from '../../index';
|
|
7
|
+
const DOOR_TYPES = [
|
|
8
|
+
'hinged',
|
|
9
|
+
'double',
|
|
10
|
+
'french',
|
|
11
|
+
'folding',
|
|
12
|
+
'pocket',
|
|
13
|
+
'barn',
|
|
14
|
+
'sliding',
|
|
15
|
+
'garage-sectional',
|
|
16
|
+
'garage-rollup',
|
|
17
|
+
'garage-tiltup',
|
|
18
|
+
];
|
|
19
|
+
function visibleBounds(mesh) {
|
|
20
|
+
mesh.updateMatrixWorld(true);
|
|
21
|
+
const bounds = new THREE.Box3();
|
|
22
|
+
for (const child of mesh.children) {
|
|
23
|
+
if (child.name === 'cutout')
|
|
24
|
+
continue;
|
|
25
|
+
bounds.expandByObject(child, true);
|
|
26
|
+
}
|
|
27
|
+
return bounds;
|
|
28
|
+
}
|
|
29
|
+
describe('door floor alignment', () => {
|
|
30
|
+
for (const doorType of DOOR_TYPES) {
|
|
31
|
+
test(`${doorType} does not extend below the opening floor`, () => {
|
|
32
|
+
const node = DoorNode.parse({
|
|
33
|
+
id: `door_floor-alignment-${doorType}`,
|
|
34
|
+
doorType,
|
|
35
|
+
operationState: 0,
|
|
36
|
+
threshold: true,
|
|
37
|
+
});
|
|
38
|
+
const mesh = buildDoorPreviewMesh(node);
|
|
39
|
+
const bounds = visibleBounds(mesh);
|
|
40
|
+
expect(bounds.min.y).toBeGreaterThanOrEqual(-node.height / 2 - 1e-6);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
test('keeps the wall cutout bottom locked to the opening floor', () => {
|
|
44
|
+
const node = DoorNode.parse({ id: 'door_floor-alignment-cutout' });
|
|
45
|
+
const mesh = buildDoorPreviewMesh(node);
|
|
46
|
+
const cutout = mesh.getObjectByName('cutout');
|
|
47
|
+
cutout.geometry.computeBoundingBox();
|
|
48
|
+
expect(cutout.geometry.boundingBox?.min.y).toBeCloseTo(-node.height / 2, 6);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -1,2 +1,26 @@
|
|
|
1
|
+
import { type DoorNode } from '@pascal-app/core';
|
|
2
|
+
import * as THREE from 'three';
|
|
1
3
|
export declare const DoorSystem: () => null;
|
|
4
|
+
/**
|
|
5
|
+
* Pose an operation door's moving parts (sliding/pocket/barn leaf, tilt-up and
|
|
6
|
+
* sectional panels, folding chain, roll-up curtain) at `value` (0 = closed,
|
|
7
|
+
* 1 = open) by transforming the named groups its builder emitted at the closed
|
|
8
|
+
* pose. Returns true when the door type has a pose path and its groups exist.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors `poseWindowMovingParts`: the live door system calls it after a
|
|
11
|
+
* (re)build so `operationState` is reflected, and the GLB exporter calls it on a
|
|
12
|
+
* clone to sample keyframes. Swing doors are not handled here — their leaf group
|
|
13
|
+
* carries a `pascalSwingLeaf` marker the exporter reads directly.
|
|
14
|
+
*
|
|
15
|
+
* Roll-up is the one type whose live geometry changes (slats vanish onto a
|
|
16
|
+
* drum), which a glTF clip can't express; the baked approximation scales the
|
|
17
|
+
* curtain up into the lintel. The live roll-up animation keeps its full-detail
|
|
18
|
+
* rebuild and is intentionally NOT routed through this scale.
|
|
19
|
+
*/
|
|
20
|
+
export declare function poseDoorMovingParts(node: DoorNode, mesh: THREE.Object3D | undefined, value: number): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Build a fresh door mesh for preview/ghost rendering.
|
|
23
|
+
* Returns a mesh with an invisible hitbox root and visible children (frame, panels, hardware).
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildDoorPreviewMesh(node: DoorNode): THREE.Mesh;
|
|
2
26
|
//# sourceMappingURL=door-system.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"door-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-system.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"door-system.d.ts","sourceRoot":"","sources":["../../../src/systems/door/door-system.tsx"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,QAAQ,EAYd,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA0D9B,eAAO,MAAM,UAAU,YA6GtB,CAAA;AAupCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,EACd,IAAI,EAAE,KAAK,CAAC,QAAQ,GAAG,SAAS,EAChC,KAAK,EAAE,MAAM,GACZ,OAAO,CA6GT;AAyuCD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,QAAQ,GAAG,KAAK,CAAC,IAAI,CAI/D"}
|