@omrips/viewstl 2.0.0-rc.4 → 2.0.0-rc.6

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 (50) hide show
  1. package/README.md +19 -173
  2. package/build/chunks/archive-QK7XZSVT.js +1 -0
  3. package/build/{v2/chunks/cad-VY42CMP4.js → chunks/cad-QGVO6UGR.js} +1 -1
  4. package/build/chunks/chunk-6AW5U52H.js +1 -0
  5. package/build/{v2/chunks/chunk-R3HNTB5A.js → chunks/chunk-CVGQTUJ5.js} +2 -2
  6. package/build/chunks/chunk-FUXP2W57.js +1 -0
  7. package/build/chunks/chunk-LFTKNL2V.js +1 -0
  8. package/build/{v2/chunks/chunk-ZMK3UZZE.js → chunks/chunk-T6PPRDOE.js} +1 -1
  9. package/build/chunks/chunk-V6KYGP62.js +1 -0
  10. package/build/{v2/chunks/export-7OP4ZGGJ.js → chunks/export-2CFMTPJ3.js} +3 -3
  11. package/build/{v2/chunks/export-cad-QPBT756V.js → chunks/export-cad-XOXIE726.js} +1 -1
  12. package/build/{v2/chunks/export-zip-EDCWH5PX.js → chunks/export-zip-I2ADLFJI.js} +1 -1
  13. package/build/chunks/measure-3LDALPCN.js +1 -0
  14. package/build/chunks/print-check-WWHTT5PW.js +1 -0
  15. package/build/{v2/chunks/scene-export-KQBS7VRJ.js → chunks/scene-export-FPPL466I.js} +1 -1
  16. package/build/chunks/scene-reader-WXTRELY3.js +1 -0
  17. package/build/{v2/node → node}/analysis-worker.js +13 -12
  18. package/build/{v2/node → node}/index.cjs +7 -7
  19. package/build/{v2/node → node}/index.js +6 -6
  20. package/build/stl_viewer.min.js +51 -0
  21. package/build/{v2/viewstl.cjs → viewstl.cjs} +1641 -272
  22. package/build/{v2/viewstl.d.cts → viewstl.d.cts} +98 -0
  23. package/build/{v2/viewstl.d.ts → viewstl.d.ts} +98 -0
  24. package/build/{v2/viewstl.js → viewstl.js} +1641 -272
  25. package/build/viewstl.min.js +51 -0
  26. package/build/viewstl.worker.js +1 -0
  27. package/docs/releases/2.0.0-rc.6.md +7 -0
  28. package/docs/v2-compatibility.md +5 -5
  29. package/docs/v2-usage.md +152 -21
  30. package/docs/v2-verification.md +169 -0
  31. package/legacy/README.md +14 -8
  32. package/package.json +18 -35
  33. package/build/v2/README.md +0 -15
  34. package/build/v2/chunks/archive-VGS4J4Z7.js +0 -1
  35. package/build/v2/chunks/chunk-JH3H3JWA.js +0 -1
  36. package/build/v2/chunks/chunk-U33KANV7.js +0 -1
  37. package/build/v2/chunks/print-check-2PU7CSL3.js +0 -1
  38. package/build/v2/chunks/scene-reader-APUJOPUR.js +0 -1
  39. package/build/v2/stl_viewer.min.js +0 -51
  40. package/build/v2/viewstl.min.js +0 -51
  41. package/build/v2/viewstl.worker.js +0 -1
  42. /package/build/{v2/THIRD-PARTY-NOTICES.txt → THIRD-PARTY-NOTICES.txt} +0 -0
  43. /package/build/{v2/cad → cad}/occt-import-js.js +0 -0
  44. /package/build/{v2/cad → cad}/occt-import-js.source.txt +0 -0
  45. /package/build/{v2/cad → cad}/occt-import-js.wasm +0 -0
  46. /package/build/{v2/cad → cad}/prepare-cad.source.txt +0 -0
  47. /package/build/{v2/chunks/chunk-JS56KC3I.js → chunks/chunk-W5KVLGVR.js} +0 -0
  48. /package/build/{v2/chunks → chunks}/chunk-ZSV4PTIA.js +0 -0
  49. /package/build/{v2/node → node}/index.d.cts +0 -0
  50. /package/build/{v2/node → node}/index.d.ts +0 -0
package/docs/v2-usage.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # ViewSTL 2 — native WebGL2 plugin
2
2
 
3
- Version: `2.0.0-rc.4` (ViewSTL v2.00 release candidate). The npm package is
4
- `@omrips/viewstl`; install the candidate with `npm install @omrips/viewstl@2.0.0-rc.4`.
5
- The legacy plugin remains at its original repository paths and on the production
6
- website. The v2 website is available at https://preview.viewstl.com/.
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
7
 
8
8
  ## Quick start
9
9
 
10
- Install with `npm install @omrips/viewstl@2.0.0-rc.4`, then copy the complete
11
- `node_modules/@omrips/viewstl/build/v2/` folder to `/viewstl-v2/` on your website.
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
12
  Paste these three lines into your page's `<body>`:
13
13
 
14
14
  ```html
@@ -23,7 +23,7 @@ required so the viewer can find its worker and optional format assets.
23
23
 
24
24
  ## Browser script
25
25
 
26
- Copy the complete `build/v2/` directory to your server, including the
26
+ Copy the complete `build/` directory to your server, including the
27
27
  worker, its `chunks/` and `cad/` directories and third-party notices. Then:
28
28
 
29
29
  ```html
@@ -58,11 +58,13 @@ The package contains ESM, CommonJS and TypeScript entry points. Importing
58
58
  the package is safe during server-side rendering; constructing a viewer
59
59
  requires a browser DOM and WebGL2.
60
60
 
61
- The package name is `@omrips/viewstl`, under the verified `omrips` npm account.
62
- Release candidates use the `next` dist-tag. Pin the version above for a
63
- repeatable installation; installing a local release tarball is also supported.
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.
64
66
 
65
- With a bundler, copy the complete `node_modules/@omrips/viewstl/build/v2/`
67
+ With a bundler, copy the complete `node_modules/viewstl/build/`
66
68
  directory to your app's public assets (for example `public/viewstl-v2/`), keeping
67
69
  the worker, `chunks/`, `cad/` and notices together. Set `worker_url` to its served
68
70
  URL, or provide your bundler's worker factory.
@@ -70,7 +72,7 @@ Do not assume a bundler copies a module worker's dynamic chunks merely
70
72
  because it sees a URL string.
71
73
 
72
74
  ```js
73
- import StlViewer from '@omrips/viewstl';
75
+ import StlViewer from 'viewstl';
74
76
 
75
77
  const viewer = new StlViewer(container, {
76
78
  worker_url: '/viewstl-v2/viewstl.worker.js'
@@ -80,7 +82,7 @@ const viewer = new StlViewer(container, {
80
82
  For Vite, the optional factory form is:
81
83
 
82
84
  ```js
83
- import ViewerWorker from '@omrips/viewstl/worker?worker';
85
+ import ViewerWorker from 'viewstl/worker?worker';
84
86
  const viewer = new StlViewer(container, {
85
87
  worker_factory: () => new ViewerWorker()
86
88
  });
@@ -96,17 +98,17 @@ The CommonJS entry point also requires
96
98
 
97
99
  ## Node.js model analysis
98
100
 
99
- Install `npm install @omrips/viewstl@2.0.0-rc.4`. Node.js 20+ resolves the analysis
101
+ Run `npm install viewstl`. Node.js 20+ resolves the analysis
100
102
  worker and optional CAD WASM directly inside the package; there is no asset-copy
101
103
  step for server use.
102
104
 
103
105
  ```js
104
- import { analyzeFile } from '@omrips/viewstl/node';
106
+ import { analyzeFile } from 'viewstl/node';
105
107
  const report = await analyzeFile('part.stl', { units: 'mm' });
106
108
  console.log(report.dimensions_mm, report.surface_area_mm2, report.volume_mm3);
107
109
  ```
108
110
 
109
- CommonJS is supported with `const { analyzeFile } = require('@omrips/viewstl/node')`.
111
+ CommonJS is supported with `const { analyzeFile } = require('viewstl/node')`.
110
112
  The `/node` entry point is separate from the browser viewer and has no DOM,
111
113
  WebGL or Three.js dependency. It supports STL, OBJ, 3MF, STEP/STP and IGES/IGS.
112
114
  STEP/IGES measurements describe the tessellated mesh, not exact CAD surfaces.
@@ -119,7 +121,7 @@ object. `analyzeBuffer(data, options)` accepts a Node Buffer, Uint8Array or
119
121
  ArrayBuffer, and requires `filename` or `format` to identify the format:
120
122
 
121
123
  ```js
122
- import { analyzeBuffer } from '@omrips/viewstl/node';
124
+ import { analyzeBuffer } from 'viewstl/node';
123
125
  const report = await analyzeBuffer(uploadBuffer, { filename: 'part.stl', units: 'mm' });
124
126
  ```
125
127
 
@@ -151,7 +153,7 @@ The convenience functions share an analyzer with two active workers, up to
151
153
  Create an independent analyzer when the host needs different concurrency:
152
154
 
153
155
  ```js
154
- import { createAnalyzer } from '@omrips/viewstl/node';
156
+ import { createAnalyzer } from 'viewstl/node';
155
157
  const analyzer = createAnalyzer({ concurrency: 1, max_queue: 4 });
156
158
  try {
157
159
  const reports = await Promise.all(paths.map(path => analyzer.analyzeFile(path, {
@@ -220,7 +222,7 @@ app.dispose(); // cancels work and releases listeners, workers and WebGL
220
222
 
221
223
  This controller binds the preview's HTML control layout; the host supplies
222
224
  markup and CSS. The reference layout is `Viewstl/site/index.html` in the sibling
223
- website workspace. `requiredControls` in `src/v2/app.js` validates the control
225
+ website workspace. `requiredControls` in `src/app.js` validates the control
224
226
  IDs before registering handlers. Missing controls and duplicate mounts produce
225
227
  a descriptive error. Custom integrations without that layout use `StlViewer`.
226
228
  Branding, page metadata, advertisements and static help/privacy text belong
@@ -269,7 +271,7 @@ internal geometry or retain its buffers.
269
271
  Use self-contained exports: external part files are not fetched, and native
270
272
  SolidWorks/Solid Edge formats, feature history, PMI and curve-only files are
271
273
  unsupported. Incomplete external-reference assemblies may lack parts.
272
- - Deploy the entire `build/v2/cad/` directory alongside `chunks/` and the worker.
274
+ - Deploy the entire `build/cad/` directory alongside `chunks/` and the worker.
273
275
  CAD code/WASM is loaded only for CAD files; local input bytes are never uploaded.
274
276
  Each job's worker is terminated on completion, cancellation, error or timeout.
275
277
  CAD admission estimates 128 MiB plus 40 times the input size. The WASM heap
@@ -304,6 +306,109 @@ Transparency uses model-level back-to-front sorting; intersecting transparent
304
306
  surfaces can still have ordering artifacts. STL export stores face color,
305
307
  so a 3MF triangle's per-vertex gradient cannot survive STL/VSB export exactly.
306
308
 
309
+ ## Model animation
310
+
311
+ Animation is included in `2.0.0-rc.5` on GitHub and in both npm packages.
312
+ Playback remains inside the native WebGL2 plugin.
313
+
314
+ After a model has loaded, one call starts continuous rotation:
315
+
316
+ ```js
317
+ await viewer.when_idle();
318
+ viewer.animate_model(0, {
319
+ delta: { rotationz: 2 * Math.PI, msec: 5000, loop: true },
320
+ });
321
+ ```
322
+
323
+ The legacy `animation` model option also works. `delta` adds a change to the
324
+ starting pose; `exact` interpolates toward an absolute target. They can operate
325
+ together on different properties and finish independently:
326
+
327
+ ```js
328
+ viewer.animate_model(0, {
329
+ delta: { rotationx: 1, rotationy: 1.1, rotationz: 1.2, msec: 1000, loop: true },
330
+ exact: { x: 1000, y: 150, z: 200, scale: 5, msec: 3000 },
331
+ });
332
+
333
+ viewer.pause_model_animation(0); // Keep the pose and elapsed time.
334
+ viewer.resume_model_animation(0);
335
+ console.log(viewer.get_model_animation_state(0));
336
+ viewer.animate_model(0, null); // Stop at the current pose; discard playback.
337
+ ```
338
+
339
+ Supported properties are `x/y/z`, `rotationx/y/z` in radians and
340
+ `scale` or `scalex/scaley/scalez`. Durations are positive milliseconds, defaulting
341
+ to 300. Scale deltas are additive, as in the legacy API; all resulting scales
342
+ must stay positive. Only delta tracks repeat, accumulating their change each
343
+ cycle. A negative looping scale delta is rejected. Delta and exact may not claim
344
+ the same property. Inputs must be finite and within JavaScript's safe numeric
345
+ range. Invalid replacements leave the current animation intact.
346
+
347
+ Starting a new animation replaces the old one from the current pose. Manual
348
+ position, rotation, scale and unit setters stop that model's playback first.
349
+ Camera interaction, color and viewing-mode changes remain usable. Completed
350
+ tracks no longer write their properties. Removal, `clean()` and `dispose()`
351
+ release playback; paused/completed models do not keep the render loop running.
352
+
353
+ Timing follows elapsed frame timestamps, not frame count. Hidden documents and
354
+ lost WebGL contexts suspend playback without including that gap on resumption.
355
+ User pause is independent of this automatic suspension. State queries return
356
+ `status`, `elapsed_ms`, `suspended` and active `tracks`; unknown IDs return null.
357
+
358
+ Set callbacks in the viewer options:
359
+
360
+ ```js
361
+ const viewer = new StlViewer(container, {
362
+ animation_state_callback(id, state, reason) {
363
+ console.log(id, state.status, reason);
364
+ },
365
+ animation_complete_callback(id, state) {
366
+ console.log('Finished', id, state.elapsed_ms);
367
+ },
368
+ });
369
+ ```
370
+
371
+ Callbacks describe transitions, not every frame, and are deferred until the
372
+ current operation finishes. Completion fires once when every track finishes;
373
+ an animation with a looping track does not complete. Call playback controls on
374
+ loaded models, for example after `when_idle()`.
375
+
376
+ VSB/VSJ preserve the animation definition, original pose, elapsed phase, rotation
377
+ pivot and pause flag. Both scene-loading APIs start incoming animations together
378
+ after all incoming models load. VSJ still needs its referenced model files.
379
+ Animated VSB entries retain unscaled mesh data and explicit scale metadata;
380
+ static entries retain the previous scale-baked convention. This prevents scale
381
+ being applied twice. Older viewers may read the pose but cannot restore the new
382
+ playback-state extension exactly.
383
+
384
+ PNG and modern `export_models()` capture the current pose without stopping
385
+ playback. Legacy `get_stl_bin()` keeps its documented scale/centering-only export
386
+ convention. Regular model files do not store animation. Print checks should run
387
+ while the affected models are paused; subsequent motion makes the report stale.
388
+
389
+ A self-contained example is in `examples/v2/animation.html` in the source
390
+ checkout (serve with `pnpm start`). It includes Play, Pause, Resume, Stop and Reset.
391
+
392
+ The app controller also supports an optional `animation-*` group (see the new
393
+ site's HTML template): `animation-details`, `animation-fields`, `animation-axis`,
394
+ `animation-seconds`, `animation-direction`, `animation-play`, `animation-pause`,
395
+ `animation-stop`, `animation-status` and `animation-error`. Supply the whole group
396
+ or omit it. It exposes selected-model spin around the mesh center, duration
397
+ (0.1–3,600 seconds per turn), direction and playback buttons. Other models keep
398
+ playing when selection changes. Manual tools capture the current animated pose.
399
+ Readouts update at most four times per second; geometry and GPU buffers are reused.
400
+
401
+ `mountViewstlApp` composes `animation_state_callback` and
402
+ `animation_complete_callback` options with its own UI behavior. `app.getState()`
403
+ includes each model's current transform and `animation` state. Ordinary files
404
+ never auto-spin. Imported running animations are paused when the browser reports
405
+ `prefers-reduced-motion: reduce`; explicit user playback remains available.
406
+
407
+ App print checks pause running target models. Optional `print-resume` and
408
+ `print-animation-note` elements expose resumption; closing the panel does not
409
+ silently restart motion. Resuming clears stale results and keeps the panel open.
410
+ Scene saving includes playback; PNG and regular model saving capture a pose.
411
+
307
412
  ## Existing API and intentional differences
308
413
 
309
414
  The existing constructor, model options and documented method names are
@@ -609,6 +714,7 @@ pnpm install --frozen-lockfile
609
714
  pnpm build
610
715
  pnpm test
611
716
  pnpm check:types
717
+ pnpm check:node-types
612
718
  pnpm check:legacy
613
719
  pnpm start
614
720
  pnpm pack
@@ -745,7 +851,8 @@ scene with many remote references can hit the relay's rate limit.
745
851
  Imports append models with new IDs and restore camera, background/grid, transforms,
746
852
  colors/opacity, view modes, smoothing and sections. Saved centering is converted
747
853
  to equivalent model positions without changing existing models' centering. Embed
748
- canvas size, resize/mouse controls and per-model animation are not imported.
854
+ canvas size and resize/mouse controls are not imported. The development animation
855
+ update also restores per-model animation and preserves its original pivot.
749
856
  Scene reading and mesh parsing use workers and the existing memory/triangle/archive
750
857
  limits. Models become visible together after all have loaded. Errors, cancellation,
751
858
  or a throwing progress callback remove the incoming models and retain the existing
@@ -842,3 +949,27 @@ Escape with focus inside the panel dismisses it. Reopening it from Info reveals
842
949
  and focuses the existing panel. Loading or changing the checked selection cancels
843
950
  an active check; stale results are cleared. Loading and empty scenes disable Run
844
951
  and show the current availability without closing the panel.
952
+
953
+ ## Model size, units, duplicates, cameras and measurements (rc.6)
954
+
955
+ All operations below are implemented inside the plugin. The optional site controls remain a thin HTML/CSS host.
956
+
957
+ `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.
958
+
959
+ `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.
960
+
961
+ `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.
962
+
963
+ `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:
964
+
965
+ `viewer.set_camera_bookmarks([{name: 'Detail', camera: viewer.get_camera_state()}])`
966
+
967
+ 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.
968
+
969
+ `const {area_mm2, volume_mm3, volume_valid, volume_reason} = await viewer.measure_model(id)`
970
+
971
+ 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.
972
+
973
+ 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`.
974
+
975
+ 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.
@@ -3,6 +3,167 @@
3
3
  This file records dated checks. Older sections describe the alpha at the time
4
4
  of each check; release-candidate results are recorded separately below.
5
5
 
6
+ ## npm names and installation — 2026-11-07
7
+
8
+ Published `viewstl@2.0.0-rc.5` and `@omrips/viewstl@2.0.0-rc.5` with `latest`
9
+ pointing to rc.5. The existing scoped `next` tag was also advanced to rc.5.
10
+ Both READMEs document `npm install viewstl` and retain the scoped alternative.
11
+ All existing scoped versions remain available.
12
+
13
+ All 136 unit tests, strict browser/Node type checks and 37 legacy integrity
14
+ checks pass again. Both packed distributions pass Node ESM/CommonJS type checks
15
+ and runtime checks for browser exports, worker/CAD assets and STL/OBJ/3MF/STEP/IGES
16
+ analysis. Fresh public-registry installs without a version suffix pass those
17
+ runtime checks for both package names. Public tarball bytes and SHA-512 integrity
18
+ match the tested local archives; each package contains 40 files.
19
+
20
+ All 31 files under `build/` match the previously tested GitHub rc.5 distribution
21
+ byte for byte. No renderer changes were made and browser/device tests were not
22
+ rerun for this packaging update. The GitHub browser evidence below still applies
23
+ to those exact runtime bytes. Evidence: local `test-results/npm-stage.json` and
24
+ its referenced staging directory. See [maintainer workflow](npm-releases.md).
25
+
26
+ ## GitHub root-layout release 2.0.0-rc.5 — 2026-11-07
27
+
28
+ The release worktree places the v2 sources/distribution in `src/` and `build/`.
29
+ The complete pre-v2 GitHub tree is archived under `legacy/v1/`; its 23 original
30
+ Git blobs and the 14-file local v1.14 snapshot pass SHA-256/size verification.
31
+ Historical sections below retain the paths and versions used at the time.
32
+
33
+ All **136 unit tests** pass, along with strict browser and Node TypeScript
34
+ consumers. Actual desktop Chrome 156, without device emulation, passes **46
35
+ browser checks**: 15 compatibility, 11 animation, 11 scene and 9 export checks.
36
+ These exercise the relocated classic/ESM entries, module workers, legacy mesh
37
+ bridge, STL/OBJ/3MF/STEP/IGES export/reimport, scene playback, cancellation,
38
+ context restoration and a responsive million-triangle STL export.
39
+
40
+ The packed `@omrips/viewstl@2.0.0-rc.5` tarball installs into a clean consumer
41
+ project. Browser ESM/CommonJS exports, version reporting, worker/CAD asset paths,
42
+ and server analysis of STL, OBJ, 3MF, STEP and IGES pass. Node analysis also
43
+ passes through the CommonJS entry. Archive contents exclude development fixtures,
44
+ legacy runtime copies and site/server files; license notices and lazy assets are included.
45
+
46
+ The layout update also corrects old harness references to the legacy parser and
47
+ worker directory. The compatibility animation assertion now waits for the public
48
+ completion callback and checks the public completed state, rather than assuming
49
+ the old private animation representation or a fixed wall-clock delay.
50
+
51
+ Evidence is retained locally in the original plugin checkout under
52
+ `test-results/github-v2-20261107/`. Physical mobile, Safari and Firefox validation
53
+ remain pending. This release does not update the preview website or npm registry.
54
+
55
+ ## Live animation save/open verification — 2026-11-02
56
+
57
+ Verified the existing `20261102-f04f34e66b7c` release through the actual Save and
58
+ Open controls on `https://preview.viewstl.com/`, in desktop in-app Chromium.
59
+ No runtime changes or redeployment were needed. Two binary STL fixtures have
60
+ offset geometry centers: one runs reverse X rotation at 3 seconds per turn and
61
+ is paused; the other runs forward Z rotation at 7 seconds per turn.
62
+
63
+ Both VSB and VSJ were downloaded, the page was freshly reloaded, and the downloaded
64
+ files were opened and saved again. **40 checks pass** against the resulting files
65
+ and visible controls: two models, camera, position/scale/appearance, animation
66
+ definition, pivot/origin, pause state, exact paused time and pose, advancing running
67
+ time and a pose consistent with that time. VSB mesh coordinates match both their
68
+ original files and the re-saved archive. VSJ file references remain intact. The
69
+ paused model's elapsed time stayed exactly `7400.434000000001` ms in both formats;
70
+ the running model continued from each saved time instead of restarting.
71
+
72
+ A second pair of files was saved with both models paused. PNGs downloaded before
73
+ saving and after independently reopening each format are **byte-for-byte equal**.
74
+ VSB opens alone; VSJ was opened together with its two referenced STL files. Paused
75
+ models expose Resume with the restored axis, duration and direction. There were
76
+ no console errors on the final restored page. This is desktop-browser evidence,
77
+ not physical mobile or other-browser coverage.
78
+
79
+ The website workspace retains actual downloads, UI-state records, PNGs and
80
+ `verification.json` in `results/animation-roundtrip-20261102/`. Recheck those
81
+ artifacts with `node site/qa/verify-animation-downloads.mjs --images`; regenerate
82
+ the offset STL fixtures with `node site/qa/make-animation-fixtures.mjs`.
83
+
84
+ ## Preview-site animation integration — 2026-11-02
85
+
86
+ Deployed to preview as `20261102-f04f34e66b7c`. The optional animation app binding
87
+ is bundled inside viewstl.js; the site's initializer is unchanged. The site adds
88
+ static controls under Models → Animation: axis, seconds per turn, direction and
89
+ Play/Pause/Resume/Stop. Off-center models spin about their center without moving
90
+ their visible pose. Editing uses current transforms, and adding/selecting files
91
+ preserves other models' animation. Print checks pause their targets and offer
92
+ explicit Resume while keeping the results panel open. Reduced-motion preference
93
+ pauses restored playback. Stopped animations also retain an equivalent static
94
+ VSJ pose with the original referenced mesh.
95
+
96
+ All **136 unit tests**, both strict type checks and the legacy guard pass.
97
+ Actual desktop Chromium 156 passes **25 new integration checks** and **96 existing
98
+ site checks**. New coverage includes invalid speed, concurrent models, live-pose
99
+ manual editing, drag entry, print pause/resume, VSB phase/pause restoration,
100
+ stopped off-center VSJ reopening with an identical PNG, callback composition,
101
+ clear/dispose/remount and simulated reduced motion. Keyboard Enter playback was
102
+ also exercised. 320px and 390px viewport checks show no horizontal overflow and
103
+ 44px-high playback buttons; these are emulated widths, not physical touch tests.
104
+
105
+ All 29 public HTTPS files match the local SHA-256 manifest. Live browser smoke
106
+ checks cover loading, keyboard playback and print-check interaction. Nginx, relay
107
+ and Certbot remain active; configuration/service/binary hashes match the previous
108
+ release and SELinux remains enforcing. Initial gzip total is 57,897 bytes; the
109
+ viewer entry is 42,150 bytes. No new million-triangle or total-memory benchmark
110
+ was performed for the app controls; the earlier plugin measurement remains below.
111
+ Physical mobile, Safari/Firefox and actual hidden-tab behavior remain unverified.
112
+
113
+ Evidence: the site workspace's `results/animation-integration/` and
114
+ `results/deploy-20261102-f04f34e66b7c/`. Reproduce the browser checks through the
115
+ local-only `site/qa/animation.html` and `site/qa/index.html` pages. The QA harness
116
+ is excluded from deployment. npm is unchanged (`2.0.0-rc.4`); no GitHub push.
117
+
118
+ ## Model animation — unreleased development update, 2026-11-02
119
+
120
+ The local plugin now has validated legacy delta/exact playback, per-model
121
+ pause/resume/state queries and transition/completion callbacks. Animation uses
122
+ elapsed frame time, retires finished tracks independently, stops on manual
123
+ transform edits and suspends on document hiding or context loss. VSJ and both
124
+ VSB APIs preserve animation phase, pause state, units and rotation pivot; animated
125
+ VSB meshes retain scale in metadata to avoid applying it twice on reopening.
126
+ The native WebGL2 renderer and existing worker architecture remain in use.
127
+
128
+ - All **132 Node tests pass** on Node.js 24.19.0, Windows. Nine new tests cover
129
+ mixed durations, frame cadence, pause/resume/suspension, manual changes,
130
+ replacement and copied state, scale/statistics invalidation, invalid numeric
131
+ inputs and overflow, scene validation, VSJ/VSB restoration and export snapshots.
132
+ - Browser and server-only strict TypeScript checks pass. The legacy guard passes
133
+ for tracked legacy files and all 14 archived files.
134
+ - **11 animation browser checks pass** in actual desktop Chromium 156 on Windows,
135
+ using workers and WebGL2. These cover constructor playback, concurrent models,
136
+ idle behavior, pause/resume/stop, modern and legacy scene restoration, group
137
+ starts, bad-scene rollback, print-check staleness, mesh/PNG snapshots, forced
138
+ context loss/restoration and resource cleanup. Context loss is deliberately
139
+ triggered with `WEBGL_lose_context`, not a hardware failure.
140
+ - Existing browser compatibility (**15**) and scene/rendering (**11**) checks
141
+ also pass: **37 passing browser checks** in total.
142
+ - A generated **1,000,000-triangle** model animated over 120 measured frame
143
+ intervals: median **16.7 ms**, p95 **16.8 ms**, maximum **16.9 ms**. Geometry
144
+ objects and GPU resources retained identity, with two GPU buffers and
145
+ 72,375,000 bytes of resident geometry typed arrays. These are frame intervals
146
+ on one desktop, not GPU timings, allocation profiling or total process memory.
147
+ - The interactive example at `examples/v2/animation.html` runs locally and exposes
148
+ Play, Pause, Resume, Stop and Reset. No models or external assets are fetched.
149
+
150
+ Actual hidden-tab behavior remains unverified: automation tab changes in the
151
+ in-app browser and Chrome did not produce a document `visibilitychange` event.
152
+ The suspension/resumption code is covered by unit tests and the real WebGL
153
+ context-loss check. Firefox, Safari, physical phones/tablets, touch, reduced-motion
154
+ site integration and a total-memory profile have not been tested in this update.
155
+
156
+ Reproduce with `pnpm build`, `pnpm test`, `pnpm check:types`,
157
+ `pnpm check:node-types`, `pnpm check:legacy`, then `pnpm start`. Open
158
+ `tests/v2/animation-browser.html`, `tests/v2/browser.html` and
159
+ `tests/v2/scene-browser.html` and run their checks. The million-triangle and
160
+ background-tab checks have separate buttons on the animation page.
161
+
162
+ Evidence is retained locally in ignored `test-results/animation-*` files. The
163
+ package version is still `2.0.0-rc.4`; this development update has **not** been
164
+ published to npm or deployed. The proposed site work is described in
165
+ [the preview integration plan](v2-animation-site-plan.md).
166
+
6
167
  ## Runnable server example 2.0.0-rc.4 — 2026-10-20
7
168
 
8
169
  The npm README now contains a clearly labeled server-only example, including
@@ -486,3 +647,11 @@ explicit close from an empty scene. The panel remains open throughout unrelated
486
647
  activity while stale reports and active jobs are handled safely. All 112 Node
487
648
  tests, strict types and 14 legacy hashes pass again. Evidence: sibling website
488
649
  `results/print-persistent/` and `results/deploy-20261017-5b21747c4c8f/`.
650
+
651
+ ## 2026-11-07 — rc.6 model tools
652
+
653
+ 146 unit tests pass, as do strict browser/Node TypeScript checks and the 37-file legacy hash guard. Actual Windows Chrome 156 runs against the site built from the candidate npm tarball: 25 new model-tools integration checks, 25 animation regressions and 96 existing site regressions pass. New coverage includes locked/nonuniform resize, invalid input recovery, mm/inch separation, copy animation isolation, source references, shared geometry/GPU cleanup, camera presets, bookmark validation/merge, static and animated VSB/VSJ round trips, reset baselines, explicit 3MF/CAD units, and malformed-scene rollback. Surface area and enclosed volume use known closed/open fixtures; source-unit corrections also survive untransformed mesh export.
654
+
655
+ Manual desktop checks cover size inputs, keyboard Apply and on-demand measurements. Narrow widths are viewport emulation, not physical phone tests. No physical touch device, Safari/Firefox or broad large-CAD stress testing is claimed. The bounded detailed mesh checker may return an unavailable/inconclusive volume; source coordinates without units remain assumed mm until corrected.
656
+
657
+ Website evidence is retained outside its public build in `Viewstl/results/rc6/`; local-only reproducible browser harnesses are `site/qa/model-tools.html`, `animation.html` and `index.html`. Generated assets and the whole npm distribution are packaged together.
package/legacy/README.md CHANGED
@@ -1,20 +1,26 @@
1
1
  # Preserved legacy versions
2
2
 
3
- The existing `src/`, top-level `build/` files, and `dependencies/` remain
4
- the GitHub legacy implementation. Keep their existing download paths.
5
- New v2 output belongs under `build/v2/`.
3
+ On the v2 `main` branch, [`v1/`](v1/) contains the complete legacy GitHub
4
+ tree, including its original README, source, build, dependencies and license.
5
+ Its manifest records the original bytes and SHA-256 hashes of all 23 files.
6
+
7
+ The existing [`master` branch](https://github.com/omrips/viewstl/tree/master)
8
+ remains unchanged at the baseline below, preserving explicit master download
9
+ URLs. Existing tags are retained. New v2 source and output live at the root
10
+ under `src/` and `build/` on `main`; callers following the default branch
11
+ receive v2 and should review its migration notes.
6
12
 
7
13
  ## GitHub baseline
8
14
 
9
15
  - Repository: https://github.com/omrips/viewstl
10
- - Original default branch: `master`
16
+ - Legacy branch: `master` (the default before v2)
11
17
  - Baseline commit: `fbb7f5c3894862778c3a6c5d2630573e2496d01f`
12
- - Local preservation tag: `legacy-before-v2-2026-09-30`
13
- - Latest published release when inspected: `v1.13`
18
+ - Preservation tag: `legacy-before-v2-2026-09-30`
19
+ - Latest legacy version tag: `v1.13`
14
20
 
15
21
  The baseline commit includes changes after the `v1.13` tag. Do not treat the
16
- release tag and the current legacy branch as identical. The preservation
17
- tag and development branch are local until explicitly pushed to GitHub.
22
+ release tag and the legacy branch as identical. The v2 release preserves
23
+ both that branch and the existing version tags.
18
24
 
19
25
  ## Local website snapshot
20
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omrips/viewstl",
3
- "version": "2.0.0-rc.4",
3
+ "version": "2.0.0-rc.6",
4
4
  "description": "Local-first STL, OBJ, 3MF, STEP and IGES viewer with native WebGL2 and transferable worker geometry",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -18,71 +18,54 @@
18
18
  ],
19
19
  "publishConfig": {
20
20
  "access": "public",
21
- "tag": "next",
21
+ "tag": "latest",
22
22
  "registry": "https://registry.npmjs.org/"
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
26
26
  "url": "https://github.com/omrips/viewstl.git"
27
27
  },
28
- "main": "./build/v2/viewstl.cjs",
29
- "module": "./build/v2/viewstl.js",
30
- "types": "./build/v2/viewstl.d.ts",
28
+ "main": "./build/viewstl.cjs",
29
+ "module": "./build/viewstl.js",
30
+ "types": "./build/viewstl.d.ts",
31
31
  "exports": {
32
32
  ".": {
33
33
  "import": {
34
- "types": "./build/v2/viewstl.d.ts",
35
- "default": "./build/v2/viewstl.js"
34
+ "types": "./build/viewstl.d.ts",
35
+ "default": "./build/viewstl.js"
36
36
  },
37
37
  "require": {
38
- "types": "./build/v2/viewstl.d.cts",
39
- "default": "./build/v2/viewstl.cjs"
38
+ "types": "./build/viewstl.d.cts",
39
+ "default": "./build/viewstl.cjs"
40
40
  }
41
41
  },
42
42
  "./node": {
43
43
  "node": {
44
44
  "import": {
45
- "types": "./build/v2/node/index.d.ts",
46
- "default": "./build/v2/node/index.js"
45
+ "types": "./build/node/index.d.ts",
46
+ "default": "./build/node/index.js"
47
47
  },
48
48
  "require": {
49
- "types": "./build/v2/node/index.d.cts",
50
- "default": "./build/v2/node/index.cjs"
49
+ "types": "./build/node/index.d.cts",
50
+ "default": "./build/node/index.cjs"
51
51
  }
52
52
  }
53
53
  },
54
- "./worker": "./build/v2/viewstl.worker.js",
55
- "./browser": "./build/v2/stl_viewer.min.js",
56
- "./assets/*": "./build/v2/*",
54
+ "./worker": "./build/viewstl.worker.js",
55
+ "./browser": "./build/stl_viewer.min.js",
56
+ "./assets/*": "./build/*",
57
57
  "./package.json": "./package.json"
58
58
  },
59
59
  "files": [
60
- "build/v2",
60
+ "build",
61
61
  "LICENSE",
62
62
  "docs/v2-usage.md",
63
63
  "docs/v2-compatibility.md",
64
64
  "docs/v2-verification.md",
65
+ "docs/releases/2.0.0-rc.6.md",
65
66
  "legacy/README.md",
66
67
  "THIRD-PARTY-NOTICES.txt"
67
68
  ],
68
- "scripts": {
69
- "build": "node scripts/build-v2.mjs",
70
- "test": "node --test tests/v2/*.test.mjs",
71
- "check:legacy": "node scripts/check-legacy.mjs",
72
- "check:types": "tsc --noEmit --strict --module NodeNext --moduleResolution NodeNext --target ES2022 --lib ES2022,DOM tests/v2/types.ts",
73
- "check:node-types": "tsc --noEmit --strict --module NodeNext --moduleResolution NodeNext --target ES2022 --lib ES2022 --types node tests/v2/node-types.mts tests/v2/node-types.cts",
74
- "prepack": "node scripts/build-v2.mjs && node scripts/check-legacy.mjs",
75
- "start": "node scripts/serve-v2.mjs"
76
- },
77
- "devDependencies": {
78
- "@types/node": "^20.19.43",
79
- "esbuild": "0.28.2",
80
- "fflate": "0.8.3",
81
- "occt-import-js": "0.0.23",
82
- "prettier": "3.9.9",
83
- "saxes": "6.0.0",
84
- "typescript": "5.9.3"
85
- },
86
69
  "engines": {
87
70
  "node": ">=20"
88
71
  }
@@ -1,15 +0,0 @@
1
- # Native WebGL2 build
2
-
3
- Generated ViewSTL v2.00 release-candidate artifacts. `stl_viewer.min.js` and `viewstl.min.js` are
4
- classic scripts exposing `StlViewer`. `viewstl.js` is the ES module;
5
- `viewstl.cjs` is the CommonJS entry. Keep the module worker, `chunks/` and `cad/`
6
- directories and license notices beside these files when deploying. See [usage](../../docs/v2-usage.md).
7
-
8
- The existing `../stl_viewer.min.js` and its companion files remain the
9
- legacy distribution. A v2 build must never overwrite those files.
10
-
11
- See [the compatibility requirements](../../docs/v2-compatibility.md).
12
-
13
- `node/` contains the separate server analysis entry points and worker, exported
14
- as `@omrips/viewstl/node`. These resolve `cad/` inside the npm package; Node users
15
- do not need to copy assets to a website.
@@ -1 +0,0 @@
1
- import{a,b,c,d}from"./chunk-R3HNTB5A.js";import"./chunk-ZSV4PTIA.js";import"./chunk-JH3H3JWA.js";import"./chunk-JS56KC3I.js";import"./chunk-U33KANV7.js";export{d as parse3MF,a as safePath,b as unzipChecked,c as zipScene};
@@ -1 +0,0 @@
1
- import{a as k}from"./chunk-JS56KC3I.js";var z=Object.freeze({max_file_bytes:256*1024*1024,max_triangles:5e6,max_uncompressed_bytes:256*1024*1024,max_archive_entries:1024,max_memory_bytes:Number.MAX_SAFE_INTEGER});function O(e){if(typeof e!="string"||!/^#[\da-f]{6}$/i.test(e))throw new Error("Color must be #RRGGBB");return[1,3,5].map(i=>parseInt(e.slice(i,i+2),16)/255)}function U(e,i=null,I=z){if(!(e instanceof Float32Array)||!e.length||e.length%9)throw new Error("Geometry must contain complete triangles");if(e.length/9>I.max_triangles)throw new Error("Triangle limit exceeded");if(k(I,67108864+e.length/9*224),i&&(!(i instanceof Float32Array)||i.length!==e.length))throw new Error("Invalid vertex colors");let u={min:[1/0,1/0,1/0],max:[-1/0,-1/0,-1/0]};for(let r=0;r<e.length;r++){let f=e[r],t=r%3;if(!Number.isFinite(f))throw new Error("Non-finite vertex coordinate");if(u.min[t]=Math.min(u.min[t],f),u.max[t]=Math.max(u.max[t],f),i&&(!Number.isFinite(i[r])||i[r]<0||i[r]>1))throw new Error("Invalid vertex color")}let y=new Float32Array(e.length),b=new Float32Array(Math.ceil(e.length/9/64)*6),h=0,s=0,l=0;for(let r=0;r<e.length;r+=9){let f=e[r+3]-e[r],t=e[r+4]-e[r+1],c=e[r+5]-e[r+2],n=e[r+6]-e[r],o=e[r+7]-e[r+1],x=e[r+8]-e[r+2],w=t*x-c*o,M=c*n-f*x,g=f*o-t*n,E=Math.hypot(w,M,g);h+=E/2,s+=(e[r]*w+e[r+1]*M+e[r+2]*g)/6,E||l++;for(let a=0;a<9;a+=3)y[r+a]=w/(E||1),y[r+a+1]=M/(E||1),y[r+a+2]=g/(E||1);let m=Math.floor(r/9/64)*6;r/9%64===0&&b.set([1/0,1/0,1/0,-1/0,-1/0,-1/0],m);for(let a=0;a<9;a++){let d=a%3;b[m+d]=Math.min(b[m+d],e[r+a]),b[m+3+d]=Math.max(b[m+3+d],e[r+a])}}if(!Number.isFinite(h)||!Number.isFinite(s))throw new Error("Geometry magnitude exceeds numeric limits");return{positions:e,normals:y,colors:i,blocks:b,bounds:u,triangles:e.length/9,area:h,volume:Math.abs(s),degenerateTriangles:l}}function j(e,{smooth:i=!1,edges:I=!1,wire:u=!1,edgeAngle:y=1}={}){let b=new Map,h=new Uint32Array(e.length/3),s=[],l=[];for(let t=0;t<e.length;t+=3){let c=`${e[t]},${e[t+1]},${e[t+2]}`,n=b.get(c);n===void 0&&(n=s.length,b.set(c,n),s.push([e[t],e[t+1],e[t+2]]),l.push([0,0,0])),h[t/3]=n}let r=new Map;for(let t=0;t<e.length;t+=9){let c=e[t+3]-e[t],n=e[t+4]-e[t+1],o=e[t+5]-e[t+2],x=e[t+6]-e[t],w=e[t+7]-e[t+1],M=e[t+8]-e[t+2],g=[n*M-o*w,o*x-c*M,c*w-n*x],E=Math.hypot(...g)||1;for(let m=0;m<3;m++)if(i)for(let a=0;a<3;a++)l[h[t/3+m]][a]+=g[a];if(I||u)for(let m=0;m<3;m++){let a=h[t/3+m],d=h[t/3+(m+1)%3],A=a<d?`${a}:${d}`:`${d}:${a}`,F=g.map(v=>v/E),_=r.get(A);_?(_.count++,_.n.reduce((v,N,T)=>v+N*F[T],0)<Math.cos(y*Math.PI/180)&&(_.sharp=!0)):r.set(A,{a,b:d,n:F,count:1,sharp:!1})}}let f={};if(i){f.smoothNormals=new Float32Array(e.length);for(let t=0;t<h.length;t++){let c=l[h[t]],n=Math.hypot(...c)||1;f.smoothNormals.set(c.map(o=>o/n),t*3)}}for(let[t,c,n]of[[I,"edges",o=>o.count!==2||o.sharp],[u,"wire",()=>!0]])if(t){let o=[...r.values()].filter(n),x=new Float32Array(o.length*6);o.forEach((w,M)=>{x.set(s[w.a],M*6),x.set(s[w.b],M*6+3)}),f[c]=x,c==="edges"&&(f.edgeAngle=y)}return f}function D(e,i=[1,1,1],I=[0,0,0]){let u=new ArrayBuffer(84+e.triangles*50),y=new DataView(u),b=e.positions;new Uint8Array(u).set(new TextEncoder().encode("Binary STL by ViewSTL 2")),y.setUint32(80,e.triangles,!0);for(let h=0;h<e.triangles;h++){let s=84+h*50,l=Array.from({length:9},(n,o)=>(b[h*9+o]-I[o%3])*i[o%3]),r=[l[3]-l[0],l[4]-l[1],l[5]-l[2]],f=[l[6]-l[0],l[7]-l[1],l[8]-l[2]],t=[r[1]*f[2]-r[2]*f[1],r[2]*f[0]-r[0]*f[2],r[0]*f[1]-r[1]*f[0]],c=Math.hypot(...t)||1;for(let n=0;n<3;n++)y.setFloat32(s+n*4,t[n]/c,!0);for(let n=0;n<9;n++)y.setFloat32(s+12+n*4,l[n],!0);if(e.colors){let n=[0,1,2].map(o=>Math.round(e.colors[h*9+o]*31));y.setUint16(s+48,32768|n[0]<<10|n[1]<<5|n[2],!0)}}return u}export{z as a,O as b,U as c,j as d,D as e};
@@ -1 +0,0 @@
1
- var f=Object.create;var s=Object.defineProperty;var c=Object.getOwnPropertyDescriptor;var i=Object.getOwnPropertyNames;var l=Object.getPrototypeOf,u=Object.prototype.hasOwnProperty;var x=(t,r)=>()=>{try{return r||t((r={exports:{}}).exports,r),r.exports}catch(o){throw r=0,o}};var h=(t,r,o,e)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of i(r))!u.call(t,n)&&n!==o&&s(t,n,{get:()=>r[n],enumerable:!(e=c(r,n))||e.enumerable});return t};var M=(t,r,o)=>(o=t!=null?f(l(t)):{},h(r||!t||!t.__esModule?s(o,"default",{value:t,enumerable:!0}):o,t));var A=(t,r)=>t.map((o,e)=>o-r[e]),y=(t,r)=>t.map(o=>o*r);var w=(t,r)=>[t[1]*r[2]-t[2]*r[1],t[2]*r[0]-t[0]*r[2],t[0]*r[1]-t[1]*r[0]],F=t=>y(t,1/(Math.hypot(...t)||1)),I=()=>new Float64Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]);function d(t,r){let o=new Float64Array(16);for(let e=0;e<4;e++)for(let n=0;n<4;n++)for(let a=0;a<4;a++)o[e*4+n]+=t[a*4+n]*r[e*4+a];return o}function _(t,r,o=1){let e=t[3]*r[0]+t[7]*r[1]+t[11]*r[2]+t[15]*o;return[0,1,2].map(n=>(t[n]*r[0]+t[4+n]*r[1]+t[8+n]*r[2]+t[12+n]*o)/(o&&e?e:1))}export{x as a,M as b,A as c,w as d,F as e,I as f,d as g,_ as h};