@omrips/viewstl 2.0.0-rc.8 → 2.0.0-rc.9
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/README.md +40 -38
- package/build/stl_viewer.min.js +6 -6
- package/build/viewstl.cjs +477 -2369
- package/build/viewstl.d.cts +95 -45
- package/build/viewstl.d.ts +95 -45
- package/build/viewstl.js +477 -2369
- package/build/viewstl.min.js +6 -6
- package/docs/releases/2.0.0-rc.9.md +20 -0
- package/docs/v2-usage.md +945 -993
- package/docs/v2-verification.md +31 -0
- package/package.json +2 -2
- package/docs/releases/2.0.0-rc.8.md +0 -11
package/docs/v2-usage.md
CHANGED
|
@@ -1,993 +1,945 @@
|
|
|
1
|
-
# ViewSTL 2 — native WebGL2 plugin
|
|
2
|
-
|
|
3
|
-
Version: `2.0.0-rc.5` (ViewSTL v2.00 release candidate, GitHub and npm).
|
|
4
|
-
On `main`, the new plugin is in `src/` and `build/`; the legacy tree is in
|
|
5
|
-
`legacy/v1/`. The unchanged `master` branch retains original legacy paths.
|
|
6
|
-
The v2 website is available at https://preview.viewstl.com/.
|
|
7
|
-
|
|
8
|
-
## Quick start
|
|
9
|
-
|
|
10
|
-
Download the plugin ZIP from the [GitHub release](https://github.com/omrips/viewstl/releases/tag/v2.0.0-rc.5),
|
|
11
|
-
then copy its complete `build/` folder to `/viewstl-v2/` on your website.
|
|
12
|
-
Paste these three lines into your page's `<body>`:
|
|
13
|
-
|
|
14
|
-
```html
|
|
15
|
-
<div id="viewer" style="height:500px"></div>
|
|
16
|
-
<script src="/viewstl-v2/viewstl.min.js"></script>
|
|
17
|
-
<script>new StlViewer(document.getElementById('viewer'), { models: [{ filename: 'model.stl' }] });</script>
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
Replace `model.stl` with your model's path and open the page over HTTP(S).
|
|
21
|
-
You can also drag local files onto the viewer. The full distribution folder is
|
|
22
|
-
required so the viewer can find its worker and optional format assets.
|
|
23
|
-
|
|
24
|
-
## Browser script
|
|
25
|
-
|
|
26
|
-
Copy the complete `build/` directory to your server, including the
|
|
27
|
-
worker, its `chunks/` and `cad/` directories and third-party notices. Then:
|
|
28
|
-
|
|
29
|
-
```html
|
|
30
|
-
<div id="viewer" style="width:100%;height:500px"></div>
|
|
31
|
-
<script src="/viewstl-v2/stl_viewer.min.js"></script>
|
|
32
|
-
<script>
|
|
33
|
-
const viewer = new StlViewer(document.getElementById('viewer'), {
|
|
34
|
-
models: [{ id: 0, filename: '/models/part.stl', color: '#568bdc' }],
|
|
35
|
-
load_error_callback: message => console.error(message)
|
|
36
|
-
});
|
|
37
|
-
</script>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
`viewstl.min.js` is an identical classic-script alias. The canvas requires
|
|
41
|
-
a container with a nonzero height. Files must be served over HTTP(S),
|
|
42
|
-
including during local development; browsers restrict workers on `file:` URLs.
|
|
43
|
-
|
|
44
|
-
## ES modules and npm
|
|
45
|
-
|
|
46
|
-
```js
|
|
47
|
-
import StlViewer from './viewstl-v2/viewstl.js';
|
|
48
|
-
|
|
49
|
-
const viewer = new StlViewer(container, {
|
|
50
|
-
models: [{ id: 0, local_file: fileInput.files[0] }]
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
await viewer.when_idle();
|
|
54
|
-
console.log(viewer.get_model_info(0));
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
The package contains ESM, CommonJS and TypeScript entry points. Importing
|
|
58
|
-
the package is safe during server-side rendering; constructing a viewer
|
|
59
|
-
requires a browser DOM and WebGL2.
|
|
60
|
-
|
|
61
|
-
Install with `npm install viewstl`. No version suffix is required: the `latest`
|
|
62
|
-
tag selects the current release candidate. The established `@omrips/viewstl`
|
|
63
|
-
package is also updated with the same plugin; `npm install @omrips/viewstl`
|
|
64
|
-
works without a version suffix. Use either package name consistently in imports
|
|
65
|
-
and asset-copy paths; there is no need to install both.
|
|
66
|
-
|
|
67
|
-
With a bundler, copy the complete `node_modules/viewstl/build/`
|
|
68
|
-
directory to your app's public assets (for example `public/viewstl-v2/`), keeping
|
|
69
|
-
the worker, `chunks/`, `cad/` and notices together. Set `worker_url` to its served
|
|
70
|
-
URL, or provide your bundler's worker factory.
|
|
71
|
-
Do not assume a bundler copies a module worker's dynamic chunks merely
|
|
72
|
-
because it sees a URL string.
|
|
73
|
-
|
|
74
|
-
```js
|
|
75
|
-
import StlViewer from 'viewstl';
|
|
76
|
-
|
|
77
|
-
const viewer = new StlViewer(container, {
|
|
78
|
-
worker_url: '/viewstl-v2/viewstl.worker.js'
|
|
79
|
-
});
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
For Vite, the optional factory form is:
|
|
83
|
-
|
|
84
|
-
```js
|
|
85
|
-
import ViewerWorker from 'viewstl/worker?worker';
|
|
86
|
-
const viewer = new StlViewer(container, {
|
|
87
|
-
worker_factory: () => new ViewerWorker()
|
|
88
|
-
});
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
The static-assets/`worker_url` integration above was verified in production
|
|
92
|
-
builds using Vite 8.3.4 and Webpack 5.112.0, including STEP/IGES and lazy archive
|
|
93
|
-
assets. The optional Vite worker-factory example is separate integration guidance
|
|
94
|
-
and was not part of those checks. A factory must return a new worker for each
|
|
95
|
-
scheduled job and arrange for all dynamic chunks and CAD assets to be served.
|
|
96
|
-
The CommonJS entry point also requires
|
|
97
|
-
`worker_url` or `worker_factory` because it cannot infer a browser asset URL.
|
|
98
|
-
|
|
99
|
-
## Node.js model analysis
|
|
100
|
-
|
|
101
|
-
Run `npm install viewstl`. Node.js 20+ resolves the analysis
|
|
102
|
-
worker and optional CAD WASM directly inside the package; there is no asset-copy
|
|
103
|
-
step for server use.
|
|
104
|
-
|
|
105
|
-
```js
|
|
106
|
-
import { analyzeFile } from 'viewstl/node';
|
|
107
|
-
const report = await analyzeFile('part.stl', { units: 'mm' });
|
|
108
|
-
console.log(report.dimensions_mm, report.surface_area_mm2, report.volume_mm3);
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
CommonJS is supported with `const { analyzeFile } = require('viewstl/node')`.
|
|
112
|
-
The `/node` entry point is separate from the browser viewer and has no DOM,
|
|
113
|
-
WebGL or Three.js dependency. It supports STL, OBJ, 3MF, STEP/STP and IGES/IGS.
|
|
114
|
-
STEP/IGES measurements describe the tessellated mesh, not exact CAD surfaces.
|
|
115
|
-
Scene files, rendering, thumbnails and server-side exporting are outside this API.
|
|
116
|
-
|
|
117
|
-
### Input and units
|
|
118
|
-
|
|
119
|
-
`analyzeFile(path, options)` accepts a local regular-file path or a `file:` URL
|
|
120
|
-
object. `analyzeBuffer(data, options)` accepts a Node Buffer, Uint8Array or
|
|
121
|
-
ArrayBuffer, and requires `filename` or `format` to identify the format:
|
|
122
|
-
|
|
123
|
-
```js
|
|
124
|
-
import { analyzeBuffer } from 'viewstl/node';
|
|
125
|
-
const report = await analyzeBuffer(uploadBuffer, { filename: 'part.stl', units: 'mm' });
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
The input buffer is copied without modifying or detaching the caller's data.
|
|
129
|
-
HTTP(S) URLs and network-share paths are rejected. The module does not fetch
|
|
130
|
-
external model resources, run a web service, or write input files to disk.
|
|
131
|
-
An application's own upload handler determines how files reach its server and
|
|
132
|
-
whether they are stored. Browser-selected files sent to a server are uploads;
|
|
133
|
-
the preview website continues to perform its checks on the user's device.
|
|
134
|
-
|
|
135
|
-
STL and OBJ carry no reliable physical units. Supply `units: 'mm'`, `'cm'`, `'m'`,
|
|
136
|
-
`'in'`, `'ft'` or `'um'` when the input scale is known. With no scale, `units` and
|
|
137
|
-
all physical measurement fields (`dimensions_mm`, `surface_area_mm2`,
|
|
138
|
-
`volume_mm3`) are null; the unit check is inconclusive. The generic `dimensions`,
|
|
139
|
-
`surface_area` and `volume` fields then use input coordinate units. When units
|
|
140
|
-
are known, these generic fields also use mm, mm² and mm³.
|
|
141
|
-
|
|
142
|
-
3MF and CAD file units are honored and normalized to mm automatically. They
|
|
143
|
-
cannot be overridden with a different input scale. `units_source` is `'file'`,
|
|
144
|
-
`'option'` or null. `volume_valid` describes validation of the mesh volume;
|
|
145
|
-
it does not confirm a physical scale or guarantee printability. Invalid or
|
|
146
|
-
unverified volume is returned as null. `level: 'basic'` skips detailed
|
|
147
|
-
intersection and validated-volume checks; the default is `'detailed'`.
|
|
148
|
-
|
|
149
|
-
### Jobs, limits and cancellation
|
|
150
|
-
|
|
151
|
-
The convenience functions share an analyzer with two active workers, up to
|
|
152
|
-
16 waiting jobs, and at most 128 MiB of copied buffer data waiting in the queue.
|
|
153
|
-
Create an independent analyzer when the host needs different concurrency:
|
|
154
|
-
|
|
155
|
-
```js
|
|
156
|
-
import { createAnalyzer } from 'viewstl/node';
|
|
157
|
-
const analyzer = createAnalyzer({ concurrency: 1, max_queue: 4 });
|
|
158
|
-
try {
|
|
159
|
-
const reports = await Promise.all(paths.map(path => analyzer.analyzeFile(path, {
|
|
160
|
-
units: 'mm', signal: controller.signal, timeout_ms: 60000,
|
|
161
|
-
limits: { max_file_bytes: 32 * 1024 ** 2, max_memory_bytes: 512 * 1024 ** 2 }
|
|
162
|
-
})));
|
|
163
|
-
} finally {
|
|
164
|
-
await analyzer.dispose();
|
|
165
|
-
}
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Here `paths` is the application's array of file paths and `controller` is its
|
|
169
|
-
AbortController. Keep submitted batches within the configured queue capacity.
|
|
170
|
-
`concurrency` is 1–8; `max_queue` may be zero; `max_queued_bytes` controls queued
|
|
171
|
-
buffer copies. Exceeding queue admission rejects with `ERR_ANALYZER_BUSY`.
|
|
172
|
-
Use a shared analyzer for a service rather than creating an unbounded number
|
|
173
|
-
of independent analyzers per request.
|
|
174
|
-
|
|
175
|
-
Per-job defaults are 64 MiB input, 1,000,000 triangles, 128 MiB expanded archive
|
|
176
|
-
data, 1,024 archive entries and a 1 GiB estimated working-set budget. These are
|
|
177
|
-
independent ceilings: the memory estimate can reject a mesh below the triangle
|
|
178
|
-
ceiling. Override them in `limits` with `max_file_bytes`, `max_triangles`,
|
|
179
|
-
`max_uncompressed_bytes`, `max_archive_entries` and `max_memory_bytes`.
|
|
180
|
-
Unknown limit names are rejected. CAD input/output and WASM-heap checks are
|
|
181
|
-
also applied, and retained CAD memory counts towards the analysis budget.
|
|
182
|
-
|
|
183
|
-
`timeout_ms` defaults to 120,000 and covers waiting, reading, parsing and checks.
|
|
184
|
-
Expiry terminates the worker and rejects with `ERR_ANALYSIS_TIMEOUT`.
|
|
185
|
-
`signal` cancels queued or active work with an `AbortError` (`ABORT_ERR`).
|
|
186
|
-
`dispose()` cancels outstanding jobs and waits for their workers to terminate.
|
|
187
|
-
Workers are also terminated after success or failure before their slots are
|
|
188
|
-
reused, releasing parser and CAD state. Processing failures do not disable
|
|
189
|
-
the analyzer.
|
|
190
|
-
|
|
191
|
-
`max_time_ms` separately limits the mesh-check stage (30,000 by default, up to
|
|
192
|
-
120,000). It may yield a partial report with `limit_reason: 'time'`. Inspect
|
|
193
|
-
`summary` and each check's status; an incomplete check is not a pass.
|
|
194
|
-
`on_progress` receives phase and elapsed-time updates. The optional `profile`
|
|
195
|
-
uses the same FDM build-volume, overhang, bed-contact and sampled wall-thickness
|
|
196
|
-
settings as browser print checks. FDM results remain estimates.
|
|
197
|
-
|
|
198
|
-
Memory limits are conservative admission estimates plus a worker JS-heap limit,
|
|
199
|
-
not a hard limit on process RSS. ArrayBuffers and WASM memory are not fully
|
|
200
|
-
covered by the JS-heap limit. Size host/container limits for total concurrency;
|
|
201
|
-
use process/container isolation when serving untrusted uploads. The library
|
|
202
|
-
does not provide an HTTP endpoint, authentication or a filesystem sandbox.
|
|
203
|
-
|
|
204
|
-
##
|
|
205
|
-
|
|
206
|
-
`
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
the
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
`
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
`
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
-
|
|
305
|
-
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
viewer.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
`
|
|
420
|
-
`
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
`
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
- `
|
|
438
|
-
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
`
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
and
|
|
534
|
-
|
|
535
|
-
`
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
The
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
viewer.
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
})
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
`
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
sequence, with cooperative snapshot copying and aggregate memory admission.
|
|
947
|
-
The working estimate is 64 MiB plus 2,400 bytes per triangle, with bounded report
|
|
948
|
-
allowances; a model that can be viewed may be too large to analyze on the same
|
|
949
|
-
device. One bounded report cache is retained, not the topology/BVH. Geometry,
|
|
950
|
-
transforms, scale and unit changes invalidate reports; colors and camera changes
|
|
951
|
-
do not. Removing a checked model, clean/dispose, AbortSignal cancellation or a
|
|
952
|
-
throwing progress callback releases the active worker and reservations. Report
|
|
953
|
-
metadata uses weak references so retaining a report cannot retain removed meshes.
|
|
954
|
-
|
|
955
|
-
Highlights use temporary native WebGL line buffers and are excluded from PNG,
|
|
956
|
-
mesh and scene exports. They show up to 128 faces or 256 edges per check, through
|
|
957
|
-
occluding surfaces. Closing the site panel clears highlights. The preview exposes
|
|
958
|
-
the feature under **Info → Check for 3D printing**, with selected/all model scope,
|
|
959
|
-
basic/detailed depth and collapsed optional FDM settings. Unit confirmation resets
|
|
960
|
-
when the checked scope or an existing result's geometry/transform changes. A complete
|
|
961
|
-
optional `print-*` control group is bound by `mountViewstlApp`; hosts omitting the
|
|
962
|
-
entire group keep their previous behavior. The website only supplies markup/styles.
|
|
963
|
-
|
|
964
|
-
The check panel stays open while using the canvas, other controls or dialogs,
|
|
965
|
-
loading files, selecting models and clearing the scene. Only its close button or
|
|
966
|
-
Escape with focus inside the panel dismisses it. Reopening it from Info reveals
|
|
967
|
-
and focuses the existing panel. Loading or changing the checked selection cancels
|
|
968
|
-
an active check; stale results are cleared. Loading and empty scenes disable Run
|
|
969
|
-
and show the current availability without closing the panel.
|
|
970
|
-
|
|
971
|
-
## Model size, units, duplicates, cameras and measurements (rc.6)
|
|
972
|
-
|
|
973
|
-
All operations below are implemented inside the plugin. The optional site controls remain a thin HTML/CSS host.
|
|
974
|
-
|
|
975
|
-
`viewer.resize_model(id, [2, 2, 2])` doubles the model about its world-space bounds center, preserving rotation and placement. Positive finite XYZ factors are required; independent factors allow nonuniform scaling. It stops only that model's animation. `get_model_size(id)` returns dimensions and the reset baseline in millimetres. `reset_model_size(id)` restores that baseline while retaining file-unit corrections.
|
|
976
|
-
|
|
977
|
-
`viewer.set_file_units(id, 'inch')` reinterprets a unitless STL/OBJ mesh (default mm) without moving its center. Switching back reverses the conversion. Explicit 3MF/STEP/IGES units are normalized to mm during parsing and cannot be reinterpreted. `set_display_units('inch')` changes the optional app's size/Info readouts only; scene coordinates, position controls and the grid remain in mm. Existing `set_model_units` and `set_scale` retain their legacy semantics.
|
|
978
|
-
|
|
979
|
-
`const copy = viewer.duplicate_model(id)` returns a new ID at the same pose, with independent appearance, transforms and metadata. Immutable geometry and matching GPU buffers are shared; memory admission remains conservative. The source reference remains unchanged for VSJ, while `display_name` identifies the copy. Copied animation definitions and phase are retained but paused; the original keeps playing.
|
|
980
|
-
|
|
981
|
-
`viewer.set_camera_view('top')` fits the scene from front/back/left/right/top/bottom/isometric using Z up and the existing perspective projection. It never rotates the models. Camera bookmarks are explicit data:
|
|
982
|
-
|
|
983
|
-
`viewer.set_camera_bookmarks([{name: 'Detail', camera: viewer.get_camera_state()}])`
|
|
984
|
-
|
|
985
|
-
Use `get_camera_bookmarks()` to obtain a defensive copy and `set_camera_state(bookmark.camera)` to restore one. Up to 32 uniquely named views (80 characters per name) are supported. They are kept in memory and in scene exports, not localStorage.
|
|
986
|
-
|
|
987
|
-
`const {area_mm2, volume_mm3, volume_valid, volume_reason} = await viewer.measure_model(id)`
|
|
988
|
-
|
|
989
|
-
Measurements use workers and support `signal`, `max_time_ms` (default 30 seconds) and `on_progress`. Area includes all triangles at current scale. Volume is nullable: detailed topology, intersections and shell-orientation checks must establish a closed volume. An open/invalid mesh or an inconclusive/time-limited check never becomes a plausible-looking volume. Results are cached by mesh and scale; position/rotation do not invalidate them and uniform resizing updates cached measurements algebraically. Nonuniform area is recalculated in the worker. Memory limits can reject the calculation before buffers are copied. This is a bounded mesh analysis, not a guarantee of printability.
|
|
990
|
-
|
|
991
|
-
VSB/VSJ retain size baselines, file-unit interpretation, display units, duplicate labels and bookmarks. VSB still embeds geometry; VSJ still needs the referenced source files. No second unit conversion occurs during import. Appending scenes merges bookmark names with numbered suffixes; a combined total above 32 rejects the incoming scene. Old files without the new optional fields continue to load. The `size_state.unit_factor` serialization field preserves source-unit correction separately from resize factors for exports with `apply_transforms:false`.
|
|
992
|
-
|
|
993
|
-
The optional app control group is listed in `src/app-tools.js` (`toolsControls`). Provide the whole group or omit it when embedding older markup. The preview's `site/index.html` is a complete example. Models contains collapsed Size & units and Duplicate; View contains collapsed Camera & saved views; Info contains area and the volume calculation. No server is involved for local files or measurements.
|
|
1
|
+
# ViewSTL 2 — native WebGL2 plugin
|
|
2
|
+
|
|
3
|
+
Version: `2.0.0-rc.5` (ViewSTL v2.00 release candidate, GitHub and npm).
|
|
4
|
+
On `main`, the new plugin is in `src/` and `build/`; the legacy tree is in
|
|
5
|
+
`legacy/v1/`. The unchanged `master` branch retains original legacy paths.
|
|
6
|
+
The v2 website is available at https://preview.viewstl.com/.
|
|
7
|
+
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
Download the plugin ZIP from the [GitHub release](https://github.com/omrips/viewstl/releases/tag/v2.0.0-rc.5),
|
|
11
|
+
then copy its complete `build/` folder to `/viewstl-v2/` on your website.
|
|
12
|
+
Paste these three lines into your page's `<body>`:
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<div id="viewer" style="height:500px"></div>
|
|
16
|
+
<script src="/viewstl-v2/viewstl.min.js"></script>
|
|
17
|
+
<script>new StlViewer(document.getElementById('viewer'), { models: [{ filename: 'model.stl' }] });</script>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Replace `model.stl` with your model's path and open the page over HTTP(S).
|
|
21
|
+
You can also drag local files onto the viewer. The full distribution folder is
|
|
22
|
+
required so the viewer can find its worker and optional format assets.
|
|
23
|
+
|
|
24
|
+
## Browser script
|
|
25
|
+
|
|
26
|
+
Copy the complete `build/` directory to your server, including the
|
|
27
|
+
worker, its `chunks/` and `cad/` directories and third-party notices. Then:
|
|
28
|
+
|
|
29
|
+
```html
|
|
30
|
+
<div id="viewer" style="width:100%;height:500px"></div>
|
|
31
|
+
<script src="/viewstl-v2/stl_viewer.min.js"></script>
|
|
32
|
+
<script>
|
|
33
|
+
const viewer = new StlViewer(document.getElementById('viewer'), {
|
|
34
|
+
models: [{ id: 0, filename: '/models/part.stl', color: '#568bdc' }],
|
|
35
|
+
load_error_callback: message => console.error(message)
|
|
36
|
+
});
|
|
37
|
+
</script>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
`viewstl.min.js` is an identical classic-script alias. The canvas requires
|
|
41
|
+
a container with a nonzero height. Files must be served over HTTP(S),
|
|
42
|
+
including during local development; browsers restrict workers on `file:` URLs.
|
|
43
|
+
|
|
44
|
+
## ES modules and npm
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
import StlViewer from './viewstl-v2/viewstl.js';
|
|
48
|
+
|
|
49
|
+
const viewer = new StlViewer(container, {
|
|
50
|
+
models: [{ id: 0, local_file: fileInput.files[0] }]
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await viewer.when_idle();
|
|
54
|
+
console.log(viewer.get_model_info(0));
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The package contains ESM, CommonJS and TypeScript entry points. Importing
|
|
58
|
+
the package is safe during server-side rendering; constructing a viewer
|
|
59
|
+
requires a browser DOM and WebGL2.
|
|
60
|
+
|
|
61
|
+
Install with `npm install viewstl`. No version suffix is required: the `latest`
|
|
62
|
+
tag selects the current release candidate. The established `@omrips/viewstl`
|
|
63
|
+
package is also updated with the same plugin; `npm install @omrips/viewstl`
|
|
64
|
+
works without a version suffix. Use either package name consistently in imports
|
|
65
|
+
and asset-copy paths; there is no need to install both.
|
|
66
|
+
|
|
67
|
+
With a bundler, copy the complete `node_modules/viewstl/build/`
|
|
68
|
+
directory to your app's public assets (for example `public/viewstl-v2/`), keeping
|
|
69
|
+
the worker, `chunks/`, `cad/` and notices together. Set `worker_url` to its served
|
|
70
|
+
URL, or provide your bundler's worker factory.
|
|
71
|
+
Do not assume a bundler copies a module worker's dynamic chunks merely
|
|
72
|
+
because it sees a URL string.
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
import StlViewer from 'viewstl';
|
|
76
|
+
|
|
77
|
+
const viewer = new StlViewer(container, {
|
|
78
|
+
worker_url: '/viewstl-v2/viewstl.worker.js'
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For Vite, the optional factory form is:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
import ViewerWorker from 'viewstl/worker?worker';
|
|
86
|
+
const viewer = new StlViewer(container, {
|
|
87
|
+
worker_factory: () => new ViewerWorker()
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The static-assets/`worker_url` integration above was verified in production
|
|
92
|
+
builds using Vite 8.3.4 and Webpack 5.112.0, including STEP/IGES and lazy archive
|
|
93
|
+
assets. The optional Vite worker-factory example is separate integration guidance
|
|
94
|
+
and was not part of those checks. A factory must return a new worker for each
|
|
95
|
+
scheduled job and arrange for all dynamic chunks and CAD assets to be served.
|
|
96
|
+
The CommonJS entry point also requires
|
|
97
|
+
`worker_url` or `worker_factory` because it cannot infer a browser asset URL.
|
|
98
|
+
|
|
99
|
+
## Node.js model analysis
|
|
100
|
+
|
|
101
|
+
Run `npm install viewstl`. Node.js 20+ resolves the analysis
|
|
102
|
+
worker and optional CAD WASM directly inside the package; there is no asset-copy
|
|
103
|
+
step for server use.
|
|
104
|
+
|
|
105
|
+
```js
|
|
106
|
+
import { analyzeFile } from 'viewstl/node';
|
|
107
|
+
const report = await analyzeFile('part.stl', { units: 'mm' });
|
|
108
|
+
console.log(report.dimensions_mm, report.surface_area_mm2, report.volume_mm3);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
CommonJS is supported with `const { analyzeFile } = require('viewstl/node')`.
|
|
112
|
+
The `/node` entry point is separate from the browser viewer and has no DOM,
|
|
113
|
+
WebGL or Three.js dependency. It supports STL, OBJ, 3MF, STEP/STP and IGES/IGS.
|
|
114
|
+
STEP/IGES measurements describe the tessellated mesh, not exact CAD surfaces.
|
|
115
|
+
Scene files, rendering, thumbnails and server-side exporting are outside this API.
|
|
116
|
+
|
|
117
|
+
### Input and units
|
|
118
|
+
|
|
119
|
+
`analyzeFile(path, options)` accepts a local regular-file path or a `file:` URL
|
|
120
|
+
object. `analyzeBuffer(data, options)` accepts a Node Buffer, Uint8Array or
|
|
121
|
+
ArrayBuffer, and requires `filename` or `format` to identify the format:
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
import { analyzeBuffer } from 'viewstl/node';
|
|
125
|
+
const report = await analyzeBuffer(uploadBuffer, { filename: 'part.stl', units: 'mm' });
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The input buffer is copied without modifying or detaching the caller's data.
|
|
129
|
+
HTTP(S) URLs and network-share paths are rejected. The module does not fetch
|
|
130
|
+
external model resources, run a web service, or write input files to disk.
|
|
131
|
+
An application's own upload handler determines how files reach its server and
|
|
132
|
+
whether they are stored. Browser-selected files sent to a server are uploads;
|
|
133
|
+
the preview website continues to perform its checks on the user's device.
|
|
134
|
+
|
|
135
|
+
STL and OBJ carry no reliable physical units. Supply `units: 'mm'`, `'cm'`, `'m'`,
|
|
136
|
+
`'in'`, `'ft'` or `'um'` when the input scale is known. With no scale, `units` and
|
|
137
|
+
all physical measurement fields (`dimensions_mm`, `surface_area_mm2`,
|
|
138
|
+
`volume_mm3`) are null; the unit check is inconclusive. The generic `dimensions`,
|
|
139
|
+
`surface_area` and `volume` fields then use input coordinate units. When units
|
|
140
|
+
are known, these generic fields also use mm, mm² and mm³.
|
|
141
|
+
|
|
142
|
+
3MF and CAD file units are honored and normalized to mm automatically. They
|
|
143
|
+
cannot be overridden with a different input scale. `units_source` is `'file'`,
|
|
144
|
+
`'option'` or null. `volume_valid` describes validation of the mesh volume;
|
|
145
|
+
it does not confirm a physical scale or guarantee printability. Invalid or
|
|
146
|
+
unverified volume is returned as null. `level: 'basic'` skips detailed
|
|
147
|
+
intersection and validated-volume checks; the default is `'detailed'`.
|
|
148
|
+
|
|
149
|
+
### Jobs, limits and cancellation
|
|
150
|
+
|
|
151
|
+
The convenience functions share an analyzer with two active workers, up to
|
|
152
|
+
16 waiting jobs, and at most 128 MiB of copied buffer data waiting in the queue.
|
|
153
|
+
Create an independent analyzer when the host needs different concurrency:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
import { createAnalyzer } from 'viewstl/node';
|
|
157
|
+
const analyzer = createAnalyzer({ concurrency: 1, max_queue: 4 });
|
|
158
|
+
try {
|
|
159
|
+
const reports = await Promise.all(paths.map(path => analyzer.analyzeFile(path, {
|
|
160
|
+
units: 'mm', signal: controller.signal, timeout_ms: 60000,
|
|
161
|
+
limits: { max_file_bytes: 32 * 1024 ** 2, max_memory_bytes: 512 * 1024 ** 2 }
|
|
162
|
+
})));
|
|
163
|
+
} finally {
|
|
164
|
+
await analyzer.dispose();
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Here `paths` is the application's array of file paths and `controller` is its
|
|
169
|
+
AbortController. Keep submitted batches within the configured queue capacity.
|
|
170
|
+
`concurrency` is 1–8; `max_queue` may be zero; `max_queued_bytes` controls queued
|
|
171
|
+
buffer copies. Exceeding queue admission rejects with `ERR_ANALYZER_BUSY`.
|
|
172
|
+
Use a shared analyzer for a service rather than creating an unbounded number
|
|
173
|
+
of independent analyzers per request.
|
|
174
|
+
|
|
175
|
+
Per-job defaults are 64 MiB input, 1,000,000 triangles, 128 MiB expanded archive
|
|
176
|
+
data, 1,024 archive entries and a 1 GiB estimated working-set budget. These are
|
|
177
|
+
independent ceilings: the memory estimate can reject a mesh below the triangle
|
|
178
|
+
ceiling. Override them in `limits` with `max_file_bytes`, `max_triangles`,
|
|
179
|
+
`max_uncompressed_bytes`, `max_archive_entries` and `max_memory_bytes`.
|
|
180
|
+
Unknown limit names are rejected. CAD input/output and WASM-heap checks are
|
|
181
|
+
also applied, and retained CAD memory counts towards the analysis budget.
|
|
182
|
+
|
|
183
|
+
`timeout_ms` defaults to 120,000 and covers waiting, reading, parsing and checks.
|
|
184
|
+
Expiry terminates the worker and rejects with `ERR_ANALYSIS_TIMEOUT`.
|
|
185
|
+
`signal` cancels queued or active work with an `AbortError` (`ABORT_ERR`).
|
|
186
|
+
`dispose()` cancels outstanding jobs and waits for their workers to terminate.
|
|
187
|
+
Workers are also terminated after success or failure before their slots are
|
|
188
|
+
reused, releasing parser and CAD state. Processing failures do not disable
|
|
189
|
+
the analyzer.
|
|
190
|
+
|
|
191
|
+
`max_time_ms` separately limits the mesh-check stage (30,000 by default, up to
|
|
192
|
+
120,000). It may yield a partial report with `limit_reason: 'time'`. Inspect
|
|
193
|
+
`summary` and each check's status; an incomplete check is not a pass.
|
|
194
|
+
`on_progress` receives phase and elapsed-time updates. The optional `profile`
|
|
195
|
+
uses the same FDM build-volume, overhang, bed-contact and sampled wall-thickness
|
|
196
|
+
settings as browser print checks. FDM results remain estimates.
|
|
197
|
+
|
|
198
|
+
Memory limits are conservative admission estimates plus a worker JS-heap limit,
|
|
199
|
+
not a hard limit on process RSS. ArrayBuffers and WASM memory are not fully
|
|
200
|
+
covered by the JS-heap limit. Size host/container limits for total concurrency;
|
|
201
|
+
use process/container isolation when serving untrusted uploads. The library
|
|
202
|
+
does not provide an HTTP endpoint, authentication or a filesystem sandbox.
|
|
203
|
+
|
|
204
|
+
## Viewer sessions and custom interfaces
|
|
205
|
+
|
|
206
|
+
Use `StlViewer` directly for a viewer, or `ViewerSession` for a reusable loading
|
|
207
|
+
queue and model editing. Neither requires the ViewSTL website's controls, labels
|
|
208
|
+
or dialogs. Each session belongs to one container and starts its canvas/workers
|
|
209
|
+
only when loading begins (or when you explicitly call `ready()`).
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
import { ViewerSession } from './viewer/viewstl.js';
|
|
213
|
+
const session = new ViewerSession(document.getElementById('viewer'), {
|
|
214
|
+
device_memory: navigator.deviceMemory,
|
|
215
|
+
viewer_options: { worker_url: '/viewer/viewstl.worker.js' },
|
|
216
|
+
on_event(event) {
|
|
217
|
+
if (event.type === 'error') console.error(event.code, event.message);
|
|
218
|
+
if (event.type === 'progress') console.log(event.progress);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
await session.openFiles(fileInput.files);
|
|
222
|
+
const models = session.getModels(); // detached metadata; no mesh copies
|
|
223
|
+
if (models.length) session.setRotation(models[0].id, {x: 0, y: 0, z: 90});
|
|
224
|
+
// Existing export/analysis/camera APIs stay on StlViewer:
|
|
225
|
+
await session.viewer.download_scene({format: 'vsb'});
|
|
226
|
+
session.dispose();
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The host supplies any file input, toolbar and status display. `viewer_options`
|
|
230
|
+
accepts existing constructor options except `models` and `allow_drag_and_drop`;
|
|
231
|
+
pass initial/dropped files to the session loading methods so it tracks imported models. Existing viewer callbacks are
|
|
232
|
+
composed with session notifications. Optional `colors` supplies fallback colors
|
|
233
|
+
for files without their own colors. Without an override the memory policy uses
|
|
234
|
+
half the browser's supplied device-memory estimate, or half a conservative 2 GiB
|
|
235
|
+
fallback. This is a limit estimate, not a free-memory measurement.
|
|
236
|
+
|
|
237
|
+
| Session API | Contract |
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| `ready()`, `viewer` | Explicitly initialize and await the viewer; the property is null before initialization. |
|
|
240
|
+
| `openFiles(files)` | Queue Files or configured {name, url, size?} sources. VSB/VSJ imports and VSJ companions use the same path. |
|
|
241
|
+
| `openURL(url, format?)` | Queue a remote model/scene through the explicitly configured same-origin relay. |
|
|
242
|
+
| `cancelLoading()` | Cancel queued/active loading, retaining existing models. |
|
|
243
|
+
| `getModel(id)`, `getModels()` | Detached metadata including name, bounds, transforms, reset baselines and animation definition/state; no private geometry buffers. |
|
|
244
|
+
| `setPosition(id, xyz)`, `setRotation(id, degrees)` | Edit center-preserving transforms; return false for invalid input. Stop only the edited model's animation. Original StlViewer methods continue to use radians. |
|
|
245
|
+
| `prepareManual(id)`, `centerPivot(id)` | Capture/stop current playback; optionally rebase the pivot without moving any rendered vertex. |
|
|
246
|
+
| `moveDepth(id, amount)` | Translate along the eye-to-model ray. Positive moves away, negative moves nearer. |
|
|
247
|
+
| `setArrangement(mode)` | original, side-by-side or custom; keep source geometry unchanged. |
|
|
248
|
+
| `setGrid(enabled, spacing?, height?)` | Configure the infinite XY grid. |
|
|
249
|
+
| `duplicate(id)`, `remove(id)`, `clear()` | Manage session models through these methods to retain session metadata/reset baselines. |
|
|
250
|
+
| `bindManipulation(options)` | After ready, bind move/rotate/depth canvas gestures; options supply getModelId(event?), getTool(), onChange/onStart/onEnd. Returns setEnabled/dispose. No toolbar or styles are created. |
|
|
251
|
+
| `dispose()` | Cancel work and release manipulation listeners, workers and viewer resources. Idempotent; later operations reject. |
|
|
252
|
+
|
|
253
|
+
Read-only `busy`, `arrangement`, `grid` and `memoryPolicy` expose current settings.
|
|
254
|
+
Events are `ready`, `busy`, `opening`, `progress`, `loaded`, `scene-loaded`,
|
|
255
|
+
`load-failed`, `error` and `animation`; their shapes are in the public types.
|
|
256
|
+
Queue promises settle after work drains, including per-file failures reported
|
|
257
|
+
through events. Inspect the resulting models/events rather than treating promise
|
|
258
|
+
resolution alone as proof every file succeeded. Invalid URL arguments throw.
|
|
259
|
+
|
|
260
|
+
**Migration from rc.8:** `mountViewstlApp` and its `ViewstlApp*` types are no longer
|
|
261
|
+
exported. That helper depended on ViewSTL-specific HTML IDs and UI policy. Use
|
|
262
|
+
`ViewerSession` or `StlViewer` with your own UI, or retain your pinned rc.8 build
|
|
263
|
+
while migrating. Existing published releases and legacy v1 remain available.
|
|
264
|
+
The ViewSTL website's controller is maintained separately, not distributed in
|
|
265
|
+
the plugin repository or npm package. Ordinary `StlViewer` integrations keep their API.
|
|
266
|
+
|
|
267
|
+
## Rendering and file processing
|
|
268
|
+
|
|
269
|
+
- WebGL2 only; no CanvasRenderer, WebGL1 or IE fallback.
|
|
270
|
+
- No Three.js or other third-party library in the STL/OBJ rendering core.
|
|
271
|
+
- ASCII/binary STL, VisCAM/MeshLab and Materialise colors, plus the legacy
|
|
272
|
+
ViewSTL colored-STL convention.
|
|
273
|
+
- OBJ polygon fan triangulation and negative indices. OBJ materials,
|
|
274
|
+
textures and robust triangulation of concave polygons are not included.
|
|
275
|
+
- Core 3MF meshes, nested components, build transforms, units, base
|
|
276
|
+
materials and vertex colors. Root model relationships are followed.
|
|
277
|
+
Texture/composite materials, external component parts and unsupported
|
|
278
|
+
required extensions produce explicit errors rather than silent loss.
|
|
279
|
+
- STEP/STP and IGES/IGS solid/surface import via the optional pinned
|
|
280
|
+
Open CASCADE WASM reader. Units normalize to millimetres; assembly placements
|
|
281
|
+
and mesh/face colors are retained. All surfaces in a file form one model.
|
|
282
|
+
The display mesh is an approximation (relative deflection 0.001, angular
|
|
283
|
+
deflection 0.5 radians), not an exact CAD editing or measurement kernel.
|
|
284
|
+
Use self-contained exports: external part files are not fetched, and native
|
|
285
|
+
SolidWorks/Solid Edge formats, feature history, PMI and curve-only files are
|
|
286
|
+
unsupported. Incomplete external-reference assemblies may lack parts.
|
|
287
|
+
- Deploy the entire `build/cad/` directory alongside `chunks/` and the worker.
|
|
288
|
+
CAD code/WASM is loaded only for CAD files; local input bytes are never uploaded.
|
|
289
|
+
Each job's worker is terminated on completion, cancellation, error or timeout.
|
|
290
|
+
CAD admission estimates 128 MiB plus 40 times the input size. The WASM heap
|
|
291
|
+
is separately capped to at most 512 MiB and to part of the remaining scene
|
|
292
|
+
budget. Output arrays and triangle counts are also checked; these checks
|
|
293
|
+
cannot predict exact peak browser/GPU memory for every CAD file.
|
|
294
|
+
- A strict CSP must allow `script-src 'self' 'wasm-unsafe-eval'` for the worker;
|
|
295
|
+
JavaScript `unsafe-eval` is not needed. Serve `.wasm` as `application/wasm`.
|
|
296
|
+
`scripts/prepare-cad.mjs` replaces the pinned glue's dynamic bindings with
|
|
297
|
+
static closures and adds its heap ceiling. The WASM is unmodified. Adapted
|
|
298
|
+
glue, the recipe, license texts and source references ship with the assets.
|
|
299
|
+
- VSJ JSON and VSB archives containing `json_data.vsj` and STL files.
|
|
300
|
+
- ZIP/3MF uses separately loaded worker chunks containing pinned fflate,
|
|
301
|
+
saxes and xmlchars. Those chunks are not fetched for ordinary STL/OBJ.
|
|
302
|
+
- ZIP accepts stored and deflated entries, validates CRC/length and bounds
|
|
303
|
+
expanded data. ZIP64, encryption and multi-disk archives are unsupported.
|
|
304
|
+
- Local files never leave the browser. Remote URLs obey normal CORS and
|
|
305
|
+
same-origin credential rules; there is no upload service or CORS proxy.
|
|
306
|
+
|
|
307
|
+
Parsing, bounds, flat normals and initial requested smooth/edge geometry
|
|
308
|
+
are calculated in workers. Results use transferable buffers. Workers are
|
|
309
|
+
bounded and terminated after jobs, on cancellation, and on disposal.
|
|
310
|
+
|
|
311
|
+
Changing an already-loaded model to smooth/edges/wireframe derives its
|
|
312
|
+
additional data in a worker. That operation intentionally copies positions
|
|
313
|
+
once, since the main thread retains them for synchronous export and picking.
|
|
314
|
+
Nonuniform-scale area queries also require a cached synchronous scan. These
|
|
315
|
+
are distinct from the initial file handoff, which does not clone geometry.
|
|
316
|
+
|
|
317
|
+
Rendering runs only when needed, including during interaction and animation.
|
|
318
|
+
Transparency uses model-level back-to-front sorting; intersecting transparent
|
|
319
|
+
surfaces can still have ordering artifacts. STL export stores face color,
|
|
320
|
+
so a 3MF triangle's per-vertex gradient cannot survive STL/VSB export exactly.
|
|
321
|
+
|
|
322
|
+
## Model animation
|
|
323
|
+
|
|
324
|
+
Animation is included in `2.0.0-rc.5` on GitHub and in both npm packages.
|
|
325
|
+
Playback remains inside the native WebGL2 plugin.
|
|
326
|
+
|
|
327
|
+
After a model has loaded, one call starts continuous rotation:
|
|
328
|
+
|
|
329
|
+
```js
|
|
330
|
+
await viewer.when_idle();
|
|
331
|
+
viewer.animate_model(0, {
|
|
332
|
+
delta: { rotationz: 2 * Math.PI, msec: 5000, loop: true },
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
The legacy `animation` model option also works. `delta` adds a change to the
|
|
337
|
+
starting pose; `exact` interpolates toward an absolute target. They can operate
|
|
338
|
+
together on different properties and finish independently:
|
|
339
|
+
|
|
340
|
+
```js
|
|
341
|
+
viewer.animate_model(0, {
|
|
342
|
+
delta: { rotationx: 1, rotationy: 1.1, rotationz: 1.2, msec: 1000, loop: true },
|
|
343
|
+
exact: { x: 1000, y: 150, z: 200, scale: 5, msec: 3000 },
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
viewer.pause_model_animation(0); // Keep the pose and elapsed time.
|
|
347
|
+
viewer.resume_model_animation(0);
|
|
348
|
+
console.log(viewer.get_model_animation_state(0));
|
|
349
|
+
viewer.animate_model(0, null); // Stop at the current pose; discard playback.
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Supported properties are `x/y/z`, `rotationx/y/z` in radians and
|
|
353
|
+
`scale` or `scalex/scaley/scalez`. Durations are positive milliseconds, defaulting
|
|
354
|
+
to 300. Scale deltas are additive, as in the legacy API; all resulting scales
|
|
355
|
+
must stay positive. Only delta tracks repeat, accumulating their change each
|
|
356
|
+
cycle. A negative looping scale delta is rejected. Delta and exact may not claim
|
|
357
|
+
the same property. Inputs must be finite and within JavaScript's safe numeric
|
|
358
|
+
range. Invalid replacements leave the current animation intact.
|
|
359
|
+
|
|
360
|
+
Starting a new animation replaces the old one from the current pose. Manual
|
|
361
|
+
position, rotation, scale and unit setters stop that model's playback first.
|
|
362
|
+
Camera interaction, color and viewing-mode changes remain usable. Completed
|
|
363
|
+
tracks no longer write their properties. Removal, `clean()` and `dispose()`
|
|
364
|
+
release playback; paused/completed models do not keep the render loop running.
|
|
365
|
+
|
|
366
|
+
Timing follows elapsed frame timestamps, not frame count. Hidden documents and
|
|
367
|
+
lost WebGL contexts suspend playback without including that gap on resumption.
|
|
368
|
+
User pause is independent of this automatic suspension. State queries return
|
|
369
|
+
`status`, `elapsed_ms`, `suspended` and active `tracks`; unknown IDs return null.
|
|
370
|
+
|
|
371
|
+
Set callbacks in the viewer options:
|
|
372
|
+
|
|
373
|
+
```js
|
|
374
|
+
const viewer = new StlViewer(container, {
|
|
375
|
+
animation_state_callback(id, state, reason) {
|
|
376
|
+
console.log(id, state.status, reason);
|
|
377
|
+
},
|
|
378
|
+
animation_complete_callback(id, state) {
|
|
379
|
+
console.log('Finished', id, state.elapsed_ms);
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Callbacks describe transitions, not every frame, and are deferred until the
|
|
385
|
+
current operation finishes. Completion fires once when every track finishes;
|
|
386
|
+
an animation with a looping track does not complete. Call playback controls on
|
|
387
|
+
loaded models, for example after `when_idle()`.
|
|
388
|
+
|
|
389
|
+
VSB/VSJ preserve the animation definition, original pose, elapsed phase, rotation
|
|
390
|
+
pivot and pause flag. Both scene-loading APIs start incoming animations together
|
|
391
|
+
after all incoming models load. VSJ still needs its referenced model files.
|
|
392
|
+
Animated VSB entries retain unscaled mesh data and explicit scale metadata;
|
|
393
|
+
static entries retain the previous scale-baked convention. This prevents scale
|
|
394
|
+
being applied twice. Older viewers may read the pose but cannot restore the new
|
|
395
|
+
playback-state extension exactly.
|
|
396
|
+
|
|
397
|
+
PNG and modern `export_models()` capture the current pose without stopping
|
|
398
|
+
playback. Legacy `get_stl_bin()` keeps its documented scale/centering-only export
|
|
399
|
+
convention. Regular model files do not store animation. Print checks should run
|
|
400
|
+
while the affected models are paused; subsequent motion makes the report stale.
|
|
401
|
+
|
|
402
|
+
A self-contained example is in `examples/v2/animation.html` in the source
|
|
403
|
+
checkout (serve with `pnpm start`). It includes Play, Pause, Resume, Stop and Reset.
|
|
404
|
+
|
|
405
|
+
Website animation controls are host-owned. Use the animation API above and
|
|
406
|
+
`ViewerSession.centerPivot(id)` for a spin around the mesh center. Session model
|
|
407
|
+
snapshots expose playback and definitions without accessing private model data.
|
|
408
|
+
The host decides how to respect reduced-motion preferences, pause for a print
|
|
409
|
+
check, present a Resume action and update its readouts. Scene saving retains
|
|
410
|
+
playback; PNG and regular model exports snapshot the current pose.
|
|
411
|
+
|
|
412
|
+
## Existing API and intentional differences
|
|
413
|
+
|
|
414
|
+
The existing constructor, model options and documented method names are
|
|
415
|
+
implemented. See [the full inventory](v2-compatibility.md). Existing getters
|
|
416
|
+
remain synchronous. `get_vsb_blob()` continues to return a Promise.
|
|
417
|
+
|
|
418
|
+
- `width` / `height` override their `canvas_width` / `canvas_height` aliases.
|
|
419
|
+
- `bg_color` overrides `bgcolor`.
|
|
420
|
+
- `on_model_mousedown` overrides `on_model_mouseclick`; both setter names work.
|
|
421
|
+
- `set_model_units(model_id, units, scale)` keeps the executable three-argument
|
|
422
|
+
signature. The old documentation omitted `model_id` incorrectly.
|
|
423
|
+
- `load_three_files: 'directory/'` remains a legacy alias for the worker
|
|
424
|
+
directory, resolved against the page. It never loads Three.js. An explicit
|
|
425
|
+
`worker_url` takes precedence; false/omitted uses the entry point's directory.
|
|
426
|
+
- `jszip_path` and `jszip_utils_path` are accepted deprecated options, with
|
|
427
|
+
no effect: archive support is shipped in the package's lazy worker chunks.
|
|
428
|
+
No scripts are fetched from those legacy paths.
|
|
429
|
+
- `get_model_info().dims` now updates after scaling. The old implementation
|
|
430
|
+
returned stale dimensions even when area and volume had changed.
|
|
431
|
+
- Zero IDs, zero coordinates, zero rotations and zero opacity are valid.
|
|
432
|
+
Invalid positive-only scales, colors, transforms and IDs report errors.
|
|
433
|
+
- Model-loading batches preserve the legacy final callback order:
|
|
434
|
+
`all_loaded_callback` precedes the final `model_loaded_callback`.
|
|
435
|
+
Failed/cancelled jobs settle the batch instead of leaving it hanging.
|
|
436
|
+
An empty initial viewer does not emit `all_loaded_callback`.
|
|
437
|
+
- `set_center_models` controls centering of subsequently loaded models.
|
|
438
|
+
- Model data remains CPU-resident for synchronous export/picking and context
|
|
439
|
+
restoration. `clean()` and `dispose()` release GPU buffers and worker jobs.
|
|
440
|
+
- `dispose()` removes only the viewer's owned DOM/listeners, not its siblings.
|
|
441
|
+
- The renderer, camera and models are native objects. Direct Three.js calls
|
|
442
|
+
into `.camera`, `.scene`, `.renderer`, or `.models` are not compatible.
|
|
443
|
+
|
|
444
|
+
STL export retains the legacy convention: geometry scale and centering are
|
|
445
|
+
baked into vertices; scene position and rotation are not baked. VSB saves
|
|
446
|
+
position/rotation in its manifest and avoids applying scale twice.
|
|
447
|
+
|
|
448
|
+
Volume is a signed-tetrahedron mesh calculation reported as an absolute
|
|
449
|
+
value, not a watertightness or printability certification. For inch models,
|
|
450
|
+
area/volume follow the legacy conversion convention.
|
|
451
|
+
|
|
452
|
+
## Optional Three.js mesh bridge
|
|
453
|
+
|
|
454
|
+
The ordinary viewer never imports Three.js. To request a real Three.js
|
|
455
|
+
Mesh from `get_model_mesh`, pass your own instance:
|
|
456
|
+
|
|
457
|
+
```js
|
|
458
|
+
const viewer = new StlViewer(container, { three: THREE });
|
|
459
|
+
// after loading:
|
|
460
|
+
const mesh = viewer.get_model_mesh(0);
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
The bridge uses `options.three` or `globalThis.THREE`. With neither present,
|
|
464
|
+
`get_model_mesh` reports a load error and returns null. It does not return
|
|
465
|
+
a misleading imitation Mesh.
|
|
466
|
+
|
|
467
|
+
Import supports static BufferGeometry meshes and legacy Geometry vertices/
|
|
468
|
+
faces. It copies geometry and basic material color/opacity. Placement is
|
|
469
|
+
controlled by the supplied model options, as in the old plugin. Skinning,
|
|
470
|
+
instances, morph targets, child objects, multiple/textured/shader materials
|
|
471
|
+
are rejected. Export returns an independent BufferGeometry Mesh. Dispose
|
|
472
|
+
the returned mesh's geometry and material when finished with it.
|
|
473
|
+
|
|
474
|
+
Use `get_model_data(id)` for a native copy of raw geometry, and
|
|
475
|
+
`add_model({ data, ...options })` to add it without Three.js. Data does not
|
|
476
|
+
include scene transforms; copy those options explicitly when duplicating.
|
|
477
|
+
|
|
478
|
+
Use `get_image_blob()` for screenshots; direct `renderer.domElement.toDataURL`
|
|
479
|
+
outside a draw is not guaranteed because continuous drawing-buffer
|
|
480
|
+
preservation is disabled.
|
|
481
|
+
|
|
482
|
+
## Additional options and helpers
|
|
483
|
+
|
|
484
|
+
| Option | Default |
|
|
485
|
+
| --- | --- |
|
|
486
|
+
| `worker_url` | Beside the ESM/classic script |
|
|
487
|
+
| `worker_factory` | None |
|
|
488
|
+
| `worker_count` | 2, allowed range 1–4 |
|
|
489
|
+
| `worker_timeout` | 120,000 ms per job |
|
|
490
|
+
| `pixel_ratio` | Device pixel ratio capped at 2 |
|
|
491
|
+
| `max_file_bytes` | 256 MiB |
|
|
492
|
+
| `max_triangles` | 5,000,000 |
|
|
493
|
+
| `max_uncompressed_bytes` | 256 MiB per archive |
|
|
494
|
+
| `max_archive_entries` | 1,024 |
|
|
495
|
+
| `max_memory_bytes` | `Number.MAX_SAFE_INTEGER` (optional estimated working-set budget) |
|
|
496
|
+
|
|
497
|
+
Limits are configurable positive integers. These are ceilings, not guarantees
|
|
498
|
+
that every mobile device has enough memory for the maximum. Derived smooth
|
|
499
|
+
normals/edge maps can use substantially more memory than flat rendering.
|
|
500
|
+
|
|
501
|
+
`max_memory_bytes` adds conservative checks for parsed geometry, text parsing,
|
|
502
|
+
archive expansion, expanded 3MF instances, and derived smooth/wire/edge maps.
|
|
503
|
+
It estimates working memory, including GPU buffers; it does not measure free
|
|
504
|
+
memory. Existing default byte/triangle limits remain unchanged for embedders.
|
|
505
|
+
The ViewSTL preview site configures its own device-dependent limits: half the
|
|
506
|
+
approximate `navigator.deviceMemory` report, or half a 2 GiB fallback if absent.
|
|
507
|
+
This leaves a 50% margin against that estimate, not a guaranteed allocation or
|
|
508
|
+
protection against an OS/browser killing an exhausted tab.
|
|
509
|
+
|
|
510
|
+
Local binary STL files are parsed in chunks of at most 65,536 triangles
|
|
511
|
+
(3.125 MiB input data) plus their 84-byte header. This avoids holding a complete
|
|
512
|
+
input file and a second assembled copy alongside geometry. ASCII/OBJ/archive
|
|
513
|
+
input is still read as a complete buffer; OBJ no longer creates a full array
|
|
514
|
+
of every input line. Native GPU allocation errors clean up partial resources
|
|
515
|
+
and report through `load_error_callback`.
|
|
516
|
+
|
|
517
|
+
`when_ready` resolves after renderer initialization, before initial models
|
|
518
|
+
finish. `when_idle()` waits for pending model, scene and derived-geometry
|
|
519
|
+
work; inspect `load_error_callback` for individual failures. `get_image_blob`
|
|
520
|
+
waits for that work, draws and returns a PNG Blob by default.
|
|
521
|
+
|
|
522
|
+
Keyboard: arrows orbit; + / - zoom; F fits the scene. Mouse: drag orbits,
|
|
523
|
+
shift/right/middle drag pans, wheel zooms. Touch: one finger rotates and
|
|
524
|
+
two fingers pan/pinch. Real mobile-device validation remains necessary.
|
|
525
|
+
|
|
526
|
+
For a same-origin standalone deployment, allow your script and worker assets
|
|
527
|
+
in the site's Content Security Policy (`script-src 'self'; worker-src 'self'`),
|
|
528
|
+
and permit intended remote model hosts in `connect-src`. SharedArrayBuffer,
|
|
529
|
+
cross-origin isolation, eval and worker blob bootstrapping are not required.
|
|
530
|
+
|
|
531
|
+
## Additional views and section cuts
|
|
532
|
+
|
|
533
|
+
The existing `flat`, `smooth` and `wireframe` display values remain supported.
|
|
534
|
+
New display values are `edges` (solid with 30-degree feature edges), `mesh`
|
|
535
|
+
(triangle overlay), `xray`, `studio` and `orientation` (blue front faces, red
|
|
536
|
+
back faces). None requires Three.js. `view_edges` / `set_edges` retain the
|
|
537
|
+
legacy one-degree edge threshold.
|
|
538
|
+
|
|
539
|
+
```js
|
|
540
|
+
viewer.set_view(0, {display: 'studio', smooth_shading: true});
|
|
541
|
+
await viewer.when_idle();
|
|
542
|
+
viewer.set_smooth(0, false); // Independent of the selected modern view
|
|
543
|
+
viewer.set_section(0, {axis: 'z', position: 0.5, flip: false});
|
|
544
|
+
viewer.set_section(0, null); // Restore the complete model
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
These fields are also accepted in model options, returned by `get_model_info`,
|
|
548
|
+
and preserved by VSJ/VSB scene export. `set_display('flat')` and
|
|
549
|
+
`set_display('smooth')` keep their legacy shading behavior. Smooth shading is
|
|
550
|
+
ignored visually in wireframe and retained for the next surface view. Derived
|
|
551
|
+
view changes commit after preparation succeeds; a rejected memory estimate or
|
|
552
|
+
failed worker preserves the previous appearance. Wait for `when_idle()` and
|
|
553
|
+
inspect `load_error_callback` as with model loading.
|
|
554
|
+
|
|
555
|
+
Mesh overlay uses shader barycentric coordinates without an extra geometry
|
|
556
|
+
buffer. Studio, orientation, X-ray and clipping also reuse existing geometry.
|
|
557
|
+
Smooth normals, wireframe and feature edges are computed on demand in a worker
|
|
558
|
+
and checked against the memory budget, including cached derived arrays.
|
|
559
|
+
|
|
560
|
+
Section position is a fraction from 0 to 1 of the source model's bounding box
|
|
561
|
+
along its chosen axis. The plane follows model transforms, clips picking and
|
|
562
|
+
PNG rendering, and does not modify exported STL geometry or model statistics.
|
|
563
|
+
The cut remains open, with an amber boundary highlight; no cap is generated.
|
|
564
|
+
X-ray uses lightweight back-face/front-face alpha passes; it is an illustrative
|
|
565
|
+
view, not exact order-independent transparency for intersecting surfaces.
|
|
566
|
+
Face orientation describes winding relative to the camera, not an automatic
|
|
567
|
+
watertightness or printability diagnosis.
|
|
568
|
+
|
|
569
|
+
## Optional streaming URL imports
|
|
570
|
+
|
|
571
|
+
The plugin does not silently route existing `filename` requests through a server.
|
|
572
|
+
To retrieve a public URL without depending on the source server's CORS policy,
|
|
573
|
+
configure a same-origin relay and explicitly use `remote_url`:
|
|
574
|
+
|
|
575
|
+
```js
|
|
576
|
+
const viewer = new StlViewer(container, {url_import_endpoint: '/api/import-url'});
|
|
577
|
+
viewer.add_model({id: 0, remote_url: 'https://example.com/model.stl'});
|
|
578
|
+
// Optional remote_format: 'stl', 'obj', '3mf', 'step', 'stp', 'iges', 'igs'.
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
`ViewerSession` accepts the endpoint inside `viewer_options` and exposes
|
|
582
|
+
`session.openURL(url, format)`. The host owns URL input and progress presentation;
|
|
583
|
+
the plugin owns validation, streaming, parsing, the queue and cancellation.
|
|
584
|
+
Session jobs default to a ten-minute worker timeout.
|
|
585
|
+
|
|
586
|
+
The relay receives the URL in a JSON POST body and streams the response without
|
|
587
|
+
file storage. A signed URL's query string is not displayed as a model name or
|
|
588
|
+
included in scene exports. Local files still never leave the browser. URL imports
|
|
589
|
+
are a separate privacy path: the relay sees the remote bytes in transit.
|
|
590
|
+
|
|
591
|
+
Binary STL is decoded incrementally in the worker. Other formats use bounded
|
|
592
|
+
browser buffers, including archives and CAD. A complete model appears after
|
|
593
|
+
download and validation; progressive partial rendering is not implemented.
|
|
594
|
+
`get_model_info().file_bytes` reports the imported byte count when available.
|
|
595
|
+
Downloaded names are metadata, not reloadable URLs; use VSB to package a scene
|
|
596
|
+
containing these models. Embedders must provide their own compatible relay; no
|
|
597
|
+
ViewSTL server is implicitly used by the library.
|
|
598
|
+
|
|
599
|
+
A host can extract links from its own drop events and call `session.openURL`.
|
|
600
|
+
Page-wide drag overlays, editable-field handling and URL dialogs belong to that
|
|
601
|
+
host. The low-level viewer's existing local file-drop contract is unchanged.
|
|
602
|
+
|
|
603
|
+
The reference relay lives in the website repository's `relay/` directory. Its
|
|
604
|
+
request is `{url, format, max_bytes}` and its response supplies X-ViewSTL-Format,
|
|
605
|
+
percent-encoded X-ViewSTL-Filename, and optional Content-Length. All relay and
|
|
606
|
+
parser failures use the existing error callback. Cancellation terminates the
|
|
607
|
+
worker/fetch; the server then closes the upstream connection.
|
|
608
|
+
|
|
609
|
+
## Local mesh export
|
|
610
|
+
|
|
611
|
+
The host owns its Save dialog and passes explicit model IDs/options to the
|
|
612
|
+
export methods. Model exports and whole-scene VSB/VSJ exports remain distinct.
|
|
613
|
+
Use AbortSignal to cancel when the host dismisses or changes an active operation;
|
|
614
|
+
the plugin does not prescribe a modal layout or panel-closing policy.
|
|
615
|
+
|
|
616
|
+
All exporters live in the plugin and run in disposable workers. They generate
|
|
617
|
+
local Blobs; no relay, upload, server conversion or third-party network endpoint
|
|
618
|
+
is used. Export modules and ZIP compression load only on demand. STEP/IGES writing
|
|
619
|
+
uses JavaScript and does **not** download the CAD importer or WASM engine.
|
|
620
|
+
|
|
621
|
+
```js
|
|
622
|
+
await viewer.when_idle();
|
|
623
|
+
const controller = new AbortController();
|
|
624
|
+
const result = await viewer.export_models({
|
|
625
|
+
format: '3mf', // stl, obj, 3mf, step/stp, iges/igs
|
|
626
|
+
model_ids: [0, 2], // omitted = all; 'selected' = selected model
|
|
627
|
+
filename: 'assembly', // correct extension added/replaced automatically
|
|
628
|
+
units: 'mm', // mm (default), inch
|
|
629
|
+
apply_transforms: true, // default: bake position, rotation, scale and centering
|
|
630
|
+
colors: true, // default for OBJ/3MF; ignored for STL/STEP/IGES
|
|
631
|
+
signal: controller.signal,
|
|
632
|
+
on_progress({ loaded, total, phase }) {
|
|
633
|
+
// Triangle counts. phase is 'exporting' or 'complete'.
|
|
634
|
+
// Encoding can reach total before ZIP packaging completes.
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
// {blob, filename, format, units, model_ids, triangles, representation: 'mesh'}
|
|
638
|
+
// Keep result.blob, or use the convenience method to initiate a browser download:
|
|
639
|
+
await viewer.download_models({format: 'step', model_ids: 'selected'});
|
|
640
|
+
// controller.abort() rejects an active/queued operation with AbortError.
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
Exports capture the model state at dispatch; subsequent edits/removal do not
|
|
644
|
+
change an in-flight export. Buffers used for rendering are never transferred or
|
|
645
|
+
detached. No selection, an unknown ID, duplicate IDs or an unfinished model
|
|
646
|
+
rejects the promise. Call `when_idle()` first to include pending imports. Errors
|
|
647
|
+
reject the promise and progress goes only to the supplied callback; exporters do
|
|
648
|
+
not show dialogs or invoke the viewer's import-error callback. A throwing progress
|
|
649
|
+
callback rejects its export and terminates the worker. `clean()`/`dispose()` cancel
|
|
650
|
+
active and queued jobs. The caller owns the returned Blob and its lifetime.
|
|
651
|
+
|
|
652
|
+
`apply_transforms: false` exports source coordinates and their accumulated unit
|
|
653
|
+
conversion, without centering, presentation movement/rotation or user scaling.
|
|
654
|
+
Scene coordinates are treated as millimeters; `units: 'inch'` divides output
|
|
655
|
+
coordinates by 25.4 and writes inch metadata where the format supports it.
|
|
656
|
+
Unitless inputs must be interpreted/scaled appropriately by the embedding app.
|
|
657
|
+
STL and OBJ have no standardized units; OBJ includes an informational comment.
|
|
658
|
+
|
|
659
|
+
| Format | Output and scope |
|
|
660
|
+
| --- | --- |
|
|
661
|
+
| STL | Standard binary STL; selected meshes combined into one triangle stream. Attributes are zero, so no ambiguous vendor color encoding. |
|
|
662
|
+
| OBJ | With `colors: true`, a ZIP containing `model.obj` and `materials.mtl`, including object groups, face normals, per-face colors and opacity. Vertex colors are averaged per face and colors/opacity quantized to 8-bit channels. With `colors: false`, a plain `.obj` file. |
|
|
663
|
+
| 3MF | ZIP/XML package with separate mesh objects, indexed vertices, units and per-vertex color/opacity using the Materials extension. Requires non-degenerate triangles, paired consistently oriented edges, and positive volume. Open/inconsistently wound meshes reject; there is no automatic repair or full self-intersection validation. |
|
|
664
|
+
| STEP | AP214 `AUTOMOTIVE_DESIGN`: separate named products made of planar triangle faces in open shells. Exact displayed mesh coordinates are written; this is a surface model, not a reconstructed CAD solid. |
|
|
665
|
+
| IGES | IGES 5.3 bounded planes (108/form 1) with closed 3D polylines (106/form 12). Models use separate levels. Triangle surfaces, not stitched CAD solids. |
|
|
666
|
+
|
|
667
|
+
STEP/IGES preserve mesh geometry and placement, but omit colors, textures,
|
|
668
|
+
analytic curves, original CAD topology/assemblies and feature history. They work
|
|
669
|
+
with open triangle meshes, but reject zero-area triangles. No F3D export is
|
|
670
|
+
provided. A consuming site can describe these limitations in its own interface;
|
|
671
|
+
the low-level export API is UI-free. CAD interoperability has been verified
|
|
672
|
+
against Open CASCADE, not every commercial CAD application.
|
|
673
|
+
|
|
674
|
+
Section cuts, camera orientation, grid, selection frames and viewing/shading modes
|
|
675
|
+
are presentation settings and do not modify exported geometry. Use VSJ/VSB for
|
|
676
|
+
ViewSTL scene settings. Texture/UV data discarded at import cannot be recreated.
|
|
677
|
+
|
|
678
|
+
The scene memory budget includes pending exports. Output is encoded in chunks
|
|
679
|
+
instead of building one large text string; Blobs still require browser storage
|
|
680
|
+
and this API does not stream directly to a filesystem handle. Output obeys
|
|
681
|
+
`max_file_bytes`, archives also obey `max_uncompressed_bytes` and
|
|
682
|
+
`max_archive_entries`, and the complete selection obeys `max_triangles`.
|
|
683
|
+
Conservative format-specific memory estimates can reject an export before copying
|
|
684
|
+
geometry. Cancellation terminates its worker, including compression.
|
|
685
|
+
|
|
686
|
+
The existing synchronous `get_stl_bin`/`download_model` and VSJ/VSB methods retain
|
|
687
|
+
their legacy behavior. Use the new API for scene transforms, correct filename
|
|
688
|
+
extensions, asynchronous execution and cancellation.
|
|
689
|
+
|
|
690
|
+
## Development and packaging
|
|
691
|
+
|
|
692
|
+
```sh
|
|
693
|
+
pnpm install --frozen-lockfile
|
|
694
|
+
pnpm build
|
|
695
|
+
pnpm test
|
|
696
|
+
pnpm check:types
|
|
697
|
+
pnpm check:node-types
|
|
698
|
+
pnpm check:legacy
|
|
699
|
+
pnpm start
|
|
700
|
+
pnpm pack
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
Open `/examples/v2/` for the demo and `/tests/v2/browser.html` for real worker/
|
|
704
|
+
WebGL checks. `pnpm pack` builds a local tarball; it does not publish anything.
|
|
705
|
+
The package allowlist includes v2 artifacts, types, documentation and license
|
|
706
|
+
notices. It excludes legacy snapshots, old dependencies, tests and server data.
|
|
707
|
+
|
|
708
|
+
Before a stable release, validate real mobile hardware, Safari and Firefox,
|
|
709
|
+
larger user fixtures, npm/bundler integrations and the integrations used by
|
|
710
|
+
the production site. Automated and desktop browser results are recorded in
|
|
711
|
+
[verification](v2-verification.md).
|
|
712
|
+
|
|
713
|
+
## Multiple models, colors and an infinite grid
|
|
714
|
+
|
|
715
|
+
Use `center_models: false` to preserve the coordinates in assembly files, then call
|
|
716
|
+
`add_model` / `add_models` to append files. `set_position` changes presentation
|
|
717
|
+
coordinates without rewriting source geometry. The preview site's Models panel
|
|
718
|
+
adds selection, individual removal and reversible side-by-side placement.
|
|
719
|
+
|
|
720
|
+
```js
|
|
721
|
+
const viewer = new StlViewer(element, { center_models: false });
|
|
722
|
+
viewer.add_models([{id: 0, local_file: partA}, {id: 1, local_file: partB}]);
|
|
723
|
+
await viewer.when_idle();
|
|
724
|
+
viewer.set_color(0, '#dd766b');
|
|
725
|
+
viewer.set_color(0, null); // Restore embedded file colors (or neutral gray if absent).
|
|
726
|
+
viewer.set_bg_color('#202d35');
|
|
727
|
+
viewer.set_infinite_grid(true, 1, 0); // enabled, base spacing, Z height
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
The infinite XY grid is a procedural shader. Its spacing adapts as the camera
|
|
731
|
+
zooms and it fades near the horizon. Panning does not allocate a larger grid;
|
|
732
|
+
opaque models occlude it. `set_grid` retains its existing finite-grid behavior.
|
|
733
|
+
Infinite settings round trip through VSJ/VSB as `infinite_grid: {spacing,height}`.
|
|
734
|
+
Colors change shader uniforms without rebuilding the mesh buffers.
|
|
735
|
+
|
|
736
|
+
An explicit `max_memory_bytes` budget now covers the whole scene, including
|
|
737
|
+
resident geometry, cached view arrays and pending derived geometry. Parsing is
|
|
738
|
+
serialized under that budget so each new model receives the remaining allowance.
|
|
739
|
+
Removing a model frees its allowance and cancels its pending derived work.
|
|
740
|
+
These remain conservative estimates, not measurements of free RAM or GPU memory.
|
|
741
|
+
|
|
742
|
+
### Selecting a model for editing
|
|
743
|
+
|
|
744
|
+
`set_selection(id)` draws a light 3D bounding frame using that model's transform.
|
|
745
|
+
`set_selection(null)` clears it. The frame uses one 288-byte vertex buffer, follows
|
|
746
|
+
rotation/translation/scale without rebuilding mesh buffers, and is excluded from
|
|
747
|
+
PNG and scene exports. Removing or clearing its model releases the frame.
|
|
748
|
+
|
|
749
|
+
`pick_model(clientX, clientY)` returns `{id, distance}` for the nearest visible
|
|
750
|
+
surface, or `null` for empty space. Coordinates use the same viewport coordinate
|
|
751
|
+
system as mouse events. It respects transforms, hidden models and section cuts.
|
|
752
|
+
The host chooses when to pick: for example, on double-click or at the start of a selection drag. The existing legacy click callbacks retain their behavior.
|
|
753
|
+
|
|
754
|
+
The preview provides Move, Rotate and Depth controls per model. Rotation uses
|
|
755
|
+
degrees in the UI and compensates for the original mesh center; the plugin's
|
|
756
|
+
`set_rotation` API continues to accept radians with its existing origin behavior.
|
|
757
|
+
Depth translates along the eye-to-model ray while holding the camera fixed.
|
|
758
|
+
|
|
759
|
+
## Saving ViewSTL scenes
|
|
760
|
+
|
|
761
|
+
Use `export_scene` for a Blob or `download_scene` for a browser download:
|
|
762
|
+
|
|
763
|
+
```js
|
|
764
|
+
const result = await viewer.export_scene({
|
|
765
|
+
format: 'vsb', // or 'vsj'
|
|
766
|
+
filename: 'my-scene',
|
|
767
|
+
signal: abortController.signal,
|
|
768
|
+
on_progress({ loaded, total, phase }) {
|
|
769
|
+
// Model counts; exporting, packaging (VSB only), complete.
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
await viewer.download_scene({ format: 'vsj', filename: 'scene', force_basename: true });
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
Both methods always capture the entire loaded scene. They return `{blob,
|
|
776
|
+
filename, format, model_ids, triangles, representation: 'scene'}`. Pending loads,
|
|
777
|
+
empty scenes, excess model/triangle counts, memory and output limits reject.
|
|
778
|
+
Errors reject the Promise instead of using the import-error callback. Cancellation
|
|
779
|
+
and throwing progress callbacks release the worker and memory reservation.
|
|
780
|
+
|
|
781
|
+
VSB uses the existing ZIP layout: `json_data.vsj` plus one colored binary STL per
|
|
782
|
+
model. Mesh encoding and compression run in a disposable worker using copied
|
|
783
|
+
snapshot buffers; renderer buffers remain intact. Camera, background, grid,
|
|
784
|
+
transforms, model colors/opacity, viewing modes and sections stay in the manifest.
|
|
785
|
+
Original model names are kept as `orig_filename`. As with the legacy VSB format,
|
|
786
|
+
STL geometry uses float32 positions and 5-bit per-face colors, taking the first
|
|
787
|
+
vertex color of each triangle. Texture/UV data and original CAD topology/history
|
|
788
|
+
are not reconstructed or included.
|
|
789
|
+
|
|
790
|
+
VSJ is JSON settings and model references, without embedded geometry. The
|
|
791
|
+
`force_basename` option defaults to false for API callers; the preview uses true
|
|
792
|
+
to write only filenames. Referenced files must be supplied separately. A local-file
|
|
793
|
+
reference alone does not grant browser access to a file on disk. VSB is the
|
|
794
|
+
self-contained choice. The preview opens both formats through the file picker,
|
|
795
|
+
drag and drop, and URL opening. Existing `get_vsj`, `get_vsb`, `get_vsb_blob` and their legacy
|
|
796
|
+
download methods retain their signatures and behavior.
|
|
797
|
+
|
|
798
|
+
In the app Save dialog, VSB/VSJ save the entire scene. STL/OBJ/3MF/STEP/IGES save
|
|
799
|
+
only the selected model. There is no user-selectable scope dropdown. The low-level
|
|
800
|
+
`export_models` API still permits explicit multiple-model selections for embedders.
|
|
801
|
+
|
|
802
|
+
## Opening ViewSTL scenes
|
|
803
|
+
|
|
804
|
+
Use the Promise-based `import_scene` API for local files or public URLs:
|
|
805
|
+
|
|
806
|
+
```js
|
|
807
|
+
const result = await viewer.import_scene(sceneFile, {
|
|
808
|
+
files: companionFiles, // File[]: model files referenced by a local VSJ.
|
|
809
|
+
signal: abortController.signal,
|
|
810
|
+
on_progress({ loaded, total, phase }) {
|
|
811
|
+
// reading-scene: reader progress; opening-model/complete: model counts.
|
|
812
|
+
},
|
|
813
|
+
});
|
|
814
|
+
console.log(result.model_ids, result.scene, result.used_files);
|
|
815
|
+
|
|
816
|
+
// Requires the viewer's same-origin url_import_endpoint configuration.
|
|
817
|
+
await viewer.import_scene('https://example.com/scene.vsb');
|
|
818
|
+
await viewer.import_scene('https://example.com/download?id=123', { format: 'vsj' });
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
VSB archives contain `json_data.vsj` and their model files. VSJ manifests reference
|
|
822
|
+
separate files: supplied local files match by exact relative path or unambiguous
|
|
823
|
+
basename. Missing or ambiguous files reject with an actionable error. Absolute
|
|
824
|
+
HTTP/HTTPS references use the configured URL relay. For a remote VSJ, relative
|
|
825
|
+
references resolve against its final directory after redirects, also through the
|
|
826
|
+
relay. Local files and manifests are not uploaded. The relay streams remote files
|
|
827
|
+
without saving them. It still applies its request, size and network limits, so a
|
|
828
|
+
scene with many remote references can hit the relay's rate limit.
|
|
829
|
+
|
|
830
|
+
Imports append models with new IDs and restore camera, background/grid, transforms,
|
|
831
|
+
colors/opacity, view modes, smoothing and sections. Saved centering is converted
|
|
832
|
+
to equivalent model positions without changing existing models' centering. Embed
|
|
833
|
+
canvas size and resize/mouse controls are not imported. The development animation
|
|
834
|
+
update also restores per-model animation and preserves its original pivot.
|
|
835
|
+
Scene reading and mesh parsing use workers and the existing memory/triangle/archive
|
|
836
|
+
limits. Models become visible together after all have loaded. Errors, cancellation,
|
|
837
|
+
or a throwing progress callback remove the incoming models and retain the existing
|
|
838
|
+
scene. `clean()` and `dispose()` also cancel pending scene imports. Existing
|
|
839
|
+
`load_vsj` and `load_vsb` retain their previous signatures and behavior.
|
|
840
|
+
|
|
841
|
+
In the preview, choose or drop a local VSJ together with its referenced model files;
|
|
842
|
+
companions used by the scene are not added twice. A VSB needs no separate files.
|
|
843
|
+
Choose VSB/VSJ explicitly in Open URL for a URL without a scene extension. Scene
|
|
844
|
+
opening, progress, cancellation and restored editing controls all live in the
|
|
845
|
+
plugin's app controller; the website initializer remains minimal.
|
|
846
|
+
|
|
847
|
+
## Checking meshes for 3D printing
|
|
848
|
+
|
|
849
|
+
The optional checks run locally on demand. They analyze the loaded triangle mesh
|
|
850
|
+
from any supported format, including models restored from VSB/VSJ. CAD accuracy
|
|
851
|
+
is limited by the imported tessellation. Analysis does not repair or modify models.
|
|
852
|
+
|
|
853
|
+
```js
|
|
854
|
+
const controller = new AbortController();
|
|
855
|
+
const report = await viewer.check_printability({
|
|
856
|
+
model_ids: [selectedId], // Omit for all loaded models; 'selected' uses the editing selection.
|
|
857
|
+
level: 'detailed', // 'basic' is the default.
|
|
858
|
+
apply_transforms: true, // Default: current placement/rotation/scale, like mesh export.
|
|
859
|
+
units_confirmed: true, // Confirm the current mm scale for unitless input.
|
|
860
|
+
profile: { // Optional; omit for geometry checks only.
|
|
861
|
+
technology: 'fdm',
|
|
862
|
+
build_volume_mm: [220, 220, 250],
|
|
863
|
+
min_wall_mm: 0.8, // User-chosen thresholds, not universal printing rules.
|
|
864
|
+
overhang_degrees: 45 // Measured from vertical; +Z is print up.
|
|
865
|
+
},
|
|
866
|
+
max_time_ms: 30000, // Per model, maximum 120000.
|
|
867
|
+
signal: controller.signal,
|
|
868
|
+
on_progress: ({ phase, model_index, total_models }) => console.log(phase)
|
|
869
|
+
});
|
|
870
|
+
if (viewer.is_printability_current(report)) {
|
|
871
|
+
viewer.show_printability(report, { model_id: selectedId, issue: 'boundary_edges' });
|
|
872
|
+
}
|
|
873
|
+
viewer.clear_printability();
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
Basic checks cover degenerate and nearly degenerate triangles, duplicates, open
|
|
877
|
+
boundaries, non-manifold edges and vertices, inconsistent winding, connected
|
|
878
|
+
components and physical-unit provenance. Detailed checks add a native-JavaScript
|
|
879
|
+
BVH for self-intersections (including coplanar overlaps), shell containment,
|
|
880
|
+
orientation and enclosed volume. Separate components can be intentional. Hollow
|
|
881
|
+
models require opposite cavity-shell orientation. Vertex connectivity uses exact
|
|
882
|
+
source coordinates; small gaps are not silently welded closed. Intersection
|
|
883
|
+
checks use the reported scale-dependent numerical tolerance; near-contact cases
|
|
884
|
+
can be inconclusive. The existing 3MF export guards remain independent and unchanged.
|
|
885
|
+
|
|
886
|
+
Optional FDM checks compare dimensions with a build-volume box, identify potential
|
|
887
|
+
overhang regions, estimate bed contact and sample wall thickness. `line_width_mm`
|
|
888
|
+
can supply the wall threshold when `min_wall_mm` is omitted. No printer defaults
|
|
889
|
+
are assumed. Build fit tests current orientation dimensions, not actual placement,
|
|
890
|
+
exclusion zones or alternative rotations. Bed contact virtually lowers each model
|
|
891
|
+
to its minimum Z; the visible grid and camera do not define the print bed.
|
|
892
|
+
Thickness tests at most 2,048 face centres using inward normal rays and can miss
|
|
893
|
+
small features or oblique thin regions. No finding from those samples is explicitly
|
|
894
|
+
inconclusive, never proof of a minimum thickness. Overhangs do not simulate
|
|
895
|
+
bridging/supports; contact area does not predict adhesion/stability. Resin, toolpaths,
|
|
896
|
+
cross-model collisions and Boolean unions are outside these checks.
|
|
897
|
+
|
|
898
|
+
Reports include each check's `pass`, `issue`, `inconclusive` or `not_run` status,
|
|
899
|
+
severity, counts, explanations, bounded highlight samples, dimensions, tolerance,
|
|
900
|
+
timing and optional estimates. `volume_valid` means a geometrically validated
|
|
901
|
+
enclosed volume; its physical size still depends on the `units` check. Overall
|
|
902
|
+
status is `issues_found`, `incomplete` or `no_issues_found`. A basic report remains
|
|
903
|
+
incomplete because detailed checks were not run. Errors and time/memory limits
|
|
904
|
+
never imply a pass. This API does not certify a successful print.
|
|
905
|
+
|
|
906
|
+
Workers and analysis modules load only when requested. Models are checked in
|
|
907
|
+
sequence, with cooperative snapshot copying and aggregate memory admission.
|
|
908
|
+
The working estimate is 64 MiB plus 2,400 bytes per triangle, with bounded report
|
|
909
|
+
allowances; a model that can be viewed may be too large to analyze on the same
|
|
910
|
+
device. One bounded report cache is retained, not the topology/BVH. Geometry,
|
|
911
|
+
transforms, scale and unit changes invalidate reports; colors and camera changes
|
|
912
|
+
do not. Removing a checked model, clean/dispose, AbortSignal cancellation or a
|
|
913
|
+
throwing progress callback releases the active worker and reservations. Report
|
|
914
|
+
metadata uses weak references so retaining a report cannot retain removed meshes.
|
|
915
|
+
|
|
916
|
+
Highlights use temporary native WebGL line buffers and are excluded from PNG,
|
|
917
|
+
mesh and scene exports. They show up to 128 faces or 256 edges per check, through
|
|
918
|
+
occluding surfaces. The host controls results presentation and calls
|
|
919
|
+
`clear_printability()` when appropriate. It decides when its panel closes, how
|
|
920
|
+
stale results are shown, and whether to pause/resume animation. The plugin exposes
|
|
921
|
+
analysis, cancellation, validity checks and geometry highlighting without a UI.
|
|
922
|
+
|
|
923
|
+
## Model size, units, duplicates, cameras and measurements (rc.6)
|
|
924
|
+
|
|
925
|
+
All operations below are implemented inside the plugin. Their interface controls and presentation belong to the host.
|
|
926
|
+
|
|
927
|
+
`viewer.resize_model(id, [2, 2, 2])` doubles the model about its world-space bounds center, preserving rotation and placement. Positive finite XYZ factors are required; independent factors allow nonuniform scaling. It stops only that model's animation. `get_model_size(id)` returns dimensions and the reset baseline in millimetres. `reset_model_size(id)` restores that baseline while retaining file-unit corrections.
|
|
928
|
+
|
|
929
|
+
`viewer.set_file_units(id, 'inch')` reinterprets a unitless STL/OBJ mesh (default mm) without moving its center. Switching back reverses the conversion. Explicit 3MF/STEP/IGES units are normalized to mm during parsing and cannot be reinterpreted. `set_display_units('inch')` changes the optional app's size/Info readouts only; scene coordinates, position controls and the grid remain in mm. Existing `set_model_units` and `set_scale` retain their legacy semantics.
|
|
930
|
+
|
|
931
|
+
`const copy = viewer.duplicate_model(id)` returns a new ID at the same pose, with independent appearance, transforms and metadata. Immutable geometry and matching GPU buffers are shared; memory admission remains conservative. The source reference remains unchanged for VSJ, while `display_name` identifies the copy. Copied animation definitions and phase are retained but paused; the original keeps playing.
|
|
932
|
+
|
|
933
|
+
`viewer.set_camera_view('top')` fits the scene from front/back/left/right/top/bottom/isometric using Z up and the existing perspective projection. It never rotates the models. Camera bookmarks are explicit data:
|
|
934
|
+
|
|
935
|
+
`viewer.set_camera_bookmarks([{name: 'Detail', camera: viewer.get_camera_state()}])`
|
|
936
|
+
|
|
937
|
+
Use `get_camera_bookmarks()` to obtain a defensive copy and `set_camera_state(bookmark.camera)` to restore one. Up to 32 uniquely named views (80 characters per name) are supported. They are kept in memory and in scene exports, not localStorage.
|
|
938
|
+
|
|
939
|
+
`const {area_mm2, volume_mm3, volume_valid, volume_reason} = await viewer.measure_model(id)`
|
|
940
|
+
|
|
941
|
+
Measurements use workers and support `signal`, `max_time_ms` (default 30 seconds) and `on_progress`. Area includes all triangles at current scale. Volume is nullable: detailed topology, intersections and shell-orientation checks must establish a closed volume. An open/invalid mesh or an inconclusive/time-limited check never becomes a plausible-looking volume. Results are cached by mesh and scale; position/rotation do not invalidate them and uniform resizing updates cached measurements algebraically. Nonuniform area is recalculated in the worker. Memory limits can reject the calculation before buffers are copied. This is a bounded mesh analysis, not a guarantee of printability.
|
|
942
|
+
|
|
943
|
+
VSB/VSJ retain size baselines, file-unit interpretation, display units, duplicate labels and bookmarks. VSB still embeds geometry; VSJ still needs the referenced source files. No second unit conversion occurs during import. Appending scenes merges bookmark names with numbered suffixes; a combined total above 32 rejects the incoming scene. Old files without the new optional fields continue to load. The `size_state.unit_factor` serialization field preserves source-unit correction separately from resize factors for exports with `apply_transforms:false`.
|
|
944
|
+
|
|
945
|
+
Local measurements and model operations need no server. Use the public APIs with the interface of your choice.
|