@selvajs/compute 2.1.0-beta.0 → 2.1.0-beta.1

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.
Files changed (40) hide show
  1. package/dist/chunk-JPXUC3P5.cjs +54 -0
  2. package/dist/chunk-JPXUC3P5.cjs.map +1 -0
  3. package/dist/chunk-MA6YB3YZ.cjs.map +1 -1
  4. package/dist/{chunk-6GCGLBJM.cjs → chunk-P4SF7AKZ.cjs} +2 -2
  5. package/dist/chunk-P4SF7AKZ.cjs.map +1 -0
  6. package/dist/{chunk-QUD7W5K3.js → chunk-SKAHIC4G.js} +2 -2
  7. package/dist/chunk-Z4TVQVMV.js +54 -0
  8. package/dist/chunk-Z4TVQVMV.js.map +1 -0
  9. package/dist/core.cjs.map +1 -1
  10. package/dist/grasshopper.cjs +1 -1
  11. package/dist/grasshopper.cjs.map +1 -1
  12. package/dist/grasshopper.d.cts +2 -2
  13. package/dist/grasshopper.d.ts +2 -2
  14. package/dist/grasshopper.js +1 -1
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/{types-DgepKOuU.d.cts → types-BuRCHPlb.d.cts} +6 -1
  21. package/dist/{types-B6KrSthC.d.ts → types-jPuWWtnU.d.ts} +6 -1
  22. package/dist/visualization-R7QUUUWV.js +2 -0
  23. package/dist/visualization-ZDXKGD2L.cjs +2 -0
  24. package/dist/visualization-ZDXKGD2L.cjs.map +1 -0
  25. package/dist/visualization.cjs +1 -1
  26. package/dist/visualization.cjs.map +1 -1
  27. package/dist/visualization.d.cts +173 -2
  28. package/dist/visualization.d.ts +173 -2
  29. package/dist/visualization.js +1 -1
  30. package/package.json +130 -133
  31. package/dist/chunk-6GCGLBJM.cjs.map +0 -1
  32. package/dist/chunk-KAULD2XQ.cjs +0 -2
  33. package/dist/chunk-KAULD2XQ.cjs.map +0 -1
  34. package/dist/chunk-SP73WPYA.js +0 -2
  35. package/dist/chunk-SP73WPYA.js.map +0 -1
  36. package/dist/visualization-5D5Y547Q.cjs +0 -2
  37. package/dist/visualization-5D5Y547Q.cjs.map +0 -1
  38. package/dist/visualization-JF4W754M.js +0 -2
  39. /package/dist/{chunk-QUD7W5K3.js.map → chunk-SKAHIC4G.js.map} +0 -0
  40. /package/dist/{visualization-JF4W754M.js.map → visualization-R7QUUUWV.js.map} +0 -0
@@ -355,12 +355,17 @@ interface DisplayPosition {
355
355
  }
356
356
  /**
357
357
  * A curve shipped as Rhino-native JSON (`curve.ToNurbsCurve().ToJSON()`), decoded via rhino3dm and
358
- * tessellated to a `THREE.Line` on the web.
358
+ * tessellated to a fat `Line2` on the web (so {@link DisplayCurve.width} is honoured).
359
359
  */
360
360
  interface DisplayCurve extends DisplayItemBase {
361
361
  kind: 'curve';
362
362
  /** Rhino CommonObject JSON for the curve. */
363
363
  json: string;
364
+ /**
365
+ * Line thickness in CSS pixels (screen-space, constant regardless of zoom). Rendered via a fat
366
+ * `Line2`, so unlike `THREE.Line` this is actually honoured. Omitted → viewer default.
367
+ */
368
+ width?: number;
364
369
  }
365
370
  /** A single point, shipped raw (no rhino3dm decode), rendered as one vertex of a `THREE.Points`. */
366
371
  interface DisplayPoint extends DisplayItemBase {
@@ -355,12 +355,17 @@ interface DisplayPosition {
355
355
  }
356
356
  /**
357
357
  * A curve shipped as Rhino-native JSON (`curve.ToNurbsCurve().ToJSON()`), decoded via rhino3dm and
358
- * tessellated to a `THREE.Line` on the web.
358
+ * tessellated to a fat `Line2` on the web (so {@link DisplayCurve.width} is honoured).
359
359
  */
360
360
  interface DisplayCurve extends DisplayItemBase {
361
361
  kind: 'curve';
362
362
  /** Rhino CommonObject JSON for the curve. */
363
363
  json: string;
364
+ /**
365
+ * Line thickness in CSS pixels (screen-space, constant regardless of zoom). Rendered via a fat
366
+ * `Line2`, so unlike `THREE.Line` this is actually honoured. Omitted → viewer default.
367
+ */
368
+ width?: number;
364
369
  }
365
370
  /** A single point, shipped raw (no rhino3dm decode), rendered as one vertex of a `THREE.Points`. */
366
371
  interface DisplayPoint extends DisplayItemBase {
@@ -0,0 +1,2 @@
1
+ import{A as u,a as e,b as r,c as o,d as t,e as a,f as s,g as p,h as i,i as n,j as m,k as l,l as f,m as y,n as d,o as h,p as x,q as C,r as g,s as M,t as c,u as B,v as j,w as E,x as D,y as O,z as b}from"./chunk-Z4TVQVMV.js";import"./chunk-GTTKNF4G.js";export{M as BINARY_MESH_MAGIC,c as BINARY_MESH_VERSION,i as EDGE_USERDATA_KIND,B as FLAG_FLOAT32,C as Materials,b as SCALE_FACTORS,n as addEdges,o as applyOffset,t as computeCombinedBoundingBox,a as createCameraController,s as createGrid,y as createLabelLayer,h as createMeasureTool,f as createRenderPipeline,p as createViewGizmo,u as getThreeMeshesFromComputeResponse,x as initThree,m as isEdgeOverlay,j as parseBinaryMeshBatch,r as parseColor,g as parseDisplayItems,E as parseMeshBatch,O as parseMeshBatchBlob,D as parseMeshBatchObject,l as removeEdges,d as snapToVertex,e as updateScene};
2
+ //# sourceMappingURL=visualization-R7QUUUWV.js.map
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkJPXUC3P5cjs = require('./chunk-JPXUC3P5.cjs');require('./chunk-MA6YB3YZ.cjs');exports.BINARY_MESH_MAGIC = _chunkJPXUC3P5cjs.s; exports.BINARY_MESH_VERSION = _chunkJPXUC3P5cjs.t; exports.EDGE_USERDATA_KIND = _chunkJPXUC3P5cjs.h; exports.FLAG_FLOAT32 = _chunkJPXUC3P5cjs.u; exports.Materials = _chunkJPXUC3P5cjs.q; exports.SCALE_FACTORS = _chunkJPXUC3P5cjs.z; exports.addEdges = _chunkJPXUC3P5cjs.i; exports.applyOffset = _chunkJPXUC3P5cjs.c; exports.computeCombinedBoundingBox = _chunkJPXUC3P5cjs.d; exports.createCameraController = _chunkJPXUC3P5cjs.e; exports.createGrid = _chunkJPXUC3P5cjs.f; exports.createLabelLayer = _chunkJPXUC3P5cjs.m; exports.createMeasureTool = _chunkJPXUC3P5cjs.o; exports.createRenderPipeline = _chunkJPXUC3P5cjs.l; exports.createViewGizmo = _chunkJPXUC3P5cjs.g; exports.getThreeMeshesFromComputeResponse = _chunkJPXUC3P5cjs.A; exports.initThree = _chunkJPXUC3P5cjs.p; exports.isEdgeOverlay = _chunkJPXUC3P5cjs.j; exports.parseBinaryMeshBatch = _chunkJPXUC3P5cjs.v; exports.parseColor = _chunkJPXUC3P5cjs.b; exports.parseDisplayItems = _chunkJPXUC3P5cjs.r; exports.parseMeshBatch = _chunkJPXUC3P5cjs.w; exports.parseMeshBatchBlob = _chunkJPXUC3P5cjs.y; exports.parseMeshBatchObject = _chunkJPXUC3P5cjs.x; exports.removeEdges = _chunkJPXUC3P5cjs.k; exports.snapToVertex = _chunkJPXUC3P5cjs.n; exports.updateScene = _chunkJPXUC3P5cjs.a;
2
+ //# sourceMappingURL=visualization-ZDXKGD2L.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/selva-compute/selva-compute/dist/visualization-ZDXKGD2L.cjs"],"names":[],"mappings":"AAAA,iIAA8N,gCAA6B,owCAA+kB","file":"/home/runner/work/selva-compute/selva-compute/dist/visualization-ZDXKGD2L.cjs"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkKAULD2XQcjs = require('./chunk-KAULD2XQ.cjs');require('./chunk-MA6YB3YZ.cjs');exports.Materials = _chunkKAULD2XQcjs.f; exports.SCALE_FACTORS = _chunkKAULD2XQcjs.o; exports.getThreeMeshesFromComputeResponse = _chunkKAULD2XQcjs.p; exports.initThree = _chunkKAULD2XQcjs.a; exports.parseDisplayItems = _chunkKAULD2XQcjs.g; exports.parseMeshBatchBlob = _chunkKAULD2XQcjs.n; exports.parseMeshBatchObject = _chunkKAULD2XQcjs.m; exports.updateScene = _chunkKAULD2XQcjs.b;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkJPXUC3P5cjs = require('./chunk-JPXUC3P5.cjs');require('./chunk-MA6YB3YZ.cjs');exports.Materials = _chunkJPXUC3P5cjs.q; exports.SCALE_FACTORS = _chunkJPXUC3P5cjs.z; exports.getThreeMeshesFromComputeResponse = _chunkJPXUC3P5cjs.A; exports.initThree = _chunkJPXUC3P5cjs.p; exports.parseDisplayItems = _chunkJPXUC3P5cjs.r; exports.parseMeshBatchBlob = _chunkJPXUC3P5cjs.y; exports.parseMeshBatchObject = _chunkJPXUC3P5cjs.x; exports.updateScene = _chunkJPXUC3P5cjs.a;
2
2
  //# sourceMappingURL=visualization.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["c:\\Users\\felix\\coding\\selva-compute\\dist\\visualization.cjs"],"names":[],"mappings":"AAAA,iIAAoF,gCAA6B,iYAA0L","file":"C:\\Users\\felix\\coding\\selva-compute\\dist\\visualization.cjs"}
1
+ {"version":3,"sources":["/home/runner/work/selva-compute/selva-compute/dist/visualization.cjs"],"names":[],"mappings":"AAAA,iIAAyF,gCAA6B,iYAA0L","file":"/home/runner/work/selva-compute/selva-compute/dist/visualization.cjs"}
@@ -1,7 +1,7 @@
1
1
  import * as THREE from 'three';
2
2
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
3
- import { f as GrasshopperComputeResponse, M as MeshExtractionOptions, m as MeshBatchParsingOptions, n as DisplayBatch, o as DisplayItem } from './types-DgepKOuU.cjs';
4
- export { p as DisplayCurve, q as DisplayIdentity, r as DisplayItemBase, s as DisplayPoint, t as DisplayPosition, u as MeshBatch } from './types-DgepKOuU.cjs';
3
+ import { f as GrasshopperComputeResponse, M as MeshExtractionOptions, m as MeshBatchParsingOptions, n as DisplayBatch, o as DisplayItem } from './types-BuRCHPlb.cjs';
4
+ export { p as DisplayCurve, q as DisplayIdentity, r as DisplayItemBase, s as DisplayPoint, t as DisplayPosition, u as MeshBatch } from './types-BuRCHPlb.cjs';
5
5
  import { RhinoModule } from 'rhino3dm';
6
6
  import './types-D1SkNje_.cjs';
7
7
 
@@ -43,6 +43,24 @@ type RenderConfig = {
43
43
  toneMapping?: THREE.ToneMapping;
44
44
  toneMappingExposure?: number;
45
45
  preserveDrawingBuffer?: boolean;
46
+ /**
47
+ * Enable ground-truth ambient occlusion (GTAO) via a postprocessing pipeline. Default false —
48
+ * turning it on switches rendering from `renderer.render` to an EffectComposer, which costs more.
49
+ */
50
+ ambientOcclusion?: boolean;
51
+ /** AO strength 0–1 when {@link RenderConfig.ambientOcclusion} is on. Default 1. */
52
+ aoIntensity?: number;
53
+ };
54
+ /** Crisp boundary/crease edge overlays on meshes. See `addEdges`. */
55
+ type EdgesConfig = {
56
+ /** Auto-attach edge overlays to meshes as they load. Default false (opt-in). */
57
+ enabled?: boolean;
58
+ /** Edge color. Default near-black. */
59
+ color?: THREE.ColorRepresentation;
60
+ /** Edge thickness in CSS px. Default 1.5. */
61
+ width?: number;
62
+ /** Crease angle (degrees): keep edges where faces differ by more than this. Default 30. */
63
+ thresholdAngle?: number;
46
64
  };
47
65
  type ControlsConfig = {
48
66
  enableDamping?: boolean;
@@ -54,6 +72,44 @@ type ControlsConfig = {
54
72
  minDistance?: number;
55
73
  maxDistance?: number;
56
74
  };
75
+ /** Infinite distance-fading reference grid. See `createGrid`. */
76
+ type GridConfig = {
77
+ /** Show the grid. Default false (opt-in). */
78
+ enabled?: boolean;
79
+ /** Minor cell size in world units (meters). Default 1. */
80
+ cellSize?: number;
81
+ /** Minor cells per major line. Default 10. */
82
+ majorEvery?: number;
83
+ /** Minor line color. */
84
+ cellColor?: THREE.ColorRepresentation;
85
+ /** Major line color. */
86
+ majorColor?: THREE.ColorRepresentation;
87
+ /** World radius at which the grid fully fades. Default 100. */
88
+ fadeDistance?: number;
89
+ /** Plane the grid lies on. 'y' = horizontal ground. Default 'y'. */
90
+ plane?: 'x' | 'y' | 'z';
91
+ };
92
+ /** Corner nav-cube/axis gizmo that snaps to preset views. See `createViewGizmo`. */
93
+ type GizmoConfig = {
94
+ /** Show the gizmo. Default false (opt-in). */
95
+ enabled?: boolean;
96
+ };
97
+ /** Two-click distance measurement tool. See `createMeasureTool`. */
98
+ type MeasureConfig = {
99
+ /**
100
+ * Create the measurement tool. Default false. Note: this only *builds* the tool (and its label
101
+ * overlay); start measuring by calling `measureTool.setEnabled(true)` on the init result.
102
+ */
103
+ enabled?: boolean;
104
+ /** Snap to a vertex within this many screen px. Default 12. */
105
+ snapPixels?: number;
106
+ /** Marker + line color. Default yellow. */
107
+ color?: THREE.ColorRepresentation;
108
+ /** CSS class for the distance label. */
109
+ labelClassName?: string;
110
+ /** Format the distance number → label text. Default 3 sig-digits + " m". */
111
+ format?: (distance: number) => string;
112
+ };
57
113
  type ThreeInitializerOptions = {
58
114
  sceneScale?: 'mm' | 'cm' | 'm' | 'inches' | 'feet';
59
115
  camera?: CameraConfig;
@@ -62,6 +118,10 @@ type ThreeInitializerOptions = {
62
118
  floor?: FloorConfig;
63
119
  render?: RenderConfig;
64
120
  controls?: ControlsConfig;
121
+ grid?: GridConfig;
122
+ gizmo?: GizmoConfig;
123
+ edges?: EdgesConfig;
124
+ measure?: MeasureConfig;
65
125
  events?: EventConfig;
66
126
  };
67
127
  type EventConfig = {
@@ -88,6 +148,105 @@ type EventConfig = {
88
148
  onFrame?: (delta: number) => void;
89
149
  };
90
150
 
151
+ /**
152
+ * Runtime camera control for the viewer: preset views (top/front/…), a true 2D/3D toggle
153
+ * (orthographic ⇄ perspective), and a rotate lock.
154
+ *
155
+ * Why a controller and not just loose methods: all three features have to agree on *which* camera
156
+ * is active. Switching projection swaps the camera object OrbitControls drives, the animation loop
157
+ * renders, the resize handler reshapes, and the raycaster picks with. Centralizing that here keeps
158
+ * those four call sites reading one source of truth ({@link getActiveCamera}) instead of each
159
+ * branching on a `mode` flag.
160
+ *
161
+ * The perspective camera stays the primary (it's what {@link updateScene} and existing consumers
162
+ * size). The orthographic camera shadows it: same position/target, frustum derived from the
163
+ * perspective FOV + current distance so the 3D→2D switch doesn't visually jump.
164
+ */
165
+ /** The six axis-aligned presets plus the default 3/4 iso. Named in Three's Y-up frame. */
166
+ type ViewPreset = 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right' | 'iso';
167
+ type CameraProjection = 'perspective' | 'orthographic';
168
+ interface CameraController {
169
+ /** The camera currently being rendered/picked with. Swaps identity on {@link setProjection}. */
170
+ getActiveCamera(): THREE.Camera;
171
+ /** Current projection mode. */
172
+ getProjection(): CameraProjection;
173
+ /** Switch between perspective (3D) and orthographic (2D). No-op if already in that mode. */
174
+ setProjection(projection: CameraProjection): void;
175
+ /** Convenience toggle for a 2D/3D button. */
176
+ toggleProjection(): CameraProjection;
177
+ /** Move the camera to a preset orientation, framing current scene content. Animated. */
178
+ setView(preset: ViewPreset, animate?: boolean): void;
179
+ /** Enable/disable orbit rotation at runtime (pan/zoom unaffected). */
180
+ setRotateEnabled(enabled: boolean): void;
181
+ /** Whether rotation is currently enabled. */
182
+ isRotateEnabled(): boolean;
183
+ /** Keep the orthographic frustum aspect in sync on canvas resize. Called by the resize loop. */
184
+ updateAspect(width: number, height: number): void;
185
+ }
186
+
187
+ interface Grid {
188
+ /** The grid mesh; add to the scene. Tagged `userData.id = 'grid'` so pick/fit code skips it. */
189
+ readonly object: THREE.Mesh;
190
+ /** Keep the fade centered on the camera so the grid feels infinite as you move. Call per frame. */
191
+ update(cameraPosition: THREE.Vector3): void;
192
+ setVisible(visible: boolean): void;
193
+ dispose(): void;
194
+ }
195
+
196
+ /**
197
+ * The corner nav-cube/axis gizmo. Wraps three's {@link ViewHelper} (the standard, well-tested
198
+ * widget) and uses its built-in click → animate behavior, which we keep rather than reimplement:
199
+ * ViewHelper's hit-test depends on private internals (`dim`, `interactiveObjects`, viewport math),
200
+ * so replicating it is fragile. We let it drive the perspective camera directly.
201
+ *
202
+ * Two integration points with the viewer's dual-camera setup:
203
+ * 1. Before each click we point `helper.center` at the live orbit target, so the snap rotates about
204
+ * what the user is looking at (not the world origin).
205
+ * 2. ViewHelper only drives the perspective camera. The nav cube is inherently a 3D-orientation
206
+ * tool, so if the viewer is in orthographic (2D) mode when the gizmo is clicked, we first flip
207
+ * back to perspective — then ViewHelper animates as usual. Using the cube returns you to 3D.
208
+ *
209
+ * Caller responsibilities (mirror ViewHelper's own contract):
210
+ * - call {@link ViewGizmo.render} *after* the main scene render each frame (overlay viewport),
211
+ * - call {@link ViewGizmo.update} each frame with the frame delta (drives the snap animation),
212
+ * - forward pointer clicks to {@link ViewGizmo.handleClick}.
213
+ */
214
+ interface ViewGizmo {
215
+ render(renderer: THREE.WebGLRenderer): void;
216
+ update(delta: number): void;
217
+ /** Hit-test a click. Returns true if it hit the gizmo (and a view change started). */
218
+ handleClick(event: MouseEvent): boolean;
219
+ readonly isAnimating: boolean;
220
+ /** Show/hide the gizmo at runtime. Hidden = not rendered and not click-hittable. */
221
+ setVisible(visible: boolean): void;
222
+ isVisible(): boolean;
223
+ dispose(): void;
224
+ }
225
+
226
+ /**
227
+ * A two-click distance measurement tool — the CAD verb users expect. Click a point, click a second,
228
+ * read the distance off a label on the connecting line; a third click starts a new measurement.
229
+ *
230
+ * Picking snaps to geometry so measurements are exact, not "wherever the ray happened to land":
231
+ * on a mesh hit we snap to the nearest vertex of the struck triangle if it's within
232
+ * {@link MeasureOptions.snapPixels} on screen, else use the raw hit point. This is a cheap local
233
+ * snap (three candidate vertices), no spatial index — enough for clean vertex-to-vertex measurement
234
+ * without the cost/complexity of full edge/midpoint snapping (a later refinement).
235
+ *
236
+ * The tool is dormant until {@link MeasureTool.setEnabled}(true). While enabled it intercepts clicks
237
+ * (the caller forwards them and swallows the event when {@link MeasureTool.handleClick} returns
238
+ * true) so measuring doesn't also select objects.
239
+ */
240
+ interface MeasureTool {
241
+ setEnabled(enabled: boolean): void;
242
+ isEnabled(): boolean;
243
+ /** Process a click. Returns true if the tool consumed it (caller should not also select). */
244
+ handleClick(event: MouseEvent): boolean;
245
+ /** Clear the current measurement (markers, line, label). */
246
+ clear(): void;
247
+ dispose(): void;
248
+ }
249
+
91
250
  /**
92
251
  * Initializes a Three.js environment with scene, camera, renderer, and event handling.
93
252
  */
@@ -96,6 +255,18 @@ declare const initThree: (canvas: HTMLCanvasElement, options?: ThreeInitializerO
96
255
  camera: THREE.PerspectiveCamera;
97
256
  controls: OrbitControls;
98
257
  renderer: THREE.WebGLRenderer;
258
+ cameraController: CameraController;
259
+ grid: Grid | null;
260
+ gizmo: ViewGizmo | null;
261
+ /** Two-click distance measurement tool. Null unless `measure.enabled`; `setEnabled(true)` to use. */
262
+ measureTool: MeasureTool | null;
263
+ /**
264
+ * Attach edge overlays to the meshes under `root` (no-op unless `edges.enabled`). Call after
265
+ * loading meshes via `updateScene`, since meshes arrive after init.
266
+ */
267
+ applyEdges: (root: THREE.Object3D) => void;
268
+ /** Toggle ambient occlusion at runtime — builds or tears down the postprocessing pipeline. */
269
+ setAmbientOcclusion: (enabled: boolean) => void;
99
270
  dispose: () => void;
100
271
  fitToView: () => void;
101
272
  clearSelection: () => void;
@@ -1,7 +1,7 @@
1
1
  import * as THREE from 'three';
2
2
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
3
- import { f as GrasshopperComputeResponse, M as MeshExtractionOptions, m as MeshBatchParsingOptions, n as DisplayBatch, o as DisplayItem } from './types-B6KrSthC.js';
4
- export { p as DisplayCurve, q as DisplayIdentity, r as DisplayItemBase, s as DisplayPoint, t as DisplayPosition, u as MeshBatch } from './types-B6KrSthC.js';
3
+ import { f as GrasshopperComputeResponse, M as MeshExtractionOptions, m as MeshBatchParsingOptions, n as DisplayBatch, o as DisplayItem } from './types-jPuWWtnU.js';
4
+ export { p as DisplayCurve, q as DisplayIdentity, r as DisplayItemBase, s as DisplayPoint, t as DisplayPosition, u as MeshBatch } from './types-jPuWWtnU.js';
5
5
  import { RhinoModule } from 'rhino3dm';
6
6
  import './types-D1SkNje_.js';
7
7
 
@@ -43,6 +43,24 @@ type RenderConfig = {
43
43
  toneMapping?: THREE.ToneMapping;
44
44
  toneMappingExposure?: number;
45
45
  preserveDrawingBuffer?: boolean;
46
+ /**
47
+ * Enable ground-truth ambient occlusion (GTAO) via a postprocessing pipeline. Default false —
48
+ * turning it on switches rendering from `renderer.render` to an EffectComposer, which costs more.
49
+ */
50
+ ambientOcclusion?: boolean;
51
+ /** AO strength 0–1 when {@link RenderConfig.ambientOcclusion} is on. Default 1. */
52
+ aoIntensity?: number;
53
+ };
54
+ /** Crisp boundary/crease edge overlays on meshes. See `addEdges`. */
55
+ type EdgesConfig = {
56
+ /** Auto-attach edge overlays to meshes as they load. Default false (opt-in). */
57
+ enabled?: boolean;
58
+ /** Edge color. Default near-black. */
59
+ color?: THREE.ColorRepresentation;
60
+ /** Edge thickness in CSS px. Default 1.5. */
61
+ width?: number;
62
+ /** Crease angle (degrees): keep edges where faces differ by more than this. Default 30. */
63
+ thresholdAngle?: number;
46
64
  };
47
65
  type ControlsConfig = {
48
66
  enableDamping?: boolean;
@@ -54,6 +72,44 @@ type ControlsConfig = {
54
72
  minDistance?: number;
55
73
  maxDistance?: number;
56
74
  };
75
+ /** Infinite distance-fading reference grid. See `createGrid`. */
76
+ type GridConfig = {
77
+ /** Show the grid. Default false (opt-in). */
78
+ enabled?: boolean;
79
+ /** Minor cell size in world units (meters). Default 1. */
80
+ cellSize?: number;
81
+ /** Minor cells per major line. Default 10. */
82
+ majorEvery?: number;
83
+ /** Minor line color. */
84
+ cellColor?: THREE.ColorRepresentation;
85
+ /** Major line color. */
86
+ majorColor?: THREE.ColorRepresentation;
87
+ /** World radius at which the grid fully fades. Default 100. */
88
+ fadeDistance?: number;
89
+ /** Plane the grid lies on. 'y' = horizontal ground. Default 'y'. */
90
+ plane?: 'x' | 'y' | 'z';
91
+ };
92
+ /** Corner nav-cube/axis gizmo that snaps to preset views. See `createViewGizmo`. */
93
+ type GizmoConfig = {
94
+ /** Show the gizmo. Default false (opt-in). */
95
+ enabled?: boolean;
96
+ };
97
+ /** Two-click distance measurement tool. See `createMeasureTool`. */
98
+ type MeasureConfig = {
99
+ /**
100
+ * Create the measurement tool. Default false. Note: this only *builds* the tool (and its label
101
+ * overlay); start measuring by calling `measureTool.setEnabled(true)` on the init result.
102
+ */
103
+ enabled?: boolean;
104
+ /** Snap to a vertex within this many screen px. Default 12. */
105
+ snapPixels?: number;
106
+ /** Marker + line color. Default yellow. */
107
+ color?: THREE.ColorRepresentation;
108
+ /** CSS class for the distance label. */
109
+ labelClassName?: string;
110
+ /** Format the distance number → label text. Default 3 sig-digits + " m". */
111
+ format?: (distance: number) => string;
112
+ };
57
113
  type ThreeInitializerOptions = {
58
114
  sceneScale?: 'mm' | 'cm' | 'm' | 'inches' | 'feet';
59
115
  camera?: CameraConfig;
@@ -62,6 +118,10 @@ type ThreeInitializerOptions = {
62
118
  floor?: FloorConfig;
63
119
  render?: RenderConfig;
64
120
  controls?: ControlsConfig;
121
+ grid?: GridConfig;
122
+ gizmo?: GizmoConfig;
123
+ edges?: EdgesConfig;
124
+ measure?: MeasureConfig;
65
125
  events?: EventConfig;
66
126
  };
67
127
  type EventConfig = {
@@ -88,6 +148,105 @@ type EventConfig = {
88
148
  onFrame?: (delta: number) => void;
89
149
  };
90
150
 
151
+ /**
152
+ * Runtime camera control for the viewer: preset views (top/front/…), a true 2D/3D toggle
153
+ * (orthographic ⇄ perspective), and a rotate lock.
154
+ *
155
+ * Why a controller and not just loose methods: all three features have to agree on *which* camera
156
+ * is active. Switching projection swaps the camera object OrbitControls drives, the animation loop
157
+ * renders, the resize handler reshapes, and the raycaster picks with. Centralizing that here keeps
158
+ * those four call sites reading one source of truth ({@link getActiveCamera}) instead of each
159
+ * branching on a `mode` flag.
160
+ *
161
+ * The perspective camera stays the primary (it's what {@link updateScene} and existing consumers
162
+ * size). The orthographic camera shadows it: same position/target, frustum derived from the
163
+ * perspective FOV + current distance so the 3D→2D switch doesn't visually jump.
164
+ */
165
+ /** The six axis-aligned presets plus the default 3/4 iso. Named in Three's Y-up frame. */
166
+ type ViewPreset = 'top' | 'bottom' | 'front' | 'back' | 'left' | 'right' | 'iso';
167
+ type CameraProjection = 'perspective' | 'orthographic';
168
+ interface CameraController {
169
+ /** The camera currently being rendered/picked with. Swaps identity on {@link setProjection}. */
170
+ getActiveCamera(): THREE.Camera;
171
+ /** Current projection mode. */
172
+ getProjection(): CameraProjection;
173
+ /** Switch between perspective (3D) and orthographic (2D). No-op if already in that mode. */
174
+ setProjection(projection: CameraProjection): void;
175
+ /** Convenience toggle for a 2D/3D button. */
176
+ toggleProjection(): CameraProjection;
177
+ /** Move the camera to a preset orientation, framing current scene content. Animated. */
178
+ setView(preset: ViewPreset, animate?: boolean): void;
179
+ /** Enable/disable orbit rotation at runtime (pan/zoom unaffected). */
180
+ setRotateEnabled(enabled: boolean): void;
181
+ /** Whether rotation is currently enabled. */
182
+ isRotateEnabled(): boolean;
183
+ /** Keep the orthographic frustum aspect in sync on canvas resize. Called by the resize loop. */
184
+ updateAspect(width: number, height: number): void;
185
+ }
186
+
187
+ interface Grid {
188
+ /** The grid mesh; add to the scene. Tagged `userData.id = 'grid'` so pick/fit code skips it. */
189
+ readonly object: THREE.Mesh;
190
+ /** Keep the fade centered on the camera so the grid feels infinite as you move. Call per frame. */
191
+ update(cameraPosition: THREE.Vector3): void;
192
+ setVisible(visible: boolean): void;
193
+ dispose(): void;
194
+ }
195
+
196
+ /**
197
+ * The corner nav-cube/axis gizmo. Wraps three's {@link ViewHelper} (the standard, well-tested
198
+ * widget) and uses its built-in click → animate behavior, which we keep rather than reimplement:
199
+ * ViewHelper's hit-test depends on private internals (`dim`, `interactiveObjects`, viewport math),
200
+ * so replicating it is fragile. We let it drive the perspective camera directly.
201
+ *
202
+ * Two integration points with the viewer's dual-camera setup:
203
+ * 1. Before each click we point `helper.center` at the live orbit target, so the snap rotates about
204
+ * what the user is looking at (not the world origin).
205
+ * 2. ViewHelper only drives the perspective camera. The nav cube is inherently a 3D-orientation
206
+ * tool, so if the viewer is in orthographic (2D) mode when the gizmo is clicked, we first flip
207
+ * back to perspective — then ViewHelper animates as usual. Using the cube returns you to 3D.
208
+ *
209
+ * Caller responsibilities (mirror ViewHelper's own contract):
210
+ * - call {@link ViewGizmo.render} *after* the main scene render each frame (overlay viewport),
211
+ * - call {@link ViewGizmo.update} each frame with the frame delta (drives the snap animation),
212
+ * - forward pointer clicks to {@link ViewGizmo.handleClick}.
213
+ */
214
+ interface ViewGizmo {
215
+ render(renderer: THREE.WebGLRenderer): void;
216
+ update(delta: number): void;
217
+ /** Hit-test a click. Returns true if it hit the gizmo (and a view change started). */
218
+ handleClick(event: MouseEvent): boolean;
219
+ readonly isAnimating: boolean;
220
+ /** Show/hide the gizmo at runtime. Hidden = not rendered and not click-hittable. */
221
+ setVisible(visible: boolean): void;
222
+ isVisible(): boolean;
223
+ dispose(): void;
224
+ }
225
+
226
+ /**
227
+ * A two-click distance measurement tool — the CAD verb users expect. Click a point, click a second,
228
+ * read the distance off a label on the connecting line; a third click starts a new measurement.
229
+ *
230
+ * Picking snaps to geometry so measurements are exact, not "wherever the ray happened to land":
231
+ * on a mesh hit we snap to the nearest vertex of the struck triangle if it's within
232
+ * {@link MeasureOptions.snapPixels} on screen, else use the raw hit point. This is a cheap local
233
+ * snap (three candidate vertices), no spatial index — enough for clean vertex-to-vertex measurement
234
+ * without the cost/complexity of full edge/midpoint snapping (a later refinement).
235
+ *
236
+ * The tool is dormant until {@link MeasureTool.setEnabled}(true). While enabled it intercepts clicks
237
+ * (the caller forwards them and swallows the event when {@link MeasureTool.handleClick} returns
238
+ * true) so measuring doesn't also select objects.
239
+ */
240
+ interface MeasureTool {
241
+ setEnabled(enabled: boolean): void;
242
+ isEnabled(): boolean;
243
+ /** Process a click. Returns true if the tool consumed it (caller should not also select). */
244
+ handleClick(event: MouseEvent): boolean;
245
+ /** Clear the current measurement (markers, line, label). */
246
+ clear(): void;
247
+ dispose(): void;
248
+ }
249
+
91
250
  /**
92
251
  * Initializes a Three.js environment with scene, camera, renderer, and event handling.
93
252
  */
@@ -96,6 +255,18 @@ declare const initThree: (canvas: HTMLCanvasElement, options?: ThreeInitializerO
96
255
  camera: THREE.PerspectiveCamera;
97
256
  controls: OrbitControls;
98
257
  renderer: THREE.WebGLRenderer;
258
+ cameraController: CameraController;
259
+ grid: Grid | null;
260
+ gizmo: ViewGizmo | null;
261
+ /** Two-click distance measurement tool. Null unless `measure.enabled`; `setEnabled(true)` to use. */
262
+ measureTool: MeasureTool | null;
263
+ /**
264
+ * Attach edge overlays to the meshes under `root` (no-op unless `edges.enabled`). Call after
265
+ * loading meshes via `updateScene`, since meshes arrive after init.
266
+ */
267
+ applyEdges: (root: THREE.Object3D) => void;
268
+ /** Toggle ambient occlusion at runtime — builds or tears down the postprocessing pipeline. */
269
+ setAmbientOcclusion: (enabled: boolean) => void;
99
270
  dispose: () => void;
100
271
  fitToView: () => void;
101
272
  clearSelection: () => void;
@@ -1,2 +1,2 @@
1
- import{a as e,b as t,f as i,g as s,m as o,n as a,o as r,p}from"./chunk-SP73WPYA.js";import"./chunk-GTTKNF4G.js";export{i as Materials,r as SCALE_FACTORS,p as getThreeMeshesFromComputeResponse,e as initThree,s as parseDisplayItems,a as parseMeshBatchBlob,o as parseMeshBatchObject,t as updateScene};
1
+ import{A as p,a as e,p as t,q as i,r as s,x as o,y as a,z as r}from"./chunk-Z4TVQVMV.js";import"./chunk-GTTKNF4G.js";export{i as Materials,r as SCALE_FACTORS,p as getThreeMeshesFromComputeResponse,t as initThree,s as parseDisplayItems,a as parseMeshBatchBlob,o as parseMeshBatchObject,e as updateScene};
2
2
  //# sourceMappingURL=visualization.js.map