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

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 CHANGED
@@ -4,13 +4,13 @@
4
4
 
5
5
  Embed a viewer in your website, open local 3D files, inspect models and save the result. ViewSTL v2 uses its own renderer, with no Three.js runtime required. File parsing and geometry analysis run in workers, with memory limits and cancellation support.
6
6
 
7
- [Try the preview](https://preview.viewstl.com/) · [v2 usage and API notes](https://unpkg.com/@omrips/viewstl@2.0.0-rc.3/docs/v2-usage.md) · [Legacy plugin documentation](https://www.viewstl.com/plugin/)
7
+ [Try the preview](https://preview.viewstl.com/) · [v2 usage and API notes](https://unpkg.com/@omrips/viewstl@2.0.0-rc.4/docs/v2-usage.md) · [Legacy plugin documentation](https://www.viewstl.com/plugin/)
8
8
 
9
- > **Preview release: 2.0.0-rc.3.** v2 is a new implementation, with documented differences from v1. Existing versions remain available at their original paths. Read the migration notes before updating an existing integration.
9
+ > **Preview release: 2.0.0-rc.4.** v2 is a new implementation, with documented differences from v1. Existing versions remain available at their original paths. Read the migration notes before updating an existing integration.
10
10
 
11
11
  ## Quick start
12
12
 
13
- Install with `npm install @omrips/viewstl@2.0.0-rc.3`, then copy the complete `node_modules/@omrips/viewstl/build/v2/` folder to `/viewstl-v2/` on your website.
13
+ Install with `npm install @omrips/viewstl@2.0.0-rc.4`, then copy the complete `node_modules/@omrips/viewstl/build/v2/` folder to `/viewstl-v2/` on your website.
14
14
 
15
15
  Paste these three lines into your page's `<body>`:
16
16
 
@@ -22,16 +22,19 @@ Paste these three lines into your page's `<body>`:
22
22
 
23
23
  Replace `model.stl` with your model's path and open the page over HTTP(S). That's it. You can also drag local files onto the viewer.
24
24
 
25
- ## Quick start (Node.js)
25
+ ## Server-only example (Node.js)
26
26
 
27
- For server-side model analysis, install `npm install @omrips/viewstl@2.0.0-rc.3` and run:
27
+ Requires Node.js 20+. Install `npm install @omrips/viewstl@2.0.0-rc.4`, then save this as `analyze.mjs` beside your `part.stl` file:
28
28
 
29
29
  ```js
30
30
  import { analyzeFile } from '@omrips/viewstl/node';
31
31
  const report = await analyzeFile('part.stl', { units: 'mm' });
32
+ console.log(JSON.stringify(report, null, 2));
32
33
  ```
33
34
 
34
- Requires Node.js 20+. Workers and CAD assets load directly from the installed package. The result is a JSON-compatible report with dimensions, surface area, validated volume and mesh checks. STL, OBJ, 3MF, STEP and IGES are supported. See [server API and limits](https://unpkg.com/@omrips/viewstl@2.0.0-rc.3/docs/v2-usage.md#nodejs-model-analysis) for buffers, batch processing, cancellation and unit handling.
35
+ Run `node analyze.mjs`. It prints a JSON report with dimensions, surface area, validated volume and mesh checks. This runs entirely in Node.js; workers and CAD assets load directly from the installed package.
36
+
37
+ STL, OBJ, 3MF, STEP and IGES are supported. For STL/OBJ, set `units` to your model's actual scale; 3MF/STEP/IGES use their file units. See [server API and limits](https://unpkg.com/@omrips/viewstl@2.0.0-rc.4/docs/v2-usage.md#nodejs-model-analysis) for buffers, batch processing and cancellation.
35
38
 
36
39
  ## What v2 can do
37
40
 
@@ -90,12 +93,12 @@ Replace `/models/part.stl` with a model URL you serve. To open a local file inst
90
93
 
91
94
  ### npm and bundlers
92
95
 
93
- > **Release candidate:** `2.0.0-rc.3` adds the Node.js model-analysis API to ViewSTL v2.00. Candidates use the `next` dist-tag; pin the version below for a repeatable installation.
96
+ > **Release candidate:** `2.0.0-rc.4` includes the Node.js model-analysis API and a runnable server-only example. Pin the version below for a repeatable installation.
94
97
 
95
98
  Install the package in your application:
96
99
 
97
100
  ```sh
98
- npm install @omrips/viewstl@2.0.0-rc.3
101
+ npm install @omrips/viewstl@2.0.0-rc.4
99
102
  ```
100
103
 
101
104
  Copy the **complete `node_modules/@omrips/viewstl/build/v2/` directory** to your application's static assets. For an application that serves `public/` at its root, use `public/viewstl-v2/`. Keep the worker, `chunks/`, `cad/` and notices together; repeat this copy when updating the package.
@@ -120,7 +123,7 @@ const viewer = new StlViewer(document.getElementById('viewer'), {
120
123
 
121
124
  Set `worker_url` to the URL where your application serves the copied worker, and replace `/models/part.stl` with your model URL. The bundler handles the viewer import; the static directory supplies its worker and optional parser assets. Creating a viewer requires a browser DOM and WebGL 2.
122
125
 
123
- The distribution includes TypeScript declarations and a CommonJS entry point. The static-assets setup above is verified with Vite 8.3.4 and Webpack 5.112.0. See [usage and packaging](https://unpkg.com/@omrips/viewstl@2.0.0-rc.3/docs/v2-usage.md) for worker factories, callbacks and more examples.
126
+ The distribution includes TypeScript declarations and a CommonJS entry point. The static-assets setup above is verified with Vite 8.3.4 and Webpack 5.112.0. See [usage and packaging](https://unpkg.com/@omrips/viewstl@2.0.0-rc.4/docs/v2-usage.md) for worker factories, callbacks and more examples.
124
127
 
125
128
  ## Formats and saving
126
129
 
@@ -137,7 +140,7 @@ The distribution includes TypeScript declarations and a CommonJS entry point. Th
137
140
 
138
141
  STL, OBJ, 3MF, STEP and IGES export model geometry. **VSB and VSJ save the scene**, including its models, transforms and viewing settings. VSJ requires access to its referenced model files; VSB carries its mesh data inside the archive.
139
142
 
140
- STEP/IGES exports describe the loaded triangle mesh. They do not reconstruct original CAD solids, curves, feature history or assemblies. OBJ import does not load external materials or textures, and 3MF support does not cover every extension. See the [format and export details](https://unpkg.com/@omrips/viewstl@2.0.0-rc.3/docs/v2-usage.md#rendering-and-file-processing).
143
+ STEP/IGES exports describe the loaded triangle mesh. They do not reconstruct original CAD solids, curves, feature history or assemblies. OBJ import does not load external materials or textures, and 3MF support does not cover every extension. See the [format and export details](https://unpkg.com/@omrips/viewstl@2.0.0-rc.4/docs/v2-usage.md#rendering-and-file-processing).
141
144
 
142
145
  ## Local files and URL imports
143
146
 
@@ -145,7 +148,7 @@ In the browser, local file contents are processed on the user's device. Renderin
145
148
 
146
149
  The optional Node.js API processes files on the machine running Node. If your application accepts uploads for server analysis, those files leave the user's device. The package itself provides no upload endpoint and does not send model data anywhere.
147
150
 
148
- Direct URL imports require the remote server to allow browser access through CORS. A host application can configure its own streaming relay for other public URLs. **The relay is a separate server service**, not part of the plugin's JavaScript bundle. The preview website uses such a relay; URL imports pass through that server. See [URL import configuration](https://unpkg.com/@omrips/viewstl@2.0.0-rc.3/docs/v2-usage.md#optional-streaming-url-imports).
151
+ Direct URL imports require the remote server to allow browser access through CORS. A host application can configure its own streaming relay for other public URLs. **The relay is a separate server service**, not part of the plugin's JavaScript bundle. The preview website uses such a relay; URL imports pass through that server. See [URL import configuration](https://unpkg.com/@omrips/viewstl@2.0.0-rc.4/docs/v2-usage.md#optional-streaming-url-imports).
149
152
 
150
153
  ## Existing users and previous versions
151
154
 
@@ -156,11 +159,11 @@ Direct URL imports require the remote server to allow browser access through COR
156
159
  | Source | Existing files in `src/` | `src/v2/` |
157
160
  | Browser script | `build/stl_viewer.min.js` | `build/v2/viewstl.min.js` |
158
161
  | Renderer | Three.js | Native WebGL 2 |
159
- | Documentation | [viewstl.com/plugin](https://www.viewstl.com/plugin/) | [Package usage and API notes](https://unpkg.com/@omrips/viewstl@2.0.0-rc.3/docs/v2-usage.md) |
162
+ | Documentation | [viewstl.com/plugin](https://www.viewstl.com/plugin/) | [Package usage and API notes](https://unpkg.com/@omrips/viewstl@2.0.0-rc.4/docs/v2-usage.md) |
160
163
 
161
- The familiar `StlViewer` constructor and many public methods are retained. Integrations that access Three.js camera, scene, renderer or model internals need migration. v2 requires WebGL 2 and does not include the legacy WebGL 1 or Canvas fallback. See [API differences and migration notes](https://unpkg.com/@omrips/viewstl@2.0.0-rc.3/docs/v2-usage.md#existing-api-and-intentional-differences).
164
+ The familiar `StlViewer` constructor and many public methods are retained. Integrations that access Three.js camera, scene, renderer or model internals need migration. v2 requires WebGL 2 and does not include the legacy WebGL 1 or Canvas fallback. See [API differences and migration notes](https://unpkg.com/@omrips/viewstl@2.0.0-rc.4/docs/v2-usage.md#existing-api-and-intentional-differences).
162
165
 
163
- Existing tags and release history are retained. The [legacy archive notes](https://unpkg.com/@omrips/viewstl@2.0.0-rc.3/legacy/README.md) also describe the separate local v1.14 snapshot and its provenance.
166
+ Existing tags and release history are retained. The [legacy archive notes](https://unpkg.com/@omrips/viewstl@2.0.0-rc.4/legacy/README.md) also describe the separate local v1.14 snapshot and its provenance.
164
167
 
165
168
  The documentation link remains **https://www.viewstl.com/plugin/**. It currently documents the legacy plugin; v2 guidance is included in this package while the website documentation is prepared.
166
169
 
@@ -169,7 +172,7 @@ The documentation link remains **https://www.viewstl.com/plugin/**. It currently
169
172
  - A browser with WebGL 2 and module-worker support; WebAssembly is required for STEP/IGES import.
170
173
  - Model capacity depends on geometry complexity and available browser/GPU memory. Configured limits reject files that exceed the budget.
171
174
  - Print checks describe the loaded mesh. FDM results are estimates, and checks do not guarantee a successful print. Models are checked separately; assembly overlaps are not evaluated.
172
- - The preview has automated and desktop Chromium/Edge coverage. Firefox, Safari and physical mobile-device validation remain pending. See [verification and known limits](https://unpkg.com/@omrips/viewstl@2.0.0-rc.3/docs/v2-verification.md).
175
+ - The preview has automated and desktop Chromium/Edge coverage. Firefox, Safari and physical mobile-device validation remain pending. See [verification and known limits](https://unpkg.com/@omrips/viewstl@2.0.0-rc.4/docs/v2-verification.md).
173
176
 
174
177
  ## Development
175
178
 
@@ -188,4 +191,4 @@ Open `http://127.0.0.1:8772/examples/v2/` for the local demo. The build writes t
188
191
 
189
192
  ## License
190
193
 
191
- ViewSTL is available under the [MIT license](https://unpkg.com/@omrips/viewstl@2.0.0-rc.3/LICENSE). Optional archive and CAD dependencies have their own licenses and notices; see [third-party notices](https://unpkg.com/@omrips/viewstl@2.0.0-rc.3/THIRD-PARTY-NOTICES.txt).
194
+ ViewSTL is available under the [MIT license](https://unpkg.com/@omrips/viewstl@2.0.0-rc.4/LICENSE). Optional archive and CAD dependencies have their own licenses and notices; see [third-party notices](https://unpkg.com/@omrips/viewstl@2.0.0-rc.4/THIRD-PARTY-NOTICES.txt).
@@ -48,4 +48,4 @@ if(!unlit){
48
48
  if(appearance==4)color=(gl_FrontFacing?vec3(0.12,0.55,0.72):vec3(0.9,0.24,0.32))*(0.55+0.45*l);
49
49
  if(dot(section.xyz,section.xyz)>0.0 && abs(cut)<max(fwidth(cut)*1.2,0.0000001)){color=vec3(0.93,0.57,0.17);alpha=opacity;}
50
50
  }
51
- result=vec4(color,alpha);}`,Ze=class{constructor(e,t,i,s){if(this.canvas=e,this.domElement=e,this.resources=new Map,this.frames=0,this.lost=!1,this.gl=e.getContext("webgl2",{alpha:!0,antialias:!0,premultipliedAlpha:!1,preserveDrawingBuffer:!1}),!this.gl)throw new Error("WebGL2 is unavailable. Enable browser graphics acceleration or try another browser/device.");this.lostHandler=r=>{r.preventDefault(),this.lost=!0,this.resources.clear(),this.gridResource=null,this.infiniteGrid=null,this.selectionResource=null,this.diagnosticResource=null,t()},this.restoredHandler=()=>{try{this.initialize(),this.lost=!1,i()}catch(r){s(r)}},e.addEventListener("webglcontextlost",this.lostHandler),e.addEventListener("webglcontextrestored",this.restoredHandler),this.initialize()}initialize(){let e=this.gl,t=[],i;try{for(let[s,r]of[[e.VERTEX_SHADER,Ho],[e.FRAGMENT_SHADER,qo]]){let n=e.createShader(s);if(t.push(n),e.shaderSource(n,r),e.compileShader(n),!e.getShaderParameter(n,e.COMPILE_STATUS))throw new Error(`Shader compilation failed: ${e.getShaderInfoLog(n)}`)}i=e.createProgram();for(let s of t)e.attachShader(i,s);if(e.linkProgram(i),!e.getProgramParameter(i,e.LINK_STATUS))throw new Error(`Shader link failed: ${e.getProgramInfoLog(i)}`);this.program=i,this.uniforms=Object.fromEntries(["mvp","modelMatrix","normalMatrix","tint","light","eye","opacity","unlit","section","appearance","useColors"].map(s=>[s,e.getUniformLocation(i,s)]))}catch(s){throw i&&e.deleteProgram(i),s}finally{for(let s of t)e.deleteShader(s)}}upload(e,t,i){let s=this.gl,r=s.createVertexArray(),n=[];if(!r)throw new Error("Graphics memory is unavailable for this model.");s.bindVertexArray(r);try{for(let[d,l]of[[0,e],[1,t],[2,i]])if(l){let c=s.createBuffer();if(!c)throw new Error("Graphics memory is unavailable for this model.");n.push(c),s.bindBuffer(s.ARRAY_BUFFER,c),s.bufferData(s.ARRAY_BUFFER,l,s.STATIC_DRAW);let m=s.getError();if(m!==s.NO_ERROR)throw new Error(m===s.OUT_OF_MEMORY?"Not enough graphics memory for this model. Try closing other tabs or using another device.":"The browser could not upload this model to the graphics device.");s.enableVertexAttribArray(d),s.vertexAttribPointer(d,3,s.FLOAT,!1,0,0)}return s.bindVertexArray(null),{vao:r,buffers:n,count:e.length/3}}catch(d){s.bindVertexArray(null);for(let l of n)s.deleteBuffer(l);throw s.deleteVertexArray(r),d}}free(e){if(e){for(let t of e.buffers)this.gl.deleteBuffer(t);this.gl.deleteVertexArray(e.vao)}}remove(e){this.diagnostic?.model===e&&this.setDiagnostic(null);let t=this.resources.get(e);if(t)for(let i of Object.values(t))this.free(i);this.resources.delete(e)}clear(){this.setDiagnostic(null),this.setSelection(null);for(let e of this.resources.keys())this.remove(e);this.free(this.gridResource),this.gridResource=null,this.infiniteGrid?.dispose(),this.infiniteGrid=null}get liveBuffers(){let e=(this.gridResource?.buffers.length||0)+(this.selectionResource?.buffers.length||0)+(this.diagnosticResource?.buffers.length||0);for(let t of this.resources.values())for(let i of Object.values(t))e+=i.buffers.length;return e}setDiagnostic(e){this.free(this.diagnosticResource),this.diagnosticResource=null,this.diagnostic=e}drawDiagnostic(e){let t=this.gl,i=this.uniforms,s=this.diagnostic,r=X(s.model);this.diagnosticResource??=this.upload(s.positions),t.uniformMatrix4fv(i.mvp,!1,new Float32Array(ze(e,r))),t.uniformMatrix4fv(i.modelMatrix,!1,new Float32Array(r)),t.uniform4fv(i.section,[0,0,0,0]),t.uniform1i(i.appearance,0),t.uniform1i(i.useColors,0),t.uniform1i(i.unlit,1),t.uniform3fv(i.tint,[.85,.16,.06]),t.uniform1f(i.opacity,1),t.bindVertexArray(this.diagnosticResource.vao),t.disable(t.DEPTH_TEST),t.drawArrays(t.LINES,0,this.diagnosticResource.count),t.enable(t.DEPTH_TEST)}setSelection(e){this.selection!==e&&(this.free(this.selectionResource),this.selectionResource=null,this.selection=e)}drawSelection(e,t){let i=this.gl,s=this.uniforms,r=X(this.selection);this.selectionResource??=this.upload(po(this.selection.geometry.bounds)),i.uniformMatrix4fv(s.mvp,!1,new Float32Array(ze(e,r))),i.uniformMatrix4fv(s.modelMatrix,!1,new Float32Array(r)),i.uniformMatrix3fv(s.normalMatrix,!1,new Float32Array([1,0,0,0,1,0,0,0,1])),i.uniform4fv(s.section,[0,0,0,0]),i.uniform1i(s.appearance,0),i.uniform1i(s.useColors,0),i.uniform1i(s.unlit,1);let n=t==="transparent"?[1,1,1]:Y(t);i.uniform3fv(s.tint,n[0]+n[1]+n[2]<1.5?[.42,.81,.96]:[.08,.43,.65]),i.bindVertexArray(this.selectionResource.vao),i.disable(i.DEPTH_TEST),i.uniform1f(s.opacity,.18),i.drawArrays(i.LINES,0,this.selectionResource.count),i.enable(i.DEPTH_TEST),i.uniform1f(s.opacity,.9),i.drawArrays(i.LINES,0,this.selectionResource.count)}resource(e,t){let i=this.resources.get(e);if(i||(i={},this.resources.set(e,i)),!i[t]){let s=e.geometry;i[t]=t==="edges"||t==="wire"?this.upload(s[t]):this.upload(s.positions,t==="smooth"?s.smoothNormals:s.normals,s.colors)}return i[t]}setGrid(e){this.grid=e,this.free(this.gridResource),this.gridResource=null}resize(e,t,i=1){let s=this.gl,r=s.getParameter(s.MAX_RENDERBUFFER_SIZE);this.canvas.width=Math.min(r,Math.max(1,Math.round(e*i))),this.canvas.height=Math.min(r,Math.max(1,Math.round(t*i)))}draw(e,t,i,s=!0){if(this.lost)return;let r=this.gl,n=this.uniforms;r.viewport(0,0,this.canvas.width,this.canvas.height),r.clearColor(...i==="transparent"?[0,0,0,0]:[...Y(i),1]),r.depthMask(!0),r.clear(r.COLOR_BUFFER_BIT|r.DEPTH_BUFFER_BIT),r.enable(r.DEPTH_TEST),r.disable(r.CULL_FACE),r.useProgram(this.program),r.vertexAttrib3f(1,0,0,1),r.vertexAttrib3f(2,1,1,1),r.uniform3fv(n.light,H(V(t.position,t.target))),r.uniform3fv(n.eye,t.position);let d=Ye(t,this.canvas.width/this.canvas.height),l=e.filter(f=>f.geometry&&f.opacity>0&&f.color!=="transparent"),c=f=>{let u=le(X(f),f.geometry.bounds.min.map((y,g)=>(y+f.geometry.bounds.max[g])/2)),p=V(u,t.position);return te(p,p)},m=l.filter(f=>f.opacity>=1&&f.display!=="xray"),_=l.filter(f=>f.opacity<1||f.display==="xray").sort((f,u)=>c(u)-c(f)),w=f=>{let u=X(f),p=fe(u),y=f.geometry;r.uniformMatrix4fv(n.mvp,!1,new Float32Array(ze(d,u))),r.uniformMatrix4fv(n.modelMatrix,!1,new Float32Array(u)),r.uniform4fv(n.section,We(y.bounds,f.section)),r.uniform1i(n.appearance,{mesh:1,xray:2,studio:3,orientation:4}[f.display]||0),r.uniformMatrix3fv(n.normalMatrix,!1,new Float32Array([p[0],p[4],p[8],p[1],p[5],p[9],p[2],p[6],p[10]])),r.uniform3fv(n.tint,f.color&&f.color!=="transparent"?Y(f.color):y.colors?[1,1,1]:Y("#909090")),r.uniform1f(n.opacity,f.opacity),r.uniform1i(n.useColors,+(!f.color&&!!y.colors));let g=f.display==="wireframe"&&y.wire,A=g?"wire":(f.smooth_shading||f.display==="smooth")&&y.smoothNormals?"smooth":"flat";r.uniform1i(n.unlit,+!!g);let k=f.view_edges||f.display==="edges";k&&(r.enable(r.POLYGON_OFFSET_FILL),r.polygonOffset(1,1));let E=this.resource(f,A);if(r.bindVertexArray(E.vao),f.display==="xray"?(r.enable(r.CULL_FACE),r.cullFace(r.FRONT),r.drawArrays(r.TRIANGLES,0,E.count),r.cullFace(r.BACK),r.drawArrays(r.TRIANGLES,0,E.count),r.disable(r.CULL_FACE)):r.drawArrays(g?r.LINES:r.TRIANGLES,0,E.count),r.disable(r.POLYGON_OFFSET_FILL),k&&y.edges){r.uniform1i(n.useColors,0),r.uniform3fv(n.tint,[.08,.09,.11]),r.uniform1i(n.unlit,1);let x=this.resource(f,"edges");r.bindVertexArray(x.vao),r.drawArrays(r.LINES,0,x.count)}};r.disable(r.BLEND);for(let f of m)w(f);if(this.grid?.infinite)this.infiniteGrid??=new Ke(r),this.infiniteGrid.draw(d,t,this.grid,i),r.useProgram(this.program);else if(this.grid){if(!this.gridResource){let{size:f,divisions:u}=this.grid,p=[];for(let y=0;y<=u;y++){let g=-f/2+f*y/u;p.push(-f/2,g,0,f/2,g,0,g,-f/2,0,g,f/2,0)}this.gridResource=this.upload(new Float32Array(p))}r.uniformMatrix4fv(n.mvp,!1,new Float32Array(d)),r.uniform4fv(n.section,[0,0,0,0]),r.uniform1i(n.appearance,0),r.uniform1i(n.useColors,0),r.uniform3fv(n.tint,[.7,.73,.78]),r.uniform1f(n.opacity,1),r.uniform1i(n.unlit,1),r.bindVertexArray(this.gridResource.vao),r.drawArrays(r.LINES,0,this.gridResource.count)}r.enable(r.BLEND),r.blendFuncSeparate(r.SRC_ALPHA,r.ONE_MINUS_SRC_ALPHA,r.ONE,r.ONE_MINUS_SRC_ALPHA),r.depthMask(!1);for(let f of _)w(f);s&&this.selection&&l.includes(this.selection)&&this.drawSelection(d,i),s&&this.diagnostic&&e.includes(this.diagnostic.model)&&this.drawDiagnostic(d),r.depthMask(!0),r.disable(r.BLEND),r.bindVertexArray(null),this.frames++}dispose(){this.canvas.removeEventListener("webglcontextlost",this.lostHandler),this.canvas.removeEventListener("webglcontextrestored",this.restoredHandler),this.clear(),this.gl.deleteProgram(this.program),this.gl.getExtension("WEBGL_lose_context")?.loseContext()}};var Qe=class{constructor(e,t,i,s,r=0){this.enabled=!0,this.noZoom=!1,this.type=r,this.camera=t,this.invalidate=i,this.pointers=new Map,this.cleanups=[],this.canvas=e;let n=(l,c,m)=>{e.addEventListener(l,c,m),this.cleanups.push(()=>e.removeEventListener(l,c,m))};n("pointerdown",l=>{this.enabled&&(l.preventDefault(),e.focus({preventScroll:!0}),e.setPointerCapture(l.pointerId),this.pointers.set(l.pointerId,{x:l.clientX,y:l.clientY,button:l.button}),s(l,l.pointerType==="touch"?20:l.button===2?3:1))}),n("pointermove",l=>{let c=this.pointers.get(l.pointerId);if(!c||!this.enabled)return;let m={x:l.clientX,y:l.clientY,button:c.button};if(this.pointers.size===2){let _=[...this.pointers.entries()].find(([u])=>u!==l.pointerId)[1],w=Math.hypot(c.x-_.x,c.y-_.y),f=Math.hypot(m.x-_.x,m.y-_.y);w>0&&f>0&&this.zoom(w/f),this.pan((m.x-c.x)/2,(m.y-c.y)/2)}else c.button===2||l.shiftKey||c.button===1?this.pan(m.x-c.x,m.y-c.y):this.orbit((m.x-c.x)/Math.max(1,e.clientHeight)*Math.PI,(m.y-c.y)/Math.max(1,e.clientHeight)*Math.PI);this.pointers.set(l.pointerId,m)});let d=l=>{this.pointers.delete(l.pointerId),e.hasPointerCapture(l.pointerId)&&e.releasePointerCapture(l.pointerId)};n("pointerup",d),n("pointercancel",d),n("lostpointercapture",l=>this.pointers.delete(l.pointerId)),n("wheel",l=>{if(!this.enabled||this.noZoom)return;l.preventDefault();let c=l.deltaY*(l.deltaMode===1?16:l.deltaMode===2?e.clientHeight:1);this.zoom(Math.exp(Math.max(-1,Math.min(1,c*.001))))},{passive:!1}),n("dblclick",l=>{this.enabled&&s(l,11)}),n("contextmenu",l=>l.preventDefault())}orbit(e,t){let i=this.camera,s=V(i.position,i.target),r=H(i.up);s=Je(s,r,-e);let n=H(ie(r,s)),d=Je(s,n,-t);if(this.type===1)s=d,i.up=H(Je(r,n,-t));else{let l=H(d),c=l.reduce((m,_,w)=>m+_*r[w],0);Math.abs(c)<.9995&&(s=d)}i.position=re(i.target,s),this.invalidate()}zoom(e){if(this.noZoom)return;let t=this.camera,i=V(t.position,t.target),s=Math.hypot(...i),r=Math.max(1e-6,Math.min(1e15,s*e));t.position=re(t.target,oe(i,r/(s||1))),this.invalidate()}pan(e,t){let i=this.camera,s=V(i.position,i.target),r=H(ie(i.up,s)),n=H(ie(s,r)),d=2*Math.hypot(...s)*Math.tan(Math.PI/8)/Math.max(1,this.canvas.clientHeight),l=re(oe(r,-e*d),oe(n,t*d));i.target=re(i.target,l),i.position=re(i.position,l),this.invalidate()}dispose(){for(let e of this.cleanups)e();this.pointers.clear()}};var $e=class{constructor(e,{count:t=2,timeout:i=12e4,factory:s}={}){this.url=e,this.count=Math.max(1,Math.min(4,t)),this.timeout=i,this.factory=s,this.queue=[],this.active=new Set,this.next=0,this.disposed=!1}run(e,t,i=[]){if(this.disposed)return Promise.reject(new Error("Viewer disposed"));let s=++this.next,r,n=new Promise((d,l)=>{r={id:s,task:e,progress:t,transfer:i,resolve:d,reject:l},this.queue.push(r)});return n.cancel=()=>this.cancel(r),this.pump(),n}cancel(e){e.done||(e.done=!0,this.queue=this.queue.filter(t=>t!==e),clearTimeout(e.timer),e.worker?.terminate(),this.active.delete(e),e.reject(new DOMException("Loading cancelled","AbortError")),this.pump())}pump(){for(;!this.disposed&&this.active.size<this.count&&this.queue.length;){let e=this.queue.shift();this.active.add(e);let t=(i,s)=>{e.done||(e.done=!0,clearTimeout(e.timer),e.worker?.terminate(),this.active.delete(e),i?e.reject(i):e.resolve(s),this.pump())};try{e.worker=this.factory?this.factory():new Worker(this.url,{type:"module",name:"ViewSTL parser"}),e.worker.onmessage=({data:i})=>{if(i.id===e.id){if(i.progress){e.progress?.(i.progress);return}t(i.error?new Error(i.error):null,i.result)}},e.worker.onerror=i=>{i.preventDefault(),t(new Error(i.message||"Worker failed; check worker URL and Content Security Policy"))},e.worker.onmessageerror=()=>t(new Error("Invalid worker response")),e.timer=setTimeout(()=>t(new Error("File processing timed out")),this.timeout),e.worker.postMessage({id:e.id,task:e.task},e.transfer)}catch(i){t(i)}}}clear(){for(let e of[...this.queue,...this.active])this.cancel(e)}dispose(){this.disposed=!0,this.clear()}};function go(o,e={}){let t=new AbortController,i=t.signal,s=this._generation,r=[],n=new Map,d=new Set,l=this._models.size>0,c,m=0,_,w=!1,f,u=()=>{t.abort(),c?.cancel?.();for(let[A,k]of n)this._models.get(A)===k&&this.remove_model(A)},p=()=>{if(i.aborted||s!==this._generation||this.killsign)throw new DOMException("Scene opening cancelled","AbortError")},y=A=>e.on_progress?.(A);e.signal?.addEventListener("abort",u,{once:!0}),e.signal?.aborted&&u(),i.addEventListener("abort",()=>c?.cancel?.(),{once:!0}),this._aborters.add(t);let g=(async()=>{await this.when_ready,p(),_=this.get_camera_state();let A=e.format||pe(typeof o=="string"?o:o.name);if(!["vsb","vsj"].includes(A))throw new Error("Choose VSB or VSJ for a scene");let k,E;if(typeof o=="string"){if(k=Mt(o,document.baseURI),!this.options.url_import_endpoint)throw new Error("Configure url_import_endpoint for scene URL imports");if(E=new URL(this.options.url_import_endpoint,document.baseURI).href,new URL(E).origin!==location.origin)throw new Error("The URL relay must use the same origin as this page")}let x=this.limits.max_memory_bytes-this._occupiedMemory();K(this.limits,this._occupiedMemory()+67108864,"scene opening");let C={...this.limits,max_memory_bytes:x,max_file_bytes:Math.min(this.limits.max_file_bytes,Math.floor((x-67108864)/4))};m=Math.max(0,x-67108864),this._sceneBytes=(this._sceneBytes||0)+m,c=this._pool.run({kind:"read-scene",source:o,remote:k,relay:E,format:A,name:`scene.${A}`,limits:C},M=>{try{y({...M,phase:"reading-scene"})}catch(R){f=R,c.cancel()}});let I;try{I=await c}catch(M){throw f||M}c=null,p();let{scene:v,files:F,base:z}=I;this._sceneBytes-=m,m=Object.values(F||{}).reduce((M,R)=>M+R.byteLength,0)+JSON.stringify(v).length*2,this._sceneBytes+=m;let P=e.files||[],L=[],D=v.models.map(M=>{let R={...M};if(F)R.local_file=new File([F[M.filename]],M.filename);else{let j=mo(M.filename,P);if(j)R.local_file=j,d.add(j);else{let O=Mt(M.filename,k?z:void 0);O?R.remote_url=O:L.push(M.filename)}}if(R.remote_url&&!this.options.url_import_endpoint)throw new Error("Scene URL references require a configured URL relay");return R});if(L.length)throw new Error("Choose the VSJ together with these model files: "+L.slice(0,8).join(", ")+(L.length>8?"\u2026":""));if(D.length+this._models.size>this.limits.max_archive_entries)throw new Error("Scene model count limit exceeded");for(let M=0;M<D.length;M++){p(),y({phase:"opening-model",loaded:M,total:D.length}),p();let R=D[M],j=this.max_model_id+1;this.add_model({...R,id:j});let O=this._models.get(j);if(!O)throw new Error(this.error||"Invalid scene model");if(r.push(j),n.set(j,O),O._sceneImportPending=!0,await this._pending.get(j),p(),this._models.get(j)!==O||!O.geometry)throw new Error(this.error||"A scene model could not be opened");let we=v.center_models??this.center_models?O.geometry.bounds.min.map((W,ee)=>(W+O.geometry.bounds.max[ee])/2):[0,0,0],J=O.center.map((W,ee)=>W-we[ee]),q=le(X({...O,x:0,y:0,z:0,center:[0,0,0],scaleVector:O.scaleVector}),J);if(["x","y","z"].forEach((W,ee)=>O[W]+=q[ee]),[...this._models.values()].reduce((W,ee)=>W+(ee.geometry?.triangles||0),0)>this.limits.max_triangles)throw new Error("Scene triangle limit exceeded")}y({phase:"complete",loaded:D.length,total:D.length}),p();for(let M of n.values())M._sceneImportPending=!1;return v.bg_color!==void 0&&this.set_bg_color(v.bg_color),v.infinite_grid?this.set_infinite_grid(!0,v.infinite_grid.spacing,v.infinite_grid.height):this.set_grid(!!v.grid),v.auto_rotate!==void 0&&this.set_auto_rotate(v.auto_rotate),this._restoreCamera=v.camera_state||(l?_:null),w=!0,{model_ids:r,scene:v,used_files:[...d]}})().catch(A=>{throw u(),A}).finally(()=>{e.signal?.removeEventListener("abort",u),this._aborters.delete(t),this._sceneBytes=(this._sceneBytes||0)-m,this._sceneLoads.delete(g),s===this._generation&&!this.killsign&&(!w&&_&&(this._restoreCamera=_),this._finishSession())});return this._sessionActive=!0,this._sceneLoads.add(g),g}function _o(o={}){let e=o.level??"basic";if(!["basic","detailed"].includes(e))throw new Error("Choose basic or detailed print checks");let t=(r,n,d=1e9)=>{if(!Number.isFinite(r)||r<=0||r>d)throw new Error(`Invalid ${n}`);return r},i=null;if(o.profile!=null){if(o.profile.technology!=="fdm")throw new Error("Only FDM profiles are supported");i={technology:"fdm"};for(let r of["min_wall_mm","line_width_mm"])o.profile[r]!=null&&(i[r]=t(o.profile[r],r));if(o.profile.overhang_degrees!=null&&(i.overhang_degrees=t(o.profile.overhang_degrees,"overhang angle",89.9)),o.profile.build_volume_mm!=null){if(!Array.isArray(o.profile.build_volume_mm)||o.profile.build_volume_mm.length!==3)throw new Error("Build volume requires X, Y and Z");i.build_volume_mm=o.profile.build_volume_mm.map(r=>t(r,"build volume"))}}let s=t(o.max_time_ms??3e4,"check time limit",12e4);return{level:e,profile:i,apply_transforms:o.apply_transforms!==!1,units_confirmed:o.units_confirmed===!0,max_time_ms:s}}var yo=o=>64*1024**2+o*2400,ge=o=>JSON.stringify([o.x,o.y,o.z,o.rotationx,o.rotationy,o.rotationz,o.scaleVector,o.center,o.units,o.unitScale]);var Re=new WeakMap;function et(o,e){let t=Re.get(e);return!!t&&t.viewer.deref()===o&&t.models.every(({model:i,geometry:s,signature:r})=>{let n=i.deref();return n&&o._models.get(n.id)===n&&n.geometry===s.deref()&&ge(n)===r})}async function bo(o={}){await this.when_ready;let e=_o(o);if(this.killsign)throw new Error("Viewer disposed");let t=o.model_ids;if(t==="selected"&&(t=this.renderer?.selection?[this.renderer.selection.id]:[]),t??=[...this._models.keys()],!Array.isArray(t)||!t.length||new Set(t).size!==t.length||t.some(u=>!Number.isSafeInteger(u)))throw new Error("Choose one or more unique model IDs");if(t.length>this.limits.max_archive_entries)throw new Error("Print check model limit exceeded");let i=t.map(u=>{let p=this._model(u);if(!p.geometry||p._sceneImportPending)throw new Error("Wait for models to finish opening");return{model:p,geometry:p.geometry,signature:ge(p)}}),s=JSON.stringify([t,e]);if(o.signal?.aborted)throw new DOMException("Print check cancelled","AbortError");if(this._printCache?.key===s&&et(this,this._printCache.report)){let u=structuredClone(this._printCache.report);return Re.set(u,Re.get(this._printCache.report)),u}if(this._printJobs?.size)throw new Error("A print check is already running");this._printJobs??=new Map;let r=new AbortController,n=()=>r.abort();o.signal?.addEventListener("abort",n,{once:!0}),this._aborters.add(r),this._printJobs.set(r,new Set(i.map(u=>u.model)));let d,l=0,c,m=()=>d?.cancel?.();r.signal.addEventListener("abort",m);let _=()=>{if(r.signal.aborted||this.killsign)throw new DOMException("Print check cancelled","AbortError");if(i.some(({model:u,geometry:p,signature:y})=>this._models.get(u.id)!==u||u.geometry!==p||ge(u)!==y))throw new Error("Model changed. Run the print check again.")},w=u=>{_(),o.on_progress?.(u)},f=[];try{this._printCache=null;let u=i.length*256*1024;K(this.limits,this._occupiedMemory()+u,"print report"),l=u,this._printBytes=(this._printBytes||0)+l;for(let A=0;A<i.length;A++){_();let{model:k,geometry:E}=i[A],x=yo(E.triangles),C=this._occupiedMemory();K(this.limits,C+x,"print check"),this._printBytes+=x,l+=x;let I=new Float32Array(E.positions.length);for(let F=0;F<I.length;F+=262144)_(),I.set(E.positions.subarray(F,F+262144),F),F+262144<I.length&&await new Promise(z=>setTimeout(z,0));let v=e.apply_transforms?k:{...k,x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,center:[0,0,0],scaleVector:[k.unitScale,k.unitScale,k.unitScale]};w({phase:"Starting",model_id:k.id,model_index:A,total_models:i.length}),d=this._pool.run({kind:"print-check",model:{id:k.id,name:k.orig_filename||k.filename||`Model ${k.id}`,positions:I,matrix:X(v),source_units:E.units||null},options:e,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-C}},F=>{try{w({...F,model_id:k.id,model_index:A,total_models:i.length})}catch(z){c=z,r.abort()}},[I.buffer]);try{f.push(await d)}catch(F){throw c||F}finally{d=null,this._printBytes-=x,l-=x}}_();let p={version:1,model_ids:t.slice(),settings:e,models:f,summary:f.some(A=>A.summary==="issues_found")?"issues_found":f.some(A=>A.summary==="incomplete")?"incomplete":"no_issues_found",scope:"Each model checked separately; no cross-model union or collision analysis.",estimated:!!e.profile},y={viewer:new WeakRef(this),models:i.map(({model:A,geometry:k,signature:E})=>({model:new WeakRef(A),geometry:new WeakRef(k),signature:E}))};Re.set(p,y);let g=structuredClone(p);return Re.set(g,y),JSON.stringify(g).length<2*1024*1024&&this._occupiedMemory()+4*1024**2<=this.limits.max_memory_bytes&&(this._printCache={key:s,report:g}),w({phase:"Complete",total_models:i.length}),p}finally{d?.cancel?.(),this._printBytes=(this._printBytes||0)-l,o.signal?.removeEventListener("abort",n),this._aborters.delete(r),this._printJobs.delete(r)}}function vo(o,{issue:e,model_id:t=o.model_ids[0]}={}){if(!et(this,o))throw new Error("Print report is stale. Run the check again.");let i=o.models.find(l=>l.model_id===t),s=i?.checks.find(l=>l.code===e);if(!s)throw new Error("Unknown print check issue");let r=this._model(t),n=r.geometry.positions,d=[];if(s.edge_vertices?.length)d.push(...s.edge_vertices.slice(0,1536));else for(let l of s.triangle_ids.slice(0,128))if(!(!Number.isInteger(l)||l<0||l>=r.geometry.triangles))for(let c=0;c<3;c++)d.push(...n.subarray(l*9+c*3,l*9+c*3+3),...n.subarray(l*9+(c+1)%3*3,l*9+(c+1)%3*3+3));if(d.some(l=>!Number.isFinite(l)))throw new Error("Invalid print highlight");return this.renderer.setDiagnostic(d.length?{model:r,positions:new Float32Array(d),signature:ge(r)}:null),this._invalidate(),d.length/6}function St(o,e){return`${String(o||"scene").split(/[\\/]/).pop().split(/[?#]/)[0].replace(/[<>:"|*\x00-\x1f\x7f]/g,"_").replace(/\.(?:stl|obj|3mf|step|stp|iges|igs|zip|vsb|vsj)$/i,"").replace(/[. ]+$/g,"").slice(0,160)||"scene"}.${e}`}function wo(o={}){let e={stp:"step",igs:"iges"},t=String(o.format||"").toLowerCase(),i=e[t]||t;if(!["stl","obj","3mf","step","iges"].includes(i))throw new Error("Export format must be stl, obj, 3mf, step or iges");let s=o.units??"mm";if(!["mm","inch"].includes(s))throw new Error("Export units must be mm or inch");for(let d of["apply_transforms","colors"])if(o[d]!==void 0&&typeof o[d]!="boolean")throw new Error(`${d} must be a boolean`);if(o.on_progress!==void 0&&typeof o.on_progress!="function")throw new Error("on_progress must be a function");let r=o.colors!==!1&&["obj","3mf"].includes(i),n=i==="obj"&&r?"zip":i;return{format:i,units:s,colors:r,apply_transforms:o.apply_transforms!==!1,filename:St(o.filename,n)}}function xo(o,e){let t=o.reduce((s,r)=>s+r.geometry.triangles,0);return 67108864+t*{stl:192,obj:2400,"3mf":3e3,step:12e3,iges:4e3}[e.format]+o.length*4096}async function Mo(o,e,t=()=>{},i){let s,r=0;if(o instanceof ArrayBuffer){if(o.byteLength>e)throw new Error("File size limit exceeded");return t(o.byteLength,o.byteLength),o}if(o instanceof Blob){if(r=o.size,r>e)throw new Error("File size limit exceeded");if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");t(0,r);let _=await o.arrayBuffer();if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");return t(r,r),_}else{let _=new URL(String(o),globalThis.location?.href);if(!["http:","https:","blob:"].includes(_.protocol))throw new Error("Unsupported file URL protocol");let w=await fetch(_,{signal:i,credentials:"same-origin"});if(!w.ok)throw new Error(`HTTP ${w.status}: ${_.pathname}`);r=Number(w.headers.get("content-length"))||0,s=w.body}if(r>e)throw new Error("File size limit exceeded");if(!s)throw new Error("Empty response body");let n=s.getReader(),d=[],l=0;try{for(;;){if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");let{done:_,value:w}=await n.read();if(_)break;if(l+=w.length,l>e)throw new Error("File size limit exceeded");d.push(w),t(l,Math.max(r,l))}}catch(_){throw await n.cancel().catch(()=>{}),_}finally{n.releaseLock()}let c=new Uint8Array(l),m=0;for(let _ of d)c.set(_,m),m+=_.length;return t(l,l),c.buffer}function Eo(o,e){if(!o?.isMesh&&!o?.geometry)throw new Error("Expected a Three.js mesh");if(o.isSkinnedMesh||o.isInstancedMesh||o.children?.length||o.morphTargetInfluences?.some(Boolean))throw new Error("Only a static standalone Three.js mesh is supported");let t=o.material;if(Array.isArray(t)||t?.isShaderMaterial||t?.map)throw new Error("Multiple, textured or shader materials need an application adapter");let i=o.geometry,s,r=null;if(i?.attributes?.position){let n=i.attributes.position,d=i.index,l=d?.count??n.count;if(l%3||l/3>e.max_triangles)throw new Error("Invalid or oversized mesh");s=new Float32Array(l*3),i.attributes.color&&(r=new Float32Array(l*3));for(let c=0;c<l;c++){let m=d?d.getX(c):c;for(let[_,w]of["getX","getY","getZ"].entries())s[c*3+_]=n[w](m),r&&(r[c*3+_]=i.attributes.color[w](m))}}else if(i?.vertices&&i?.faces){if(i.faces.length>e.max_triangles)throw new Error("Triangle limit exceeded");s=new Float32Array(i.faces.length*9),t?.vertexColors&&(r=new Float32Array(s.length)),i.faces.forEach((n,d)=>{for(let[l,c]of[n.a,n.b,n.c].entries()){let m=i.vertices[c];if(!m)throw new Error("Invalid mesh index");s.set([m.x,m.y,m.z],d*9+l*3),r&&r.set([n.color.r,n.color.g,n.color.b],d*9+l*3)}})}else throw new Error("Unsupported Three.js geometry");return Xe(s,r,e)}function ko(o,e){if(!e?.Mesh||!e?.BufferGeometry||!e?.BufferAttribute)throw new Error("get_model_mesh requires options.three or globalThis.THREE; use get_model_data for native geometry");let t=o.geometry,i=new e.BufferGeometry,s=t.positions.slice(),r=o.scaleVector,n=o.center;for(let m=0;m<s.length;m++)s[m]=(s[m]-n[m%3])*r[m%3];let d=(m,_)=>{let w=new e.BufferAttribute(_,3);i.setAttribute?i.setAttribute(m,w):i.addAttribute(m,w)};d("position",s),t.colors&&!o.color&&d("color",t.colors.slice()),i.computeVertexNormals();let l=new e.MeshLambertMaterial({color:o.color==="transparent"?"#909090":o.color||(t.colors?"#ffffff":"#909090"),vertexColors:t.colors&&!o.color?e.VertexColors??!0:!1,side:e.DoubleSide,transparent:o.opacity<1||o.color==="transparent",opacity:o.color==="transparent"?0:o.opacity,wireframe:o.display==="wireframe",flatShading:o.display!=="smooth"}),c=new e.Mesh(i,l);return c.position.set(o.x,o.y,o.z),c.rotation.set(o.rotationx,o.rotationy,o.rotationz),c}var Le=["x","y","z","rotationx","rotationy","rotationz","scalex","scaley","scalez"],Wo=["id","filename","remote_url","remote_format","orig_filename","local_file","mesh","data","x","y","z","display","color","scale","scalex","scaley","scalez","rotationx","rotationy","rotationz","opacity","view_edges","smooth_shading","section","units","animation"],Q=(o,e)=>{if(typeof o!="number"||!Number.isFinite(o))throw new Error(`${e} must be a finite number`);return o},me=(o,e)=>{if(Q(o,e),o<=0)throw new Error(`${e} must be positive`);return o},At=(o,e)=>[Q(o.x,`${e}.x`),Q(o.y,`${e}.y`),Q(o.z,`${e}.z`)],tt=o=>({x:o[0],y:o[1],z:o[2]}),ot=o=>String(o||"model.stl").split(/[\\/]/).pop().split(/[?#]/)[0];function Ce(o,e){let t=document.createElement("a"),i=URL.createObjectURL(o);t.href=i,t.download=ot(e),t.click(),setTimeout(()=>URL.revokeObjectURL(i),1e3)}function So(o){let e={};for(let t of Wo)Object.hasOwn(o,t)&&(e[t]=o[t]);return e}var _e=class o{static version="2.0.0-rc.3";static workerURL=null;constructor(e,t={}){if(!e?.appendChild)throw new TypeError("StlViewer requires a DOM container");this.parent_element=e,this.options={...t},this.error=null,this.ready=!1,this.killsign=!1,this.models=[],this.models_ref=Object.create(null),this.models_count=0,this.max_model_id=-1,this.load_status=Object.create(null),this.load_session=0,this._models=new Map,this._pending=new Map,this._jobs=new Map,this._derived=new Set,this._memoryReservations=new Map,this._exportBytes=0,this._loadTail=Promise.resolve(),this._sceneLoads=new Set,this._aborters=new Set,this._generation=0,this._sessionActive=!1,this._frame=0,this._lastTime=0,this.grid=null,this._gridAuto=!1,this.camera={position:[0,0,100],target:[0,0,0],up:[0,1,0],near:.01,far:1e4},this.when_ready=new Promise((i,s)=>{this._readyResolve=i,this._readyReject=s}),this.when_ready.catch(()=>{});for(let i of["ready_callback","all_loaded_callback","model_loaded_callback","load_error_callback","loading_progress_callback","on_model_drop"])this[i]=t[i]??null;this.onmousedown_callback=t.on_model_mousedown??t.on_model_mouseclick??null,queueMicrotask(()=>this._initialize())}_initialize(){if(!this.killsign)try{let e=this.options;if(this.limits=ao(e),this.bg_color=e.bg_color??e.bgcolor??"transparent",this.bg_color!=="transparent"&&Y(this.bg_color),e.worker_count!==void 0&&(!Number.isInteger(e.worker_count)||e.worker_count<1||e.worker_count>4))throw new Error("worker_count must be 1\u20134");if(e.worker_timeout!==void 0&&me(e.worker_timeout,"worker_timeout"),e.pixel_ratio!==void 0&&me(e.pixel_ratio,"pixel_ratio"),e.controls!==void 0&&e.controls!==0&&e.controls!==1)throw new Error("controls must be 0 or 1");this.center_models=e.center_models??!0,this.auto_resize=e.auto_resize??!0,this.auto_rotate=e.auto_rotate??!1,this.mouse_zoom=e.mouse_zoom??!0,this.send_no_model_click_event=e.send_no_model_click_event??!1,this.zoom=e.zoom??-1,this.canvas_width=e.width??e.canvas_width??"100%",this.canvas_height=e.height??e.canvas_height??"100%",this.canvas=document.createElement("canvas"),this.canvas.style.cssText="display:block;touch-action:none;outline-offset:3px;",this._setSizeStyle(),this.canvas.tabIndex=0,this.canvas.setAttribute("aria-label","3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."),this.parent_element.appendChild(this.canvas),this.renderer=new Ze(this.canvas,()=>this._message("Graphics interrupted. Waiting for the browser to restore rendering\u2026"),()=>{this._removeMessage(),this._invalidate()},i=>this._error(i)),this.controls=new Qe(this.canvas,this.camera,()=>this._invalidate(),(i,s)=>this._pick(i,s),e.controls??0),this.controls.noZoom=!this.mouse_zoom;let t=e.worker_url??o.workerURL;if(e.worker_url===void 0&&typeof e.load_three_files=="string"&&(t=new URL("viewstl.worker.js",new URL(e.load_three_files||"./",document.baseURI)).href),!t&&!e.worker_factory)throw new Error("Specify worker_url when using the CommonJS build");this._pool=new $e(t,{count:e.worker_count??2,timeout:e.worker_timeout??12e4,factory:e.worker_factory}),this._drop=i=>{i.preventDefault();let s=[...i.dataTransfer.files];for(let r of s)this._callback("on_model_drop",r.name),this.add_model({local_file:r})},this._drag=i=>i.preventDefault(),this.set_drag_and_drop(e.allow_drag_and_drop??!0),this.set_auto_resize(this.auto_resize),this.do_resize(),this.camera.position=[e.camerax??0,e.cameray??0,this.zoom>=0?Math.max(this.zoom,1e-6):e.cameraz??100],e.camera_state&&(this.set_camera_state(e.camera_state),this._restoreCamera=e.camera_state),e.grid&&this.set_grid(!0),this._key=i=>{if(!this.controls.enabled)return;let s={ArrowLeft:()=>this.controls.orbit(-.12,0),ArrowRight:()=>this.controls.orbit(.12,0),ArrowUp:()=>this.controls.orbit(0,-.12),ArrowDown:()=>this.controls.orbit(0,.12),"+":()=>this.controls.zoom(.9),"-":()=>this.controls.zoom(1.1),f:()=>this.set_auto_zoom()};s[i.key]&&(i.preventDefault(),s[i.key]())},this.canvas.addEventListener("keydown",this._key),this.ready=!0,this._readyResolve(this),this._callback("ready_callback"),e.models&&this.add_models(e.models),this._invalidate()}catch(e){this.ready=!1,this._readyReject(e),this._error(e),this._message(e.message),this._pool?.dispose(),this.controls?.dispose(),this.renderer?.dispose(),this._resizeObserver?.disconnect()}}_callback(e,...t){let i=this[e];if(typeof i=="function")try{i(...t)}catch(s){console.error(`ViewSTL ${e} callback failed`,s)}}_error(e){return this.error=e?.message??String(e),this._callback("load_error_callback",this.error),null}_message(e){this._removeMessage(),this._messageElement=document.createElement("p"),this._messageElement.setAttribute("role","status"),this._messageElement.textContent=e,this.parent_element.appendChild(this._messageElement)}_removeMessage(){this._messageElement?.remove(),this._messageElement=null}_setSizeStyle(){this.canvas.style.width=typeof this.canvas_width=="number"?`${this.canvas_width}px`:this.canvas_width,this.canvas.style.height=typeof this.canvas_height=="number"?`${this.canvas_height}px`:this.canvas_height}_guard(e){if(this.killsign)return null;try{return e()}catch(t){return this._error(t)}}_model(e){let t=this._models.get(Number(e));if(!t)throw new Error(`Model id not found: ${e}`);return t}_invalidate(){let e=this.renderer?.diagnostic;e&&ge(e.model)!==e.signature&&this.renderer.setDiagnostic(null),!(this.killsign||!this.renderer||this.renderer.lost||this._frame)&&(this._frame=requestAnimationFrame(t=>{this._frame=0,this._render(t)}))}_render(e){let t=this._lastTime?Math.min((e-this._lastTime)/1e3,.1):0;this._lastTime=e;let i=!1;for(let s of this._models.values())s.geometry&&s._animation&&(this._animate(s,e),i||=!!s._animation);this.auto_rotate&&this.controls.enabled&&this.controls.orbit(t*.3,0),this._clip();try{this.renderer.draw([...this._models.values()].filter(s=>!s._sceneImportPending),this.camera,this.bg_color)}catch(s){this.renderer.clear(),this._error(s);return}i||this.auto_rotate&&this.controls.enabled?this._invalidate():this._lastTime=0}_bounds(){let e=[1/0,1/0,1/0],t=[-1/0,-1/0,-1/0],i=0;for(let s of this._models.values())if(s.geometry&&!s._sceneImportPending){i++;let{bounds:r}=s.geometry,n=X(s);for(let d=0;d<8;d++){let l=le(n,[0,1,2].map(c=>r[d&1<<c?"max":"min"][c]));for(let c=0;c<3;c++)e[c]=Math.min(e[c],l[c]),t[c]=Math.max(t[c],l[c])}}return i?{min:e,max:t,center:e.map((s,r)=>(s+t[r])/2),radius:Math.max(Math.hypot(...V(t,e))/2,1e-6)}:null}_clip(){let e=this._bounds(),t=Math.hypot(...V(this.camera.position,e?.center??this.camera.target)),i=e?.radius??10;this.camera.near=Math.max(1e-6,Math.min(i*.001,Math.max(t-i,1e-6)*.1)),this.camera.far=Math.max(this.camera.near*100,t+i*4,100)}_progress(e,t){!this._models.has(e)||this.killsign||(this.load_status[e]=t,this._callback("loading_progress_callback",this.load_status,this.load_session))}add_models(e){return this._guard(()=>{if(!Array.isArray(e))throw new Error("models must be an array");for(let t of e)this.add_model(t)})}add_model(e){return this._guard(()=>{if(!e||typeof e!="object")throw new Error("Invalid model");let t=So(e),i=t.filename??t.local_file?.name??"model.stl";if(/\.vsj(?:[?#]|$)/i.test(i))return this.load_vsj(t.local_file??i);if(/\.vsb(?:[?#]|$)/i.test(i))return this.load_vsb(t.local_file??i);let s=t.id===void 0||t.id===-1?this.max_model_id+1:t.id;if(!Number.isSafeInteger(s)||s<0||this._models.has(s))throw new Error(`Invalid or duplicate model id: ${s}`);let r={...t,id:s,filename:t.filename??t.local_file?.name??"",x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,scalex:1,scaley:1,scalez:1,opacity:1,units:"mm",display:"flat",view_edges:!1,color:null,unitScale:1,center:[0,0,0],geometry:null};for(let l of Le)r[l]=t[l]??(l.startsWith("scale")?t.scale??1:0);for(let l of Le)Q(r[l],l),l.startsWith("scale")&&me(r[l],l);if(r.opacity=t.opacity??t.mesh?.material?.opacity??1,Q(r.opacity,"opacity"),r.opacity<0||r.opacity>1)throw new Error("Opacity must be between 0 and 1");if(r.display=t.display??"flat",Object.assign(r,He(r.display,t.smooth_shading??r.display==="smooth")),r.section=qe(t.section),r.units=t.units??"mm",!["mm","inch"].includes(r.units))throw new Error("Invalid units");r.color=t.color??(t.mesh?.material?.color?.getHexString?"#"+t.mesh.material.color.getHexString():null),r.color&&r.color!=="transparent"&&Y(r.color),r.view_edges=!!t.view_edges,Object.defineProperty(r,"scaleVector",{get:()=>[r.scalex*r.unitScale,r.scaley*r.unitScale,r.scalez*r.unitScale]}),this.max_model_id=Math.max(s,this.max_model_id),this._models.set(s,r),this.models_ref[s]=this.models.length,this.models.push(r),this.models_count=this._models.size,this._sessionActive=!0,this.load_status[s]={loaded:0,total:0};let n=this._generation,d=this._loadModel(r,t,n).then(()=>{n!==this._generation||this._models.get(s)!==r||(this._pending.delete(s),this._finishSession(),this._callback("model_loaded_callback",s))}).catch(l=>{n===this._generation&&this._models.get(s)===r&&(this._pending.delete(s),this._deleteModel(r),l.name!=="AbortError"&&this._error(l),this._finishSession())}).finally(()=>{this._jobs.delete(r)});this._pending.set(s,d)})}_occupiedMemory(e){let t=(this._exportBytes||0)+(this._sceneBytes||0)+(this._printBytes||0)+(this._printCache?4194304:0);for(let i of this._models.values())if(i!==e){let s=this._memoryReservations?.get(i);t+=Math.max(ro(i.geometry),[...s?.values()||[]].reduce((r,n)=>r+n.bytes,0))}return t}_reserveMemory(e,t,i="derive"){this._memoryReservations??=new Map;let s=this._memoryReservations.get(e);s||this._memoryReservations.set(e,s=new Map);let r={};return s.set(r,{bytes:t,kind:i}),()=>{s.delete(r),!s.size&&this._memoryReservations.get(e)===s&&this._memoryReservations.delete(e)}}async _loadModel(e,t,i){let s=()=>{};if(this.options.max_memory_bytes!==void 0){let r=this._loadTail;this._loadTail=new Promise(n=>{s=n}),await r}try{await this._readModel(e,t,i)}finally{s()}}async _readModel(e,t,i){if(await this.when_ready,this.killsign||i!==this._generation||this._models.get(e.id)!==e)return;let s=Math.max(1,this.limits.max_memory_bytes-this._occupiedMemory(e)),r={...this.limits,max_memory_bytes:s};K(r,67108864,"scene");let n=this.limits.max_memory_bytes<Number.MAX_SAFE_INTEGER?this._reserveMemory(e,s-67108864,"parse"):()=>{};try{let d=wt(e);if(t.mesh)e.geometry=Eo(t.mesh,r);else if(t.data)e.geometry=Xe(new Float32Array(t.data.positions),t.data.colors?new Float32Array(t.data.colors):null,r);else{let l=t.local_file??t.filename??t.remote_url;if(!l)throw new Error("A filename, local_file or mesh is required");let c;if(t.remote_url){let _=new URL(t.remote_url);if(!["https:","http:"].includes(_.protocol)||_.username||_.password)throw new Error("Use a public HTTP or HTTPS file URL without login credentials.");if(!this.options.url_import_endpoint)throw new Error("Configure url_import_endpoint to import remote URLs through a relay.");if(c=new URL(this.options.url_import_endpoint,document.baseURI).href,new URL(c).origin!==location.origin)throw new Error("The URL relay must use the same origin as this page.")}let m=this._pool.run({kind:"parse",remote:t.remote_url,format:t.remote_format,relay:c,source:typeof l=="string"?new URL(l,document.baseURI).href:l,name:e.filename,limits:r,features:d.smooth||d.edges||d.wire?d:null},_=>this._progress(e.id,_));this._jobs.set(e,m),e.geometry=await m,t.remote_url&&(e.filename=e.geometry.source_name,delete e.remote_url,delete e.remote_format)}if(this.killsign||i!==this._generation||this._models.get(e.id)!==e){e.geometry=null;return}this.center_models&&(e.center=e.geometry.bounds.min.map((l,c)=>(l+e.geometry.bounds.max[c])/2)),(t.mesh||t.data)&&(d.smooth||d.edges||d.wire)&&await this._derive(e,d),t.animation&&this.animate_model(e.id,t.animation),delete e.mesh,delete e.data,this._invalidate()}finally{n()}}_finishSession(){this.killsign||this._pending.size||this._sceneLoads.size||!this._sessionActive||(this._sessionActive=!1,this._restoreCamera?(this.set_camera_state(this._restoreCamera),this._restoreCamera=null):this.zoom===-1&&this.set_auto_zoom(),this._gridAuto&&this.set_grid(!0),this.load_session++,this._callback("all_loaded_callback"),this._invalidate())}_deleteModel(e){for(let[t,i]of this._printJobs||[])i.has(e)&&t.abort();this._printCache=null,this.renderer?.selection===e&&this.renderer.setSelection(null),this.renderer?.remove(e),this._jobs.get(e)?.cancel?.();for(let t of e._deriveJobs||[])t.cancel?.();this._models.delete(e.id),this._memoryReservations.delete(e),this.models[this.models_ref[e.id]]=null,delete this.models_ref[e.id],delete this.load_status[e.id],this.models_count=this._models.size,this._invalidate()}remove_model(e){return this._guard(()=>{let t=this._model(e);this._deleteModel(t),this._pending.delete(t.id),this._finishSession()})}clean(){if(!this.killsign){this._generation++,this._printCache=null,this._pool?.clear();for(let e of this._aborters)e.abort();this._aborters.clear(),this._models.clear(),this._pending.clear(),this._jobs.clear(),this._derived.clear(),this._memoryReservations.clear(),this._sceneLoads.clear(),this.models=[],this.models_ref=Object.create(null),this.models_count=0,this.max_model_id=-1,this.load_status=Object.create(null),this._sessionActive=!1,this.renderer?.clear(),this._restoreCamera=null,this._invalidate()}}dispose(){if(!this.killsign){this.clean(),this.killsign=!0,this.ready=!1,this._readyReject(new Error("Viewer disposed")),cancelAnimationFrame(this._frame),this._frame=0,this._pool?.dispose(),this.controls?.dispose(),this._resizeObserver?.disconnect(),this._windowResize&&window.removeEventListener("resize",this._windowResize),this.renderer?.dispose(),this.canvas&&(this.canvas.removeEventListener("drop",this._drop),this.canvas.removeEventListener("dragover",this._drag),this.canvas.removeEventListener("keydown",this._key),this.canvas.remove()),this._removeMessage();for(let e of Object.keys(this))e.endsWith("_callback")&&(this[e]=null);this.options={}}}async when_idle(){for(await this.when_ready;this._pending.size||this._sceneLoads.size||this._derived.size;)await Promise.allSettled([...this._pending.values(),...this._sceneLoads,...this._derived]);return this}_derive(e,t){let i=this._occupiedMemory(e)+[...this._memoryReservations?.get(e)?.values()||[]].filter(m=>m.kind==="derive").reduce((m,_)=>m+_.bytes,0),s=no(e.geometry.triangles,{...t,smooth:t.smooth||!!e.geometry.smoothNormals,edges:t.edges||!!e.geometry.edges,wire:t.wire||!!e.geometry.wire});K(this.limits,i+s,"view mode");let r=this._reserveMemory(e,s-67108864),n=this._generation,d=e._deriveRevision=(e._deriveRevision||0)+1,l;try{let m=e.geometry.positions.slice();l=this._pool.run({kind:"derive",positions:m,features:t,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-i}},null,[m.buffer])}catch(m){throw r(),m}(e._deriveJobs??=new Set).add(l);let c=l.then(m=>!this.killsign&&n===this._generation&&d===e._deriveRevision&&this._models.get(e.id)===e?(Object.assign(e.geometry,m),this.renderer.remove(e),this._invalidate(),!0):!1).catch(m=>{m.name!=="AbortError"&&n===this._generation&&this._error(m)}).finally(()=>{r(),e._deriveJobs.delete(l),this._derived.delete(c)});return this._derived.add(c),c}set_view(e,t){return this._guard(()=>{let i=this._model(e),s=i._requestedView||i,r=He(t.display??s.display,t.smooth_shading??s.smooth_shading),n=wt({...i,...r}),d=i.geometry,c=d&&(n.smooth&&!d.smoothNormals||n.wire&&!d.wire||n.edges&&(!d.edges||d.edgeAngle!==n.edgeAngle))?this._derive(i,n):null,m=(i._viewRevision||0)+1;i._viewRevision=m,i._requestedView=r;let _=w=>{this._models.get(e)!==i||i._viewRevision!==m||(w&&Object.assign(i,r),delete i._requestedView,this._invalidate())};if(!c)_(!0);else{let w=c.then(_).finally(()=>this._derived.delete(w));this._derived.add(w)}})}set_display(e,t){return this.set_view(e,{display:t,...t==="flat"||t==="smooth"?{smooth_shading:t==="smooth"}:{}})}set_smooth(e,t){return this._guard(()=>{let i=this._model(e),s=i._requestedView||i;return this.set_view(e,{display:s.display==="smooth"?"flat":s.display,smooth_shading:t})})}set_section(e,t){return this._guard(()=>{this._model(e).section=qe(t),this._invalidate()})}set_color(e,t){return this._guard(()=>{t!==null&&t!=="transparent"&&Y(t);let i=this._model(e);i.color=t,this._invalidate()})}set_opacity(e,t){return this._guard(()=>{if(Q(t,"opacity"),t<0||t>1)throw new Error("Opacity must be between 0 and 1");this._model(e).opacity=t,this._invalidate()})}set_position(e,t,i,s){return this._guard(()=>{let r=this._model(e);[t,i,s].forEach((n,d)=>{n!==void 0&&(r[["x","y","z"][d]]=Q(n,"position"))}),this._invalidate()})}set_scale(e,t,i=t,s=t,r=!1){return this._guard(()=>{let n=this._model(e);if([t,i,s].forEach(d=>me(d,"scale")),r){if(t/n.scalex!==i/n.scaley||t/n.scalex!==s/n.scalez)throw new Error("keep_prev_scale requires a uniform geometry factor");n.unitScale*=t/n.scalex}else[n.scalex,n.scaley,n.scalez]=[t,i,s];n._stats=null,this._invalidate()})}set_selection(e){return this._guard(()=>{this.renderer?.setSelection(e===null?null:this._model(e)),this._invalidate()})}set_rotation(e,t,i,s,r=!1){return this._guard(()=>{let n=this._model(e);[t,i,s].forEach((d,l)=>{let c=["rotationx","rotationy","rotationz"][l];d!==void 0&&(n[c]=Q(d,"rotation")+(r?n[c]:0))}),this._invalidate()})}rotate(e,t,i,s){return this.set_rotation(e,t,i,s,!0)}set_edges(e,t){return this._guard(()=>{let i=this._model(e);i.view_edges=!!t,t&&i.geometry&&!i.geometry.edges&&this._derive(i,{edges:!0}),this._invalidate()})}set_grid(e,t,i=10){return this._guard(()=>{if(this._gridAuto=!!e&&t===void 0,!e)this.grid=null,this.renderer?.setGrid(null);else{let s=this._bounds();if(t??=s&&Math.max(...V(s.max,s.min))*1.2||10,me(t,"grid size"),!Number.isInteger(i)||i<1||i>1e3)throw new Error("Grid divisions must be 1\u20131000");this.grid={size:t,divisions:i},this.renderer?.setGrid(this.grid)}this._invalidate()})}set_infinite_grid(e,t=1,i=0){return this._guard(()=>{me(t,"grid spacing"),Q(i,"grid height"),this._gridAuto=!1,this.grid=e?{infinite:!0,spacing:t,height:i}:null,this.renderer?.setGrid(this.grid),this._invalidate()})}set_model_units(e,t,i=!1){return this._guard(()=>{if(!["mm","inch"].includes(t))throw new Error("Units must be mm or inch");let s=this._model(e);i&&s.units!==t&&(s.unitScale*=t==="inch"?25.4:1/25.4),s.units=t,s._stats=null,this._invalidate()})}set_bg_color(e){return this._guard(()=>{e!=="transparent"&&Y(e),this.bg_color=e,this._invalidate()})}set_center_models(e){this.center_models=!!e}set_zoom(e=-1){return this._guard(()=>{if(Q(e,"zoom"),e===-1)this.zoom=-1,this.set_auto_zoom();else{if(e<0)throw new Error("Zoom must be non-negative or -1");this.zoom=e,this.camera.position[2]=Math.max(e,1e-6),this._invalidate()}})}set_auto_zoom(){let e=this._bounds();if(!e)return;this.zoom=-1;let t=(this.canvas?.clientWidth||1)/(this.canvas?.clientHeight||1),i=Math.min(Math.PI/8,Math.atan(Math.tan(Math.PI/8)*t)),s=H(V(this.camera.position,this.camera.target));this.camera.target=e.center,this.camera.position=re(e.center,oe(Math.hypot(...s)?s:[0,0,1],e.radius/Math.sin(i)*1.08)),this._invalidate()}set_auto_rotate(e){this.auto_rotate=!!e,this._invalidate()}set_mouse_zoom(e){this.mouse_zoom=!!e,this.controls&&(this.controls.noZoom=!e)}set_auto_resize(e){this.auto_resize=!!e,this._resizeObserver?.disconnect(),this._windowResize&&window.removeEventListener("resize",this._windowResize),!(!e||!this.canvas)&&(typeof ResizeObserver<"u"&&(this._resizeObserver=new ResizeObserver(()=>this.do_resize()),this._resizeObserver.observe(this.parent_element)),this._windowResize=()=>this.do_resize(),window.addEventListener("resize",this._windowResize))}do_resize(){if(!this.renderer||this.killsign)return;let e=this.canvas.getBoundingClientRect();this.renderer.resize(e.width||this.parent_element.clientWidth||300,e.height||this.parent_element.clientHeight||150,this.options.pixel_ratio??Math.min(globalThis.devicePixelRatio||1,2)),this._invalidate()}set_on_model_mousedown(e){this.onmousedown_callback=e}set_on_model_mouseclick(e){this.set_on_model_mousedown(e)}set_drag_and_drop(e){this.allow_drag_and_drop=!!e,this.canvas&&(this.canvas.removeEventListener("drop",this._drop),this.canvas.removeEventListener("dragover",this._drag),e&&(this.canvas.addEventListener("drop",this._drop),this.canvas.addEventListener("dragover",this._drag)))}get_camera_state(){return{position:tt(this.camera.position),up:tt(this.camera.up),target:tt(this.camera.target)}}set_camera_state(e){return this._guard(()=>{let t=e.position?At(e.position,"position"):this.camera.position,i=e.up?At(e.up,"up"):this.camera.up,s=e.target?At(e.target,"target"):this.camera.target;if(!Math.hypot(...i)||!Math.hypot(...V(t,s)))throw new Error("Degenerate camera state");this.camera.position=t,this.camera.up=H(i),this.camera.target=s,this._invalidate()})}set_camera(e,t,i){return this.set_camera_state({position:{x:e??this.camera.position[0],y:t??this.camera.position[1],z:i??this.camera.position[2]}})}pick_model(e,t){if(!this.ready||this.killsign||!Number.isFinite(e)||!Number.isFinite(t))return null;let i=this.canvas.getBoundingClientRect();if(!i.width||!i.height||e<i.left||e>i.right||t<i.top||t>i.bottom)return null;let s=fe(Ye(this.camera,i.width/i.height)),r=le(s,[(e-i.left)/i.width*2-1,1-(t-i.top)/i.height*2,1]),n=H(V(r,this.camera.position)),d=null,l=1/0;for(let c of this._models.values())if(c.geometry&&c.opacity>0&&c.color!=="transparent"){let m=fe(X(c)),_=lo(c.geometry,le(m,this.camera.position),le(m,n,0),We(c.geometry.bounds,c.section));_<l&&(l=_,d=c.id)}return d===null?null:{id:d,distance:l}}_pick(e,t){if(!this.onmousedown_callback)return;let i=this.pick_model(e.clientX,e.clientY);(i||this.send_no_model_click_event)&&this._callback("onmousedown_callback",i?.id??null,e,i?.distance??0,t)}get_model_info(e){return this._guard(()=>{let t=this._model(e),i=t.geometry;if(!i)return null;let s=t.scaleVector,r=s.join(",");(!t._stats||t._stats.key!==r)&&(t._stats={key:r,...co(i,s)});let n=t.units==="inch"?1/25.4:1;return{name:t.filename,orig_filename:t.orig_filename??null,position:{x:t.x,y:t.y,z:t.z},dims:tt(i.bounds.min.map((d,l)=>(i.bounds.max[l]-d)*s[l])),rotation:{x:t.rotationx,y:t.rotationy,z:t.rotationz},display:t.display,smooth_shading:t.smooth_shading,section:t.section?{...t.section}:null,color:t.color??(i.colors?"#ffffff":"#909090"),scale:{x:t.scalex,y:t.scaley,z:t.scalez},volume:t._stats.volume*n**3,area:t._stats.area*n**2,triangles:i.triangles,bounds:{min:[...i.bounds.min],max:[...i.bounds.max]},has_colors:!!i.colors,source_units:i.units||null,units:t.units,opacity:t.opacity,file_bytes:i.file_bytes??null}})}get_model_data(e){return this._guard(()=>{let t=this._model(e);return t.geometry?{positions:t.geometry.positions.slice(),colors:t.geometry.colors?.slice()??null}:null})}get_model_mesh(e){return this._guard(()=>{let t=this._model(e);return t.geometry?ko(t,this.options.three??globalThis.THREE):null})}get_stl_bin(e){return this._guard(()=>{let t=this._model(e);return t.geometry?ho(t.geometry,t.scaleVector,t.center):null})}check_printability(e={}){return bo.call(this,e)}is_printability_current(e){return et(this,e)}show_printability(e,t){return vo.call(this,e,t)}clear_printability(){this.renderer?.setDiagnostic(null),this._invalidate()}async export_models(e={}){let t=wo(e),i=e.signal,s=()=>{throw new DOMException("Export cancelled","AbortError")};if(i?.aborted&&s(),await this.when_ready,i?.aborted&&s(),this.killsign)throw new Error("Viewer disposed");let r=e.model_ids;if(r==="selected"){let f=this.renderer?.selection;r=f?[f.id]:[]}else r===void 0&&(r=[...this._models.keys()]);if(!Array.isArray(r)||r.some(f=>!Number.isSafeInteger(f))||new Set(r).size!==r.length)throw new Error("model_ids must be an array of unique model IDs or 'selected'");if(!r.length)throw new Error("No models to export");let n=r.map(f=>{let u=this._model(f);if(!u.geometry)throw new Error(`Model ${f} has not finished loading`);return u});if(n.reduce((f,u)=>f+u.geometry.triangles,0)>this.limits.max_triangles)throw new Error("Export triangle limit exceeded");let l=xo(n,t),c=this._occupiedMemory();K(this.limits,c+l,"export"),this._exportBytes=(this._exportBytes||0)+l;let m,_,w;try{let f=n.map(p=>{let y=t.apply_transforms?p:{...p,center:[0,0,0],x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,scaleVector:[p.unitScale,p.unitScale,p.unitScale]},g=X(y),A=t.units==="inch"?1/25.4:1;for(let k=0;k<4;k++)for(let E=0;E<3;E++)g[k*4+E]*=A;return{id:p.id,name:ot(p.filename),matrix:g,positions:p.geometry.positions.slice(),colors:t.colors?p.geometry.colors?.slice():null,color:t.colors&&p.color&&p.color!=="transparent"?Y(p.color):null,opacity:p.color==="transparent"?0:p.opacity}}),u=f.flatMap(p=>[p.positions.buffer,...p.colors?[p.colors.buffer]:[]]);m=this._pool.run({kind:"export",models:f,options:t,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-c}},p=>{try{e.on_progress?.(p)}catch(y){w=y,m.cancel()}},u),_=()=>m.cancel(),i?.addEventListener("abort",_,{once:!0}),i?.aborted&&_();try{return await m}catch(p){throw w||p}}finally{_&&i?.removeEventListener("abort",_),this._exportBytes-=l}}async download_models(e={}){let t=await this.export_models(e);if(e.signal?.aborted)throw new DOMException("Export cancelled","AbortError");return Ce(t.blob,t.filename),t}download_model(e,t){let i=this.get_stl_bin(e);i&&Ce(new Blob([i],{type:"model/stl"}),t??this._model(e).filename??"model.stl")}async export_scene(e={}){let t=String(e.format||"").toLowerCase();if(!["vsb","vsj"].includes(t))throw new Error("Scene format must be vsb or vsj");if(e.force_basename!==void 0&&typeof e.force_basename!="boolean")throw new Error("force_basename must be a boolean");if(e.on_progress!==void 0&&typeof e.on_progress!="function")throw new Error("on_progress must be a function");let i=e.signal,s=()=>{if(i?.aborted)throw new DOMException("Export cancelled","AbortError")};if(s(),await this.when_ready,s(),this.killsign)throw new Error("Viewer disposed");if(this._pending.size||this._sceneLoads.size)throw new Error("Wait for models to finish opening before saving the scene");let r=[...this._models.values()];if(!r.length)throw new Error("No models to export");if(r.some(u=>!u.geometry))throw new Error("A model has not finished loading");if(r.length+(t==="vsb"?1:0)>this.limits.max_archive_entries)throw new Error("Scene model count limit exceeded");let n=r.reduce((u,p)=>u+p.geometry.triangles,0);if(n>this.limits.max_triangles)throw new Error("Scene triangle limit exceeded");let d=this.get_vsj(!0,!!e.force_basename,t==="vsb"),l=JSON.stringify(d),c=new Blob([l]).size;if(c>this.limits.max_file_bytes)throw new Error("Scene manifest size limit exceeded");if(t==="vsb"&&c+r.length*84+n*50>this.limits.max_uncompressed_bytes)throw new Error("Scene archive limit exceeded");let m=67108864+l.length*8+(t==="vsb"?n*512:0);K(this.limits,this._occupiedMemory()+m,"scene export"),this._exportBytes+=m;let _,w,f;try{e.on_progress?.({loaded:0,total:r.length,phase:"exporting"}),s();let u;if(t==="vsj")u=new Blob([l],{type:"application/json"}),e.on_progress?.({loaded:r.length,total:r.length,phase:"complete"});else{let p=r.map(y=>({id:y.id,triangles:y.geometry.triangles,positions:y.geometry.positions.slice(),colors:y.geometry.colors?.slice()||null,scale:y.scaleVector,center:[...y.center]}));_=this._pool.run({kind:"export-scene",scene:d,models:p,limits:this.limits},y=>{try{e.on_progress?.(y)}catch(g){f=g,_.cancel()}},p.flatMap(y=>[y.positions.buffer,...y.colors?[y.colors.buffer]:[]])),w=()=>_.cancel(),i?.addEventListener("abort",w,{once:!0}),i?.aborted&&w();try{u=await _}catch(y){throw f||y}}return s(),{blob:u,format:t,filename:St(e.filename,t),model_ids:r.map(p=>p.id),triangles:n,representation:"scene"}}finally{w&&i?.removeEventListener("abort",w),this._exportBytes-=m}}async download_scene(e={}){let t=await this.export_scene(e);if(e.signal?.aborted||this.killsign)throw new DOMException("Export cancelled","AbortError");return Ce(t.blob,t.filename),t}get_vsj(e=!1,t=!1,i=!1){let s={canvas_height:this.canvas_height,bg_color:this.bg_color,camera_state:this.get_camera_state(),auto_rotate:this.auto_rotate,mouse_zoom:this.mouse_zoom,auto_resize:this.auto_resize,center_models:this.center_models,models:[]};this.grid&&(s.grid=!0,this.grid.infinite&&(s.infinite_grid={spacing:this.grid.spacing,height:this.grid.height}));for(let r of this._models.values())if(r.geometry){let n={id:i?-1:r.id,filename:i?`${r.id}.stl`:t?ot(r.filename):r.filename,units:r.units,display:r.display,opacity:r.opacity,view_edges:r.view_edges,smooth_shading:r.smooth_shading,section:r.section?{...r.section}:null};i&&(n.orig_filename=ot(r.orig_filename||r.filename));for(let d of Le)d.startsWith("scale")||(n[d]=r[d]);i||(n.scalex=r.scalex*r.unitScale,n.scaley=r.scaley*r.unitScale,n.scalez=r.scalez*r.unitScale),r.color&&(n.color=r.color),r.animation&&(n.animation=structuredClone(r.animation)),s.models.push(n)}return e?s:JSON.stringify(s)}download_vsj(e="1.vsj"){Ce(new Blob([this.get_vsj()],{type:"application/json"}),e)}get_vsb(){return{vsj:this.get_vsj(!0,!0,!0),files:[...this._models.values()].filter(e=>e.geometry).map(e=>({id:e.id,bin:this.get_stl_bin(e.id)}))}}async get_vsb_blob(){await this.when_ready;let e=this.get_vsb(),t=this._pool.run({kind:"zip",scene:e,limits:this.limits},null,e.files.map(i=>i.bin));return new Blob([await t],{type:"application/zip"})}download_vsb(e="1.vsb"){return this.get_vsb_blob().then(t=>Ce(t,/\.vsb$/i.test(e)?e:`${e}.vsb`)).catch(t=>this._error(t))}import_scene(e,t={}){return go.call(this,e,t)}_sceneLoad(e,t){let i=this._generation;this._sessionActive=!0;let s=(async()=>{if(await this.when_ready,i!==this._generation||this.killsign)return;let r,n,d=document.baseURI;if(typeof e=="string"&&(d=new URL(e,document.baseURI).href),t){if(n=await this._pool.run({kind:"unzip",source:typeof e=="string"?d:e,limits:this.limits}),!Object.hasOwn(n,"json_data.vsj"))throw new Error("VSB manifest json_data.vsj is missing");r=JSON.parse(new TextDecoder().decode(n["json_data.vsj"]))}else{let l=new AbortController;this._aborters.add(l);try{r=JSON.parse(new TextDecoder().decode(await Mo(typeof e=="string"?d:e,this.limits.max_file_bytes,()=>{},l.signal)))}finally{this._aborters.delete(l)}}if(!(i!==this._generation||this.killsign)){if(!r||!Array.isArray(r.models)||r.models.length>this.limits.max_archive_entries)throw new Error("Invalid or oversized scene manifest");r.bg_color!==void 0&&this.set_bg_color(r.bg_color),r.center_models!==void 0&&this.set_center_models(r.center_models),r.auto_rotate!==void 0&&this.set_auto_rotate(r.auto_rotate),r.mouse_zoom!==void 0&&this.set_mouse_zoom(r.mouse_zoom),r.auto_resize!==void 0&&this.set_auto_resize(r.auto_resize),r.canvas_height!==void 0&&(this.canvas_height=r.canvas_height,this._setSizeStyle(),this.do_resize()),r.infinite_grid?this.set_infinite_grid(!0,r.infinite_grid.spacing,r.infinite_grid.height):r.grid&&this.set_grid(!0),r.camera_state&&(this._restoreCamera=r.camera_state);for(let l of r.models){if(!l||typeof l!="object")throw new Error("Invalid scene model");let c=So(l);if(delete c.mesh,delete c.data,delete c.local_file,n){if(typeof c.filename!="string"||!Object.hasOwn(n,c.filename))throw new Error("VSB model file is missing");c.local_file=new File([n[c.filename]],c.filename)}else c.filename&&(c.filename=new URL(c.filename,d).href);this.add_model(c)}}})().catch(r=>{i===this._generation&&r.name!=="AbortError"&&this._error(r)}).finally(()=>{this._sceneLoads.delete(s),this._finishSession()});this._sceneLoads.add(s)}load_vsj(e){return this._sceneLoad(e,!1)}load_vsb(e){return this._sceneLoad(e,!0)}animate_model(e,t){return this._guard(()=>{let i=this._model(e);if(!t){i.animation=null,i._animation=null;return}let s=structuredClone(t),r=[];for(let n of["delta","exact"])if(s[n]){let d=s[n];me(d.msec??300,"animation duration");let l={};for(let c of Le){let m=d[c]??(c.startsWith("scale")?d.scale:void 0);m!==void 0&&(Q(m,c),n==="exact"&&c.startsWith("scale")&&me(m,c),l[c]=m)}r.push({mode:n,values:l,duration:d.msec??300,loop:n==="delta"&&!!d.loop,from:Object.fromEntries(Le.map(c=>[c,i[c]]))})}if(!r.length)throw new Error("Animation needs delta or exact");i.animation=s,i._animation={start:null,tracks:r},this._invalidate()})}_animate(e,t){let i=e._animation;i.start??=t;let s=!1;for(let r of i.tracks){let n=(t-i.start)/r.duration,d=r.loop?n:Math.min(n,1);s||=r.loop||n<1;for(let[l,c]of Object.entries(r.values))e[l]=l.startsWith("scale")?Math.max(1e-6,r.mode==="delta"?r.from[l]+c*d:r.from[l]+(c-r.from[l])*d):r.mode==="delta"?r.from[l]+c*d:r.from[l]+(c-r.from[l])*d}e._stats=null,s||(e._animation=null,e.animation=null)}async get_image_blob(e="image/png"){await this.when_idle(),this._clip(),this.renderer.draw([...this._models.values()],this.camera,this.bg_color,!1);let t=new Promise((i,s)=>this.canvas.toBlob(r=>r?i(r):s(new Error("Screenshot failed")),e));return this._invalidate(),t}};var zt=new WeakMap,Yo=["background-color","background-value","cancel-load","clear-scene","close-colors","close-info","close-model","close-scene","close-section","close-view","color-mode-note","colors-button","colors-panel","depth-controls","depth-model-button","dismiss-notice","drop-message","drop-overlay","empty-state","file-heading","file-input","filename","finish-move","fit-button","grid-button","image-button","info-button","info-dimensions","info-panel","info-size","info-triangles","live-status","loading-label","loading-state","memory-budget","model-color","model-color-value","model-controls","model-list","model-tool","move-controls","move-gesture-message","move-mode","move-mode-name","move-model-button","notice","notice-message","open-label","position-details","position-error","position-units","position-x","position-y","position-z","reset-position","reset-rotation","restore-color","rotate-controls","rotate-model-button","rotation-error","rotation-x","rotation-y","rotation-z","sample-button","scene-arrangement","scene-button","scene-count","scene-panel","section-axis","section-button","section-enabled","section-flip","section-panel","section-position","section-value","smooth-shading","unit-note","view-button","view-description","view-label","view-panel","view-status","viewer","workspace"];function Ao(o,e={}){if(!o?.querySelectorAll)throw new TypeError("A Document or HTMLElement root is required");if(zt.has(o))throw new Error("ViewSTL app is already mounted on this root");let t=o.ownerDocument||o,i=t.defaultView,s=i.URL,r=new Map([...o.querySelectorAll("[id]")].map(a=>[a.id,a])),n=a=>r.get((e.id_prefix||"")+a),d=Yo.filter(a=>!n(a));if(Et.some(a=>n(a))&&d.push(...Et.filter(a=>!n(a))),kt.some(a=>n(a))&&d.push(...kt.filter(a=>!n(a))),d.length)throw new Error("Missing ViewSTL controls: "+d.join(", "));let l=new i.AbortController,c=o===t?i:o,m=!1,_=null,w=null,f=()=>{if(m)throw new Error("ViewSTL app is disposed")},u=(a,h,b,S={})=>a.addEventListener(h,b,{...S,signal:l.signal}),p=Vt(i.navigator.deviceMemory),y=["view","section","info","scene","colors"],g=new Map,A=[],k=["#64a5b2","#dd766b","#edb14c","#7ca984","#927fbc","#5a8ec7","#89949d"],E,x,C=0,I=0,v=null,F=0,z=null,P,L,D,M=!1,R=!1,j=!1,O=0,we=1,J=!1,q="move",W,ee,ue="transparent",zo={flat:"Solid",edges:"Solid + edges",wireframe:"Wireframe",xray:"X-ray",mesh:"Mesh overlay",studio:"Studio",orientation:"Face orientation"},ye=[...o.querySelectorAll('[name="view-mode"]')];e.id_prefix&&ye.forEach(a=>a.name=e.id_prefix+"view-mode");let it=e.sample_url,Ie=new Intl.NumberFormat(void 0,{maximumFractionDigits:2});function B(a){m||(n("live-status").textContent=a)}function ne(a){m||(n("notice-message").textContent=a,n("notice").hidden=!1)}function xe(){x?.set_selection(!R&&g.has(v)&&(J||!n("scene-panel").hidden)?v:null)}function Rt(a){n(a+"-panel").hidden=!0,n(a+"-button").setAttribute("aria-expanded","false"),xe()}function ce(){y.forEach(Rt)}function Pe(a){let h=n(a+"-panel").hidden;ce(),n(a+"-panel").hidden=!h,n(a+"-button").setAttribute("aria-expanded",String(h)),xe()}function Me(a){return a==null?"Sample model":a<1024**2?`${Ie.format(a/1024)} KiB`:a<1024**3?`${Ie.format(a/1024**2)} MiB`:`${Ie.format(a/1024**3)} GiB`}function rt(){let a=g.get(v),h=/^#[a-f0-9]{6}$/i.test(a?.color)?a.color:"#ffffff";n("model-color").value=h,n("model-color-value").textContent=a?.color===null?"File colors":h.toUpperCase(),n("restore-color").textContent=a?.embedded?"Restore file colors":"Restore original color",o.querySelectorAll("[data-model-color]").forEach(b=>b.setAttribute("aria-pressed",String(b.dataset.modelColor===a?.color))),o.querySelectorAll("[data-background]").forEach(b=>b.setAttribute("aria-pressed",String(b.dataset.background===ue))),n("background-color").value=ue==="transparent"?"#fafbfb":ue,n("background-value").textContent=ue==="transparent"?"Default":ue.toUpperCase()}function Lt(){let a=v===null?null:x.get_model_info(v),h=a?.display==="smooth"?"flat":a?.display||"flat";for(let T of ye)T.checked=T.value===h,T.disabled=R;n("view-label").textContent=zo[h],n("smooth-shading").checked=a?.smooth_shading||!1,n("smooth-shading").disabled=R||h==="wireframe",n("view-status").textContent=h==="wireframe"?"Smooth shading is available in surface views.":"Shading changes the appearance, not the geometry.";let b=h==="orientation"?"Blue: front faces \xB7 Red: back faces":"";n("view-description").textContent=b,n("view-description").hidden=!b,n("color-mode-note").hidden=h!=="orientation";let S=a?.section;n("section-enabled").checked=!!S,n("section-axis").value=S?.axis||"z",n("section-position").value=(S?.position??.5)*100,n("section-value").textContent=n("section-position").value+"%",n("section-flip").setAttribute("aria-pressed",String(S?.flip||!1)),n("section-button").setAttribute("aria-pressed",String(!!S)),o.querySelectorAll("[data-selected-name]").forEach(T=>T.textContent=g.get(v)?.name||""),rt(),st(),P?.sync(),L?.sync()}function Ct(){if(v===null)return;let a=x.get_model_info(v),h=g.get(v),b=h.units==="mm"?" mm":"";n("info-triangles").textContent=a.triangles.toLocaleString(),n("info-dimensions").textContent=[a.dims.x,a.dims.y,a.dims.z].map(S=>Ie.format(S)).join(" \xD7 ")+b,n("info-size").textContent=Me(h.size),n("unit-note").textContent=b?"Dimensions are shown in millimetres.":"Dimensions are in model units. STL and OBJ files do not specify physical units."}function It(){n("model-list").replaceChildren(...[...g.values()].map(a=>{let h=t.createElement("li"),b=t.createElement("button"),S=t.createElement("button"),T=t.createElement("button");return h.className="model-row",h.classList.toggle("selected",a.id===v),b.className="model-swatch",b.dataset.colorModel=a.id,b.style.backgroundColor=a.color||"#b7c5c9",b.setAttribute("aria-label","Color for "+a.name),b.title="Change color",S.className="model-pick",S.dataset.selectModel=a.id,S.textContent=a.name,S.title=a.name,S.setAttribute("aria-pressed",String(a.id===v)),T.className="icon-button",T.dataset.removeModel=a.id,T.textContent="\xD7",T.setAttribute("aria-label","Remove "+a.name),h.append(b,S,T),h})),n("scene-count").textContent=g.size,n("scene-arrangement").disabled=g.size===0}function be(){let a=g.size>0;n("workspace").classList.toggle("has-models",a),n("viewer").hidden=!a&&!R,n("empty-state").hidden=a||R,n("file-heading").hidden=!a,n("model-controls").hidden=!a,n("filename").textContent=g.get(v)?.name||"",n("filename").title=n("filename").textContent,n("open-label").textContent=a?"Add models":"Open files",n("drop-message").textContent=a?"Add to your scene.":"Drop it here.",It(),Lt(),Ct(),a||ce(),xe()}function Te(a){a&&P?.close(),!m&&(a&&G(!1),R=a,n("loading-state").hidden=!a,n("workspace").setAttribute("aria-busy",String(a)),be())}function Fe(a){P?.close(),g.has(a)&&(G(!1),I++,v=a,be())}function Ro(){return Qt([...g.values()],n("scene-arrangement").value)}function Oe(){x?.set_infinite_grid(j,we,O),n("grid-button").setAttribute("aria-pressed",String(j))}function nt(){if(G(!1),n("scene-arrangement").value==="original")for(let h of g.values())h.rotation={...h.originalRotation||{x:0,y:0,z:0}};let a=Ro();for(let h of a)g.get(h.id).position={x:h.x,y:h.y,z:h.z},at(g.get(h.id));(n("scene-arrangement").value!=="custom"||!g.size)&&(O=$t([...g.values()],a)),st(),Oe(),x?.set_auto_zoom()}function st(){let a=g.get(v);for(let h of["x","y","z"]){let b=n("position-"+h);b.value=Number((a?.position?.[h]||0).toPrecision(12)),b.disabled=!a,b.removeAttribute("aria-invalid")}n("position-error").hidden=!0,n("position-units").textContent=a?.units==="mm"?"Offset from file coordinates, in mm.":"Offset from file coordinates, in model units.",n("move-model-button").disabled=!a||R,n("reset-position").disabled=!a,Tt();for(let h of["rotate-model-button","depth-model-button","depth-nearer","depth-farther"])n(h).disabled=!a||R}function Ee(a,h,b=!0){return!g.has(a)||!Object.values(h).every(S=>Number.isFinite(S)&&Math.abs(S)<=1e12)?!1:(g.get(a).position={...h},at(g.get(a)),n("scene-arrangement").value="custom",a===v&&b&&st(),!0)}function at(a){let h=gt(a),b=a.rotation;x.set_rotation(a.id,...["x","y","z"].map(S=>b[S]%360*Math.PI/180)),x.set_position(a.id,h.x,h.y,h.z)}function Pt(){let a=g.get(v);return a?{...a,center:Xt(a),radius:Kt(a)}:null}function Tt(){let a=g.get(v);for(let h of["x","y","z"]){let b=n("rotation-"+h);b.value=Number((a?.rotation?.[h]||0).toPrecision(10)),b.disabled=!a,b.removeAttribute("aria-invalid")}n("rotation-error").hidden=!0,n("reset-rotation").disabled=!a}function Ne(a,h,b=!0){return!g.has(a)||!Object.values(h).every(S=>Number.isFinite(S)&&Math.abs(S)<=36e4)?!1:(g.get(a).rotation={...h},at(g.get(a)),n("scene-arrangement").value="custom",b&&a===v&&Tt(),!0)}let Lo={move:"Move",rotate:"Rotate",depth:"Depth"},Ft={move:"Drag to move the selected model",rotate:"Drag to rotate the selected model",depth:"Drag up for nearer, down for farther"};function Co(){x?.canvas&&(x.canvas.dataset.modelTool=q),o.querySelectorAll("[data-transform-tab]").forEach(a=>a.setAttribute("aria-pressed",String(a.dataset.transformTab===q)));for(let a of["move","rotate","depth"])n(a+"-controls").hidden=a!==q,n(a+"-model-button").setAttribute("aria-pressed",String(J&&a===q));n("model-tool").value=q,n("move-gesture-message").textContent=Ft[q]+" \xB7 Scroll to adjust depth"}function G(a,h=q){W?.setEnabled(!1),q=h,J=!!a&&v!==null&&!R,W?.setEnabled(J),Co(),n("move-mode").hidden=!J,n("move-mode-name").textContent=g.get(v)?.name||"",n("workspace").classList.toggle("moving-model-mode",J),x?.canvas&&x.canvas.setAttribute("aria-label",J?Ft[q]+". Scroll changes only its depth. Escape exits.":"3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."),J&&(ce(),x.canvas.focus({preventScroll:!0}),B(Lo[q]+" tool for "+g.get(v).name+". Done or Escape returns to viewing.")),xe()}for(let a of["x","y","z"]){let h=n("rotation-"+a);u(h,"input",()=>{v===null||!h.value.trim()||Ne(v,{...g.get(v).rotation,[a]:h.valueAsNumber},!1)&&(h.removeAttribute("aria-invalid"),n("rotation-error").hidden=!0)}),u(h,"change",()=>{v!==null&&(!h.value.trim()||!Ne(v,{...g.get(v).rotation,[a]:h.valueAsNumber})?(h.setAttribute("aria-invalid","true"),n("rotation-error").hidden=!1):B("Model rotation updated."))})}u(n("reset-rotation"),"click",()=>{G(!1),v!==null&&(Ne(v,{...g.get(v).originalRotation||{x:0,y:0,z:0}}),B("Selected model rotation reset."))});for(let a of["move","rotate","depth"])u(n(a+"-model-button"),"click",()=>G(!J||q!==a,a));for(let[a,h]of[["depth-nearer",-.12],["depth-farther",.12]])u(n(a),"click",()=>{let b=Pt();b&&(Ee(b.id,Ae(b,x.get_camera_state(),h)),B(h<0?"Selected model moved nearer.":"Selected model moved farther."))});o.querySelectorAll("[data-transform-tab]").forEach(a=>u(a,"click",()=>G(!1,a.dataset.transformTab))),u(n("model-tool"),"change",()=>G(!0,n("model-tool").value));for(let a of["x","y","z"]){let h=n("position-"+a);u(h,"input",()=>{v===null||!h.value.trim()||Ee(v,{...g.get(v).position,[a]:h.valueAsNumber},!1)&&(h.removeAttribute("aria-invalid"),n("position-error").hidden=!0)}),u(h,"change",()=>{v!==null&&(!h.value.trim()||!Ee(v,{...g.get(v).position,[a]:h.valueAsNumber})?(h.setAttribute("aria-invalid","true"),n("position-error").hidden=!1):B("Model position updated."))})}u(n("finish-move"),"click",()=>{G(!1),x?.canvas.focus({preventScroll:!0})}),u(n("reset-position"),"click",()=>{G(!1),v!==null&&(Ee(v,{...g.get(v).originalPosition||{x:0,y:0,z:0}}),B("Selected model restored to its original position."))}),u(i,"blur",()=>{J&&G(!1)});function De(){C++,A.length=0,D?.abort(),z!==null&&(x?.remove_model(z),z=null),Te(!1),B("Opening cancelled.")}function Ot(){P?.close(),G(!1),De(),I++,x?.clean(),g.clear(),v=null,j=!1,O=0,we=1,n("scene-arrangement").value="original",Oe(),n("notice").hidden=!0,be(),B("Scene cleared.")}function lt(a){if(!g.has(a))return;P?.close();let h=g.get(a).name;G(!1),x.remove_model(a),g.delete(a),I++,v===a&&(v=[...g.keys()].at(-1)??null),nt(),be(),B(h+" removed.")}async function Nt(){return E||(E=Promise.resolve().then(async()=>{if(m)throw new Error("ViewSTL app is disposed");if(x=new _e(n("viewer"),{...p.limits,worker_count:1,worker_timeout:e.worker_timeout??6e5,worker_url:e.worker_url,worker_factory:e.worker_factory,url_import_endpoint:e.url_import_endpoint,center_models:!1,bg_color:ue,allow_drag_and_drop:!1,camera_state:{position:{x:90,y:-130,z:90},target:{x:0,y:0,z:0},up:{x:0,y:0,z:1}},loading_progress_callback:a=>{if(m||!R||D)return;let h=a[z];n("loading-label").textContent=h?.phase==="loading-cad"?"Loading the CAD reader\u2026":h?.phase==="parsing-cad"?"Preparing CAD surfaces\u2026":h?.loaded&&!h.total?`Downloading your file\u2026 ${Me(h.loaded)}`:h?.total&&h.loaded<h.total?`Reading your file\u2026 ${Math.min(99,Math.floor(100*h.loaded/h.total))}%`:"Preparing your model\u2026"},model_loaded_callback:a=>{w=a},load_error_callback:a=>{ne(/limit exceeded|safety budget/i.test(a)?`This scene or view mode exceeds the estimated memory budget for this device (${Me(p.budget)}). Remove a model, use Solid view without smoothing, or try a device with more memory.`:`Couldn\u2019t display this model. ${a}`)}}),await x.when_ready,m)throw new Error("ViewSTL app is disposed");return W=to(x.canvas,{getCamera:()=>x.get_camera_state(),getModel:Pt,getTool:()=>q,onRotate:(a,h)=>Ne(a,h),onMove:(a,h)=>Ee(a,h),onStart:()=>{ee=n("scene-arrangement").value},onEnd:(a,h)=>{h&&(n("scene-arrangement").value=ee),B(h?"Move cancelled.":"Model updated.")}}),u(x.canvas,"dblclick",a=>{if(R||a.button!==0)return;let h=x.pick_model(a.clientX,a.clientY);!h||!g.has(h.id)||(a.preventDefault(),Fe(h.id),ce(),n("scene-panel").hidden=!1,n("scene-button").setAttribute("aria-expanded","true"),n("position-details").open=!0,xe(),B(g.get(h.id).name+" selected. Choose Move, Rotate or Depth."))}),x}).catch(a=>{throw W?.dispose(),W=null,x?.dispose(),x=null,E=null,a})),E}function je(a){f();let h=[];for(let S of a){if(!/\.(stl|obj|3mf|step|stp|iges|igs|vsb|vsj)$/i.test(S.name)){ne("Choose STL, OBJ, 3MF, STEP/STP, IGES/IGS, VSB or VSJ files.");continue}if(S.size===0){ne("This file is empty. Choose a file containing a 3D model.");continue}h.push(S)}let b=h.filter(S=>pe(S.name));if(b.length){let S={files:h.filter(T=>!pe(T.name)),used:new Set,failed:!1};for(let T of b)A.push({name:T.name,sceneFile:T,batch:S});A.push({remaining:S})}else A.push(...h);return A.length&&!M&&(_=Dt()),_||Promise.resolve()}function ct(a,h=""){if(f(),!e.url_import_endpoint)throw new Error("URL imports are not configured.");let b=new s(a.trim());if(!["https:","http:"].includes(b.protocol)||b.username||b.password)throw new Error("Use a public HTTP or HTTPS file URL without login credentials.");if(h&&!/^(stl|obj|3mf|step|stp|iges|igs|vsb|vsj)$/.test(h))throw new Error("Choose a supported 3D format.");b.hash="";let S=decodeURIComponent(b.pathname.split("/").at(-1)||"Remote model").slice(0,200);return A.push({name:S,remote:b.href,format:h,size:0,scene:["vsb","vsj"].includes(h)?h:pe(S)}),M||(_=Dt()),_||Promise.resolve()}n("url-form")&&n("url-dialog")&&u(n("url-form"),"submit",a=>{a.preventDefault();try{let h=ct(n("url-input").value,n("url-format").value);n("url-input").value="",n("url-error").hidden=!0,n("url-dialog").close(),h.catch(b=>ne(b.message))}catch(h){n("url-error").textContent=h.message,n("url-error").hidden=!1}});async function Dt(){if(!M){M=!0;try{for(;A.length;){let a=A.shift(),h=C;if(a.remaining){a.remaining.failed||A.unshift(...a.remaining.files.filter(b=>!a.remaining.used.has(b)));continue}Te(!0),n("notice").hidden=!0,n("loading-label").textContent="Opening "+a.name+"\u2026",B("Opening "+a.name);try{if(a.sceneFile||a.scene){await Io(a,h);continue}let b=await Gt(a);if(h!==C)continue;let S=[...g.values()].reduce((U,he)=>U+he.estimated,0);if(b+S>p.budget||a.size>p.limits.max_file_bytes)throw Error(`Adding this file would exceed the estimated ${Me(p.budget)} scene memory budget. Remove a model or try a device with more memory.`);let T=await Nt();if(h!==C)continue;T.do_resize(),F=Math.max(F,T.max_model_id+1);let $=F++;if(z=$,w=null,T.add_model({id:$,display:"flat",...a.remote?{remote_url:a.remote,remote_format:a.format}:a.url?{filename:a.url}:{local_file:a}}),await T.when_idle(),h!==C)continue;z=null;let se=w===$?T.get_model_info($):null;if(!se){B(a.name+" could not be opened.");continue}a.remote&&(a.name=se.name,a.size=se.file_bytes,delete a.remote);let de=se.has_colors,N=de?null:k[g.size%k.length];N&&T.set_color($,N),g.set($,{id:$,name:a.name,size:a.size,units:se.source_units||(/\.3mf$/i.test(a.name)?"mm":null),bounds:se.bounds,position:{x:0,y:0,z:0},rotation:{x:0,y:0,z:0},embedded:de,originalColor:N,color:N,estimated:se.triangles*224,sourceFile:a instanceof i.File?a:null}),v=$,I++,nt(),be(),B(a.name+" is ready."),T.canvas.focus({preventScroll:!0})}catch(b){a.batch&&(a.batch.failed=!0),h===C&&(z!==null&&(x?.remove_model(z),z=null),ne(/WebGL/i.test(b.message)?"3D viewing needs WebGL 2. Try enabling hardware acceleration in your browser, or use another device.":b.message))}}}finally{M=!1,Te(!1)}}}async function Io(a,h){let b=await Nt();if(h!==C)return;let S=new i.AbortController;D=S;try{let T=a.batch?.files||[],$=[...new Set([...g.values()].map(N=>N.sourceFile).filter(Boolean))],se=[...T,...a.sceneFile?$.filter(N=>!T.some(U=>U.name===N.name)&&$.filter(U=>U.name===N.name).length===1):[]],de=await b.import_scene(a.sceneFile||a.remote,{format:a.scene||pe(a.name),signal:S.signal,files:se,on_progress(N){h!==C||m||(n("loading-label").textContent=N.phase==="opening-model"?`Opening scene model ${N.loaded+1} of ${N.total}\u2026`:N.phase==="complete"?"Restoring scene\u2026":N.total?`Reading scene\u2026 ${Math.min(99,Math.floor(N.loaded/N.total*100))}%`:"Reading scene\u2026")}});if(h!==C)return;for(let N of de.used_files)a.batch?.used.add(N);for(let N of de.model_ids){let U=b._models.get(N),he=U.geometry,Po=Object.fromEntries(["min","max"].map(ve=>[ve,he.bounds[ve].map((Fo,Ut)=>(Fo-U.center[Ut])*U.scaleVector[Ut])])),ae={id:N,name:String(U.orig_filename||U.filename).split(/[\\/]/).pop().split(/[?#]/)[0],size:he.file_bytes??U.local_file?.size??null,units:he.units||U.units,bounds:Po,position:{x:0,y:0,z:0},rotation:{x:U.rotationx*180/Math.PI,y:U.rotationy*180/Math.PI,z:U.rotationz*180/Math.PI},embedded:!!he.colors,color:U.color||(he.colors?null:"#909090"),estimated:he.triangles*224,sourceFile:de.used_files.includes(U.local_file)?U.local_file:null},To=gt(ae);for(let ve of["x","y","z"])ae.position[ve]=U[ve]-To[ve];ae.originalPosition={...ae.position},ae.originalRotation={...ae.rotation},ae.originalColor=ae.color,g.set(N,ae)}F=Math.max(F,b.max_model_id+1),v=de.model_ids.at(-1),I++,n("scene-arrangement").value="custom",j=!!b.grid,O=b.grid?.height||0,we=b.grid?.spacing||1,n("grid-button").setAttribute("aria-pressed",String(j)),ht(b.bg_color),be(),B(a.name+" opened. "+de.model_ids.length+" models added."),b.canvas.focus({preventScroll:!0})}finally{D===S&&(D=null)}}o.querySelectorAll("[data-open-file]").forEach(a=>u(a,"click",()=>n("file-input").click())),u(n("file-input"),"change",()=>{let a=[...n("file-input").files];n("file-input").value="",je(a)}),n("sample-button").hidden=!it,u(n("sample-button"),"click",()=>{it&&je([{name:e.sample_name||"Sample.stl",url:it}])}),u(n("close-model"),"click",()=>lt(v)),u(n("cancel-load"),"click",De),u(n("clear-scene"),"click",Ot),u(n("dismiss-notice"),"click",()=>{n("notice").hidden=!0}),u(n("fit-button"),"click",()=>x?.set_auto_zoom()),u(n("scene-arrangement"),"change",nt),u(n("model-list"),"click",a=>{let h=a.target.closest("button");h&&(h.dataset.removeModel!==void 0?lt(Number(h.dataset.removeModel)):h.dataset.selectModel!==void 0?Fe(Number(h.dataset.selectModel)):h.dataset.colorModel!==void 0&&(Fe(Number(h.dataset.colorModel)),Pe("colors")))});async function jt(){if(v===null||R)return;let a=++I,h=ye.find(b=>b.checked).value;ye.forEach(b=>{b.disabled=!0}),n("smooth-shading").disabled=!0,n("view-status").textContent="Preparing view\u2026",n("notice").hidden=!0;try{x.set_view(v,{display:h,smooth_shading:n("smooth-shading").checked}),await x.when_idle()}finally{a===I&&(Lt(),B(n("view-label").textContent+" view ready."))}}ye.forEach(a=>u(a,"change",jt)),u(n("smooth-shading"),"change",jt);for(let a of y)u(n(a+"-button"),"click",()=>{a==="info"&&Ct(),Pe(a),a==="section"&&!n("section-panel").hidden&&!n("section-enabled").checked&&(n("section-enabled").checked=!0,ke())}),u(n("close-"+a),"click",()=>{Rt(a),a==="colors"&&Pe("view"),n(a+"-button").focus()});function ke(){if(v===null)return;let a=n("section-enabled").checked;x.set_section(v,a?{axis:n("section-axis").value,position:Number(n("section-position").value)/100,flip:n("section-flip").getAttribute("aria-pressed")==="true"}:null),n("section-value").textContent=n("section-position").value+"%",n("section-button").setAttribute("aria-pressed",String(a))}u(n("section-enabled"),"change",ke),u(n("section-axis"),"change",ke),u(n("section-position"),"input",ke),u(n("section-flip"),"click",()=>{n("section-flip").setAttribute("aria-pressed",String(n("section-flip").getAttribute("aria-pressed")!=="true")),ke()}),u(n("grid-button"),"click",()=>{j=!j,Oe()});function dt(a){v!==null&&(x.set_color(v,a),g.get(v).color=a,rt(),It())}u(n("model-color"),"input",()=>dt(n("model-color").value)),u(n("restore-color"),"click",()=>dt(g.get(v)?.originalColor)),o.querySelectorAll("[data-model-color]").forEach(a=>u(a,"click",()=>dt(a.dataset.modelColor)));function ht(a){ue=a;let h=a==="transparent"?[255,255,255]:[1,3,5].map(b=>parseInt(a.slice(b,b+2),16));n("workspace").classList.toggle("dark-background",h[0]*.2126+h[1]*.7152+h[2]*.0722<115),x?.set_bg_color(a),rt()}u(n("background-color"),"input",()=>ht(n("background-color").value)),o.querySelectorAll("[data-background]").forEach(a=>u(a,"click",()=>ht(a.dataset.background))),u(n("image-button"),"click",async()=>{if(v===null)return;let a=g.size>1?"viewstl-scene.png":g.get(v).name.replace(/\.[^.]+$/,"")+".png";try{let h=await x.get_image_blob("image/png");if(m)return;let b=s.createObjectURL(h),S=t.createElement("a");S.href=b,S.download=a,S.click(),setTimeout(()=>s.revokeObjectURL(b),1e3),B("Image ready to download.")}catch{ne("The image could not be saved. Please try again.")}});let Be=0,mt=a=>{let h=vt(a.dataTransfer);return h==="files"?h:!e.url_import_endpoint||a.target?.isContentEditable||a.target?.closest?.("input, textarea")?null:h},Ue=()=>{Be=0,n("drop-overlay").hidden=!0};u(c,"dragenter",a=>{mt(a)&&(a.preventDefault(),Be++,n("drop-overlay").hidden=!1)}),u(c,"dragover",a=>{mt(a)&&(a.preventDefault(),a.dataTransfer.dropEffect="copy")}),u(c,"dragleave",a=>{Be>0&&--Be===0&&Ue()}),u(c,"drop",a=>{let h=mt(a);if(Ue(),!!h){a.preventDefault();try{if(h==="files")je([...a.dataTransfer.files]).catch(b=>ne(b.message));else for(let b of oo(a.dataTransfer))ct(b).catch(S=>ne(S.message))}catch(b){ne(b.message)}}}),u(c,"dragend",Ue),u(i,"blur",Ue),o.querySelectorAll("[data-dialog]").forEach(a=>u(a,"click",()=>n(a.dataset.dialog).showModal())),o.querySelectorAll("dialog").forEach(a=>u(a,"click",h=>{if(h.target===a){let b=a.getBoundingClientRect();(h.clientX<b.left||h.clientX>b.right||h.clientY<b.top||h.clientY>b.bottom)&&(a===n("save-dialog")?P.close():a.close())}})),u(o,"keydown",a=>{if(a.key!=="Escape"||n("save-dialog")?.open)return;if(J){G(!1),B("Move tool closed.");return}let h=y.find(b=>!n(b+"-panel").hidden);h&&(ce(),h==="colors"&&Pe("view"),n(h+"-button").focus())}),u(o,"pointerdown",a=>{a.target.closest(".view-panel,.info-panel,.toolbar,.scene-heading-button,.move-mode")||ce()}),n("memory-budget").textContent=`Scene memory budget: ${Me(p.budget)}. Half of ${p.reported?"the memory estimate reported by your browser":"a conservative 2 GiB estimate because this browser does not report device memory"} is kept in reserve. This is an estimate, not a guarantee of available memory.`,n("scene-arrangement").value="original",n("notice").hidden=!0,n("drop-overlay").hidden=!0,n("save-button")&&(P=uo({$:n,listen:u,window:i,getState:()=>({busy:R,selectedId:v,models:[...g.values()]}),getViewer:()=>x,onOpen:()=>{G(!1),ce()},announce:B})),n("print-button")&&(L=fo({$:n,listen:u,window:i,getState:()=>({busy:R,selectedId:v,models:[...g.values()]}),getViewer:()=>x,onOpen:()=>{G(!1),ce()},announce:B})),Oe(),Te(!1),G(!1);let Bt={openFiles:je,openURL:ct,cancelLoading(){f(),De()},clear(){f(),Ot()},select(a){f(),Fe(a)},remove(a){f(),lt(a)},getState(){return f(),{selectedId:v,busy:R,arrangement:n("scene-arrangement").value,memoryBudget:p.budget,models:[...g.values()].map(a=>({id:a.id,name:a.name,size:a.size,units:a.units,position:{...a.position},rotation:{...a.rotation},color:a.color}))}},dispose(){m||(P?.dispose(),L?.dispose(),De(),G(!1),I++,m=!0,l.abort(),W?.dispose(),x?.dispose(),g.clear(),e.id_prefix&&ye.forEach(a=>a.name="view-mode"),zt.delete(o))}};return zt.set(o,Bt),Bt}var Jo=globalThis.document?.currentScript;_e.workerURL=new URL("viewstl.worker.js",Jo?.src||document.baseURI).href;globalThis.StlViewer=_e;globalThis.mountViewstlApp=Ao;})();
51
+ result=vec4(color,alpha);}`,Ze=class{constructor(e,t,i,s){if(this.canvas=e,this.domElement=e,this.resources=new Map,this.frames=0,this.lost=!1,this.gl=e.getContext("webgl2",{alpha:!0,antialias:!0,premultipliedAlpha:!1,preserveDrawingBuffer:!1}),!this.gl)throw new Error("WebGL2 is unavailable. Enable browser graphics acceleration or try another browser/device.");this.lostHandler=r=>{r.preventDefault(),this.lost=!0,this.resources.clear(),this.gridResource=null,this.infiniteGrid=null,this.selectionResource=null,this.diagnosticResource=null,t()},this.restoredHandler=()=>{try{this.initialize(),this.lost=!1,i()}catch(r){s(r)}},e.addEventListener("webglcontextlost",this.lostHandler),e.addEventListener("webglcontextrestored",this.restoredHandler),this.initialize()}initialize(){let e=this.gl,t=[],i;try{for(let[s,r]of[[e.VERTEX_SHADER,Ho],[e.FRAGMENT_SHADER,qo]]){let n=e.createShader(s);if(t.push(n),e.shaderSource(n,r),e.compileShader(n),!e.getShaderParameter(n,e.COMPILE_STATUS))throw new Error(`Shader compilation failed: ${e.getShaderInfoLog(n)}`)}i=e.createProgram();for(let s of t)e.attachShader(i,s);if(e.linkProgram(i),!e.getProgramParameter(i,e.LINK_STATUS))throw new Error(`Shader link failed: ${e.getProgramInfoLog(i)}`);this.program=i,this.uniforms=Object.fromEntries(["mvp","modelMatrix","normalMatrix","tint","light","eye","opacity","unlit","section","appearance","useColors"].map(s=>[s,e.getUniformLocation(i,s)]))}catch(s){throw i&&e.deleteProgram(i),s}finally{for(let s of t)e.deleteShader(s)}}upload(e,t,i){let s=this.gl,r=s.createVertexArray(),n=[];if(!r)throw new Error("Graphics memory is unavailable for this model.");s.bindVertexArray(r);try{for(let[d,l]of[[0,e],[1,t],[2,i]])if(l){let c=s.createBuffer();if(!c)throw new Error("Graphics memory is unavailable for this model.");n.push(c),s.bindBuffer(s.ARRAY_BUFFER,c),s.bufferData(s.ARRAY_BUFFER,l,s.STATIC_DRAW);let m=s.getError();if(m!==s.NO_ERROR)throw new Error(m===s.OUT_OF_MEMORY?"Not enough graphics memory for this model. Try closing other tabs or using another device.":"The browser could not upload this model to the graphics device.");s.enableVertexAttribArray(d),s.vertexAttribPointer(d,3,s.FLOAT,!1,0,0)}return s.bindVertexArray(null),{vao:r,buffers:n,count:e.length/3}}catch(d){s.bindVertexArray(null);for(let l of n)s.deleteBuffer(l);throw s.deleteVertexArray(r),d}}free(e){if(e){for(let t of e.buffers)this.gl.deleteBuffer(t);this.gl.deleteVertexArray(e.vao)}}remove(e){this.diagnostic?.model===e&&this.setDiagnostic(null);let t=this.resources.get(e);if(t)for(let i of Object.values(t))this.free(i);this.resources.delete(e)}clear(){this.setDiagnostic(null),this.setSelection(null);for(let e of this.resources.keys())this.remove(e);this.free(this.gridResource),this.gridResource=null,this.infiniteGrid?.dispose(),this.infiniteGrid=null}get liveBuffers(){let e=(this.gridResource?.buffers.length||0)+(this.selectionResource?.buffers.length||0)+(this.diagnosticResource?.buffers.length||0);for(let t of this.resources.values())for(let i of Object.values(t))e+=i.buffers.length;return e}setDiagnostic(e){this.free(this.diagnosticResource),this.diagnosticResource=null,this.diagnostic=e}drawDiagnostic(e){let t=this.gl,i=this.uniforms,s=this.diagnostic,r=X(s.model);this.diagnosticResource??=this.upload(s.positions),t.uniformMatrix4fv(i.mvp,!1,new Float32Array(ze(e,r))),t.uniformMatrix4fv(i.modelMatrix,!1,new Float32Array(r)),t.uniform4fv(i.section,[0,0,0,0]),t.uniform1i(i.appearance,0),t.uniform1i(i.useColors,0),t.uniform1i(i.unlit,1),t.uniform3fv(i.tint,[.85,.16,.06]),t.uniform1f(i.opacity,1),t.bindVertexArray(this.diagnosticResource.vao),t.disable(t.DEPTH_TEST),t.drawArrays(t.LINES,0,this.diagnosticResource.count),t.enable(t.DEPTH_TEST)}setSelection(e){this.selection!==e&&(this.free(this.selectionResource),this.selectionResource=null,this.selection=e)}drawSelection(e,t){let i=this.gl,s=this.uniforms,r=X(this.selection);this.selectionResource??=this.upload(po(this.selection.geometry.bounds)),i.uniformMatrix4fv(s.mvp,!1,new Float32Array(ze(e,r))),i.uniformMatrix4fv(s.modelMatrix,!1,new Float32Array(r)),i.uniformMatrix3fv(s.normalMatrix,!1,new Float32Array([1,0,0,0,1,0,0,0,1])),i.uniform4fv(s.section,[0,0,0,0]),i.uniform1i(s.appearance,0),i.uniform1i(s.useColors,0),i.uniform1i(s.unlit,1);let n=t==="transparent"?[1,1,1]:Y(t);i.uniform3fv(s.tint,n[0]+n[1]+n[2]<1.5?[.42,.81,.96]:[.08,.43,.65]),i.bindVertexArray(this.selectionResource.vao),i.disable(i.DEPTH_TEST),i.uniform1f(s.opacity,.18),i.drawArrays(i.LINES,0,this.selectionResource.count),i.enable(i.DEPTH_TEST),i.uniform1f(s.opacity,.9),i.drawArrays(i.LINES,0,this.selectionResource.count)}resource(e,t){let i=this.resources.get(e);if(i||(i={},this.resources.set(e,i)),!i[t]){let s=e.geometry;i[t]=t==="edges"||t==="wire"?this.upload(s[t]):this.upload(s.positions,t==="smooth"?s.smoothNormals:s.normals,s.colors)}return i[t]}setGrid(e){this.grid=e,this.free(this.gridResource),this.gridResource=null}resize(e,t,i=1){let s=this.gl,r=s.getParameter(s.MAX_RENDERBUFFER_SIZE);this.canvas.width=Math.min(r,Math.max(1,Math.round(e*i))),this.canvas.height=Math.min(r,Math.max(1,Math.round(t*i)))}draw(e,t,i,s=!0){if(this.lost)return;let r=this.gl,n=this.uniforms;r.viewport(0,0,this.canvas.width,this.canvas.height),r.clearColor(...i==="transparent"?[0,0,0,0]:[...Y(i),1]),r.depthMask(!0),r.clear(r.COLOR_BUFFER_BIT|r.DEPTH_BUFFER_BIT),r.enable(r.DEPTH_TEST),r.disable(r.CULL_FACE),r.useProgram(this.program),r.vertexAttrib3f(1,0,0,1),r.vertexAttrib3f(2,1,1,1),r.uniform3fv(n.light,H(V(t.position,t.target))),r.uniform3fv(n.eye,t.position);let d=Ye(t,this.canvas.width/this.canvas.height),l=e.filter(f=>f.geometry&&f.opacity>0&&f.color!=="transparent"),c=f=>{let u=le(X(f),f.geometry.bounds.min.map((y,g)=>(y+f.geometry.bounds.max[g])/2)),p=V(u,t.position);return te(p,p)},m=l.filter(f=>f.opacity>=1&&f.display!=="xray"),_=l.filter(f=>f.opacity<1||f.display==="xray").sort((f,u)=>c(u)-c(f)),w=f=>{let u=X(f),p=fe(u),y=f.geometry;r.uniformMatrix4fv(n.mvp,!1,new Float32Array(ze(d,u))),r.uniformMatrix4fv(n.modelMatrix,!1,new Float32Array(u)),r.uniform4fv(n.section,We(y.bounds,f.section)),r.uniform1i(n.appearance,{mesh:1,xray:2,studio:3,orientation:4}[f.display]||0),r.uniformMatrix3fv(n.normalMatrix,!1,new Float32Array([p[0],p[4],p[8],p[1],p[5],p[9],p[2],p[6],p[10]])),r.uniform3fv(n.tint,f.color&&f.color!=="transparent"?Y(f.color):y.colors?[1,1,1]:Y("#909090")),r.uniform1f(n.opacity,f.opacity),r.uniform1i(n.useColors,+(!f.color&&!!y.colors));let g=f.display==="wireframe"&&y.wire,A=g?"wire":(f.smooth_shading||f.display==="smooth")&&y.smoothNormals?"smooth":"flat";r.uniform1i(n.unlit,+!!g);let k=f.view_edges||f.display==="edges";k&&(r.enable(r.POLYGON_OFFSET_FILL),r.polygonOffset(1,1));let E=this.resource(f,A);if(r.bindVertexArray(E.vao),f.display==="xray"?(r.enable(r.CULL_FACE),r.cullFace(r.FRONT),r.drawArrays(r.TRIANGLES,0,E.count),r.cullFace(r.BACK),r.drawArrays(r.TRIANGLES,0,E.count),r.disable(r.CULL_FACE)):r.drawArrays(g?r.LINES:r.TRIANGLES,0,E.count),r.disable(r.POLYGON_OFFSET_FILL),k&&y.edges){r.uniform1i(n.useColors,0),r.uniform3fv(n.tint,[.08,.09,.11]),r.uniform1i(n.unlit,1);let x=this.resource(f,"edges");r.bindVertexArray(x.vao),r.drawArrays(r.LINES,0,x.count)}};r.disable(r.BLEND);for(let f of m)w(f);if(this.grid?.infinite)this.infiniteGrid??=new Ke(r),this.infiniteGrid.draw(d,t,this.grid,i),r.useProgram(this.program);else if(this.grid){if(!this.gridResource){let{size:f,divisions:u}=this.grid,p=[];for(let y=0;y<=u;y++){let g=-f/2+f*y/u;p.push(-f/2,g,0,f/2,g,0,g,-f/2,0,g,f/2,0)}this.gridResource=this.upload(new Float32Array(p))}r.uniformMatrix4fv(n.mvp,!1,new Float32Array(d)),r.uniform4fv(n.section,[0,0,0,0]),r.uniform1i(n.appearance,0),r.uniform1i(n.useColors,0),r.uniform3fv(n.tint,[.7,.73,.78]),r.uniform1f(n.opacity,1),r.uniform1i(n.unlit,1),r.bindVertexArray(this.gridResource.vao),r.drawArrays(r.LINES,0,this.gridResource.count)}r.enable(r.BLEND),r.blendFuncSeparate(r.SRC_ALPHA,r.ONE_MINUS_SRC_ALPHA,r.ONE,r.ONE_MINUS_SRC_ALPHA),r.depthMask(!1);for(let f of _)w(f);s&&this.selection&&l.includes(this.selection)&&this.drawSelection(d,i),s&&this.diagnostic&&e.includes(this.diagnostic.model)&&this.drawDiagnostic(d),r.depthMask(!0),r.disable(r.BLEND),r.bindVertexArray(null),this.frames++}dispose(){this.canvas.removeEventListener("webglcontextlost",this.lostHandler),this.canvas.removeEventListener("webglcontextrestored",this.restoredHandler),this.clear(),this.gl.deleteProgram(this.program),this.gl.getExtension("WEBGL_lose_context")?.loseContext()}};var Qe=class{constructor(e,t,i,s,r=0){this.enabled=!0,this.noZoom=!1,this.type=r,this.camera=t,this.invalidate=i,this.pointers=new Map,this.cleanups=[],this.canvas=e;let n=(l,c,m)=>{e.addEventListener(l,c,m),this.cleanups.push(()=>e.removeEventListener(l,c,m))};n("pointerdown",l=>{this.enabled&&(l.preventDefault(),e.focus({preventScroll:!0}),e.setPointerCapture(l.pointerId),this.pointers.set(l.pointerId,{x:l.clientX,y:l.clientY,button:l.button}),s(l,l.pointerType==="touch"?20:l.button===2?3:1))}),n("pointermove",l=>{let c=this.pointers.get(l.pointerId);if(!c||!this.enabled)return;let m={x:l.clientX,y:l.clientY,button:c.button};if(this.pointers.size===2){let _=[...this.pointers.entries()].find(([u])=>u!==l.pointerId)[1],w=Math.hypot(c.x-_.x,c.y-_.y),f=Math.hypot(m.x-_.x,m.y-_.y);w>0&&f>0&&this.zoom(w/f),this.pan((m.x-c.x)/2,(m.y-c.y)/2)}else c.button===2||l.shiftKey||c.button===1?this.pan(m.x-c.x,m.y-c.y):this.orbit((m.x-c.x)/Math.max(1,e.clientHeight)*Math.PI,(m.y-c.y)/Math.max(1,e.clientHeight)*Math.PI);this.pointers.set(l.pointerId,m)});let d=l=>{this.pointers.delete(l.pointerId),e.hasPointerCapture(l.pointerId)&&e.releasePointerCapture(l.pointerId)};n("pointerup",d),n("pointercancel",d),n("lostpointercapture",l=>this.pointers.delete(l.pointerId)),n("wheel",l=>{if(!this.enabled||this.noZoom)return;l.preventDefault();let c=l.deltaY*(l.deltaMode===1?16:l.deltaMode===2?e.clientHeight:1);this.zoom(Math.exp(Math.max(-1,Math.min(1,c*.001))))},{passive:!1}),n("dblclick",l=>{this.enabled&&s(l,11)}),n("contextmenu",l=>l.preventDefault())}orbit(e,t){let i=this.camera,s=V(i.position,i.target),r=H(i.up);s=Je(s,r,-e);let n=H(ie(r,s)),d=Je(s,n,-t);if(this.type===1)s=d,i.up=H(Je(r,n,-t));else{let l=H(d),c=l.reduce((m,_,w)=>m+_*r[w],0);Math.abs(c)<.9995&&(s=d)}i.position=re(i.target,s),this.invalidate()}zoom(e){if(this.noZoom)return;let t=this.camera,i=V(t.position,t.target),s=Math.hypot(...i),r=Math.max(1e-6,Math.min(1e15,s*e));t.position=re(t.target,oe(i,r/(s||1))),this.invalidate()}pan(e,t){let i=this.camera,s=V(i.position,i.target),r=H(ie(i.up,s)),n=H(ie(s,r)),d=2*Math.hypot(...s)*Math.tan(Math.PI/8)/Math.max(1,this.canvas.clientHeight),l=re(oe(r,-e*d),oe(n,t*d));i.target=re(i.target,l),i.position=re(i.position,l),this.invalidate()}dispose(){for(let e of this.cleanups)e();this.pointers.clear()}};var $e=class{constructor(e,{count:t=2,timeout:i=12e4,factory:s}={}){this.url=e,this.count=Math.max(1,Math.min(4,t)),this.timeout=i,this.factory=s,this.queue=[],this.active=new Set,this.next=0,this.disposed=!1}run(e,t,i=[]){if(this.disposed)return Promise.reject(new Error("Viewer disposed"));let s=++this.next,r,n=new Promise((d,l)=>{r={id:s,task:e,progress:t,transfer:i,resolve:d,reject:l},this.queue.push(r)});return n.cancel=()=>this.cancel(r),this.pump(),n}cancel(e){e.done||(e.done=!0,this.queue=this.queue.filter(t=>t!==e),clearTimeout(e.timer),e.worker?.terminate(),this.active.delete(e),e.reject(new DOMException("Loading cancelled","AbortError")),this.pump())}pump(){for(;!this.disposed&&this.active.size<this.count&&this.queue.length;){let e=this.queue.shift();this.active.add(e);let t=(i,s)=>{e.done||(e.done=!0,clearTimeout(e.timer),e.worker?.terminate(),this.active.delete(e),i?e.reject(i):e.resolve(s),this.pump())};try{e.worker=this.factory?this.factory():new Worker(this.url,{type:"module",name:"ViewSTL parser"}),e.worker.onmessage=({data:i})=>{if(i.id===e.id){if(i.progress){e.progress?.(i.progress);return}t(i.error?new Error(i.error):null,i.result)}},e.worker.onerror=i=>{i.preventDefault(),t(new Error(i.message||"Worker failed; check worker URL and Content Security Policy"))},e.worker.onmessageerror=()=>t(new Error("Invalid worker response")),e.timer=setTimeout(()=>t(new Error("File processing timed out")),this.timeout),e.worker.postMessage({id:e.id,task:e.task},e.transfer)}catch(i){t(i)}}}clear(){for(let e of[...this.queue,...this.active])this.cancel(e)}dispose(){this.disposed=!0,this.clear()}};function go(o,e={}){let t=new AbortController,i=t.signal,s=this._generation,r=[],n=new Map,d=new Set,l=this._models.size>0,c,m=0,_,w=!1,f,u=()=>{t.abort(),c?.cancel?.();for(let[A,k]of n)this._models.get(A)===k&&this.remove_model(A)},p=()=>{if(i.aborted||s!==this._generation||this.killsign)throw new DOMException("Scene opening cancelled","AbortError")},y=A=>e.on_progress?.(A);e.signal?.addEventListener("abort",u,{once:!0}),e.signal?.aborted&&u(),i.addEventListener("abort",()=>c?.cancel?.(),{once:!0}),this._aborters.add(t);let g=(async()=>{await this.when_ready,p(),_=this.get_camera_state();let A=e.format||pe(typeof o=="string"?o:o.name);if(!["vsb","vsj"].includes(A))throw new Error("Choose VSB or VSJ for a scene");let k,E;if(typeof o=="string"){if(k=Mt(o,document.baseURI),!this.options.url_import_endpoint)throw new Error("Configure url_import_endpoint for scene URL imports");if(E=new URL(this.options.url_import_endpoint,document.baseURI).href,new URL(E).origin!==location.origin)throw new Error("The URL relay must use the same origin as this page")}let x=this.limits.max_memory_bytes-this._occupiedMemory();K(this.limits,this._occupiedMemory()+67108864,"scene opening");let C={...this.limits,max_memory_bytes:x,max_file_bytes:Math.min(this.limits.max_file_bytes,Math.floor((x-67108864)/4))};m=Math.max(0,x-67108864),this._sceneBytes=(this._sceneBytes||0)+m,c=this._pool.run({kind:"read-scene",source:o,remote:k,relay:E,format:A,name:`scene.${A}`,limits:C},M=>{try{y({...M,phase:"reading-scene"})}catch(R){f=R,c.cancel()}});let I;try{I=await c}catch(M){throw f||M}c=null,p();let{scene:v,files:F,base:z}=I;this._sceneBytes-=m,m=Object.values(F||{}).reduce((M,R)=>M+R.byteLength,0)+JSON.stringify(v).length*2,this._sceneBytes+=m;let P=e.files||[],L=[],D=v.models.map(M=>{let R={...M};if(F)R.local_file=new File([F[M.filename]],M.filename);else{let j=mo(M.filename,P);if(j)R.local_file=j,d.add(j);else{let O=Mt(M.filename,k?z:void 0);O?R.remote_url=O:L.push(M.filename)}}if(R.remote_url&&!this.options.url_import_endpoint)throw new Error("Scene URL references require a configured URL relay");return R});if(L.length)throw new Error("Choose the VSJ together with these model files: "+L.slice(0,8).join(", ")+(L.length>8?"\u2026":""));if(D.length+this._models.size>this.limits.max_archive_entries)throw new Error("Scene model count limit exceeded");for(let M=0;M<D.length;M++){p(),y({phase:"opening-model",loaded:M,total:D.length}),p();let R=D[M],j=this.max_model_id+1;this.add_model({...R,id:j});let O=this._models.get(j);if(!O)throw new Error(this.error||"Invalid scene model");if(r.push(j),n.set(j,O),O._sceneImportPending=!0,await this._pending.get(j),p(),this._models.get(j)!==O||!O.geometry)throw new Error(this.error||"A scene model could not be opened");let we=v.center_models??this.center_models?O.geometry.bounds.min.map((W,ee)=>(W+O.geometry.bounds.max[ee])/2):[0,0,0],J=O.center.map((W,ee)=>W-we[ee]),q=le(X({...O,x:0,y:0,z:0,center:[0,0,0],scaleVector:O.scaleVector}),J);if(["x","y","z"].forEach((W,ee)=>O[W]+=q[ee]),[...this._models.values()].reduce((W,ee)=>W+(ee.geometry?.triangles||0),0)>this.limits.max_triangles)throw new Error("Scene triangle limit exceeded")}y({phase:"complete",loaded:D.length,total:D.length}),p();for(let M of n.values())M._sceneImportPending=!1;return v.bg_color!==void 0&&this.set_bg_color(v.bg_color),v.infinite_grid?this.set_infinite_grid(!0,v.infinite_grid.spacing,v.infinite_grid.height):this.set_grid(!!v.grid),v.auto_rotate!==void 0&&this.set_auto_rotate(v.auto_rotate),this._restoreCamera=v.camera_state||(l?_:null),w=!0,{model_ids:r,scene:v,used_files:[...d]}})().catch(A=>{throw u(),A}).finally(()=>{e.signal?.removeEventListener("abort",u),this._aborters.delete(t),this._sceneBytes=(this._sceneBytes||0)-m,this._sceneLoads.delete(g),s===this._generation&&!this.killsign&&(!w&&_&&(this._restoreCamera=_),this._finishSession())});return this._sessionActive=!0,this._sceneLoads.add(g),g}function _o(o={}){let e=o.level??"basic";if(!["basic","detailed"].includes(e))throw new Error("Choose basic or detailed print checks");let t=(r,n,d=1e9)=>{if(!Number.isFinite(r)||r<=0||r>d)throw new Error(`Invalid ${n}`);return r},i=null;if(o.profile!=null){if(o.profile.technology!=="fdm")throw new Error("Only FDM profiles are supported");i={technology:"fdm"};for(let r of["min_wall_mm","line_width_mm"])o.profile[r]!=null&&(i[r]=t(o.profile[r],r));if(o.profile.overhang_degrees!=null&&(i.overhang_degrees=t(o.profile.overhang_degrees,"overhang angle",89.9)),o.profile.build_volume_mm!=null){if(!Array.isArray(o.profile.build_volume_mm)||o.profile.build_volume_mm.length!==3)throw new Error("Build volume requires X, Y and Z");i.build_volume_mm=o.profile.build_volume_mm.map(r=>t(r,"build volume"))}}let s=t(o.max_time_ms??3e4,"check time limit",12e4);return{level:e,profile:i,apply_transforms:o.apply_transforms!==!1,units_confirmed:o.units_confirmed===!0,max_time_ms:s}}var yo=o=>64*1024**2+o*2400,ge=o=>JSON.stringify([o.x,o.y,o.z,o.rotationx,o.rotationy,o.rotationz,o.scaleVector,o.center,o.units,o.unitScale]);var Re=new WeakMap;function et(o,e){let t=Re.get(e);return!!t&&t.viewer.deref()===o&&t.models.every(({model:i,geometry:s,signature:r})=>{let n=i.deref();return n&&o._models.get(n.id)===n&&n.geometry===s.deref()&&ge(n)===r})}async function bo(o={}){await this.when_ready;let e=_o(o);if(this.killsign)throw new Error("Viewer disposed");let t=o.model_ids;if(t==="selected"&&(t=this.renderer?.selection?[this.renderer.selection.id]:[]),t??=[...this._models.keys()],!Array.isArray(t)||!t.length||new Set(t).size!==t.length||t.some(u=>!Number.isSafeInteger(u)))throw new Error("Choose one or more unique model IDs");if(t.length>this.limits.max_archive_entries)throw new Error("Print check model limit exceeded");let i=t.map(u=>{let p=this._model(u);if(!p.geometry||p._sceneImportPending)throw new Error("Wait for models to finish opening");return{model:p,geometry:p.geometry,signature:ge(p)}}),s=JSON.stringify([t,e]);if(o.signal?.aborted)throw new DOMException("Print check cancelled","AbortError");if(this._printCache?.key===s&&et(this,this._printCache.report)){let u=structuredClone(this._printCache.report);return Re.set(u,Re.get(this._printCache.report)),u}if(this._printJobs?.size)throw new Error("A print check is already running");this._printJobs??=new Map;let r=new AbortController,n=()=>r.abort();o.signal?.addEventListener("abort",n,{once:!0}),this._aborters.add(r),this._printJobs.set(r,new Set(i.map(u=>u.model)));let d,l=0,c,m=()=>d?.cancel?.();r.signal.addEventListener("abort",m);let _=()=>{if(r.signal.aborted||this.killsign)throw new DOMException("Print check cancelled","AbortError");if(i.some(({model:u,geometry:p,signature:y})=>this._models.get(u.id)!==u||u.geometry!==p||ge(u)!==y))throw new Error("Model changed. Run the print check again.")},w=u=>{_(),o.on_progress?.(u)},f=[];try{this._printCache=null;let u=i.length*256*1024;K(this.limits,this._occupiedMemory()+u,"print report"),l=u,this._printBytes=(this._printBytes||0)+l;for(let A=0;A<i.length;A++){_();let{model:k,geometry:E}=i[A],x=yo(E.triangles),C=this._occupiedMemory();K(this.limits,C+x,"print check"),this._printBytes+=x,l+=x;let I=new Float32Array(E.positions.length);for(let F=0;F<I.length;F+=262144)_(),I.set(E.positions.subarray(F,F+262144),F),F+262144<I.length&&await new Promise(z=>setTimeout(z,0));let v=e.apply_transforms?k:{...k,x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,center:[0,0,0],scaleVector:[k.unitScale,k.unitScale,k.unitScale]};w({phase:"Starting",model_id:k.id,model_index:A,total_models:i.length}),d=this._pool.run({kind:"print-check",model:{id:k.id,name:k.orig_filename||k.filename||`Model ${k.id}`,positions:I,matrix:X(v),source_units:E.units||null},options:e,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-C}},F=>{try{w({...F,model_id:k.id,model_index:A,total_models:i.length})}catch(z){c=z,r.abort()}},[I.buffer]);try{f.push(await d)}catch(F){throw c||F}finally{d=null,this._printBytes-=x,l-=x}}_();let p={version:1,model_ids:t.slice(),settings:e,models:f,summary:f.some(A=>A.summary==="issues_found")?"issues_found":f.some(A=>A.summary==="incomplete")?"incomplete":"no_issues_found",scope:"Each model checked separately; no cross-model union or collision analysis.",estimated:!!e.profile},y={viewer:new WeakRef(this),models:i.map(({model:A,geometry:k,signature:E})=>({model:new WeakRef(A),geometry:new WeakRef(k),signature:E}))};Re.set(p,y);let g=structuredClone(p);return Re.set(g,y),JSON.stringify(g).length<2*1024*1024&&this._occupiedMemory()+4*1024**2<=this.limits.max_memory_bytes&&(this._printCache={key:s,report:g}),w({phase:"Complete",total_models:i.length}),p}finally{d?.cancel?.(),this._printBytes=(this._printBytes||0)-l,o.signal?.removeEventListener("abort",n),this._aborters.delete(r),this._printJobs.delete(r)}}function vo(o,{issue:e,model_id:t=o.model_ids[0]}={}){if(!et(this,o))throw new Error("Print report is stale. Run the check again.");let i=o.models.find(l=>l.model_id===t),s=i?.checks.find(l=>l.code===e);if(!s)throw new Error("Unknown print check issue");let r=this._model(t),n=r.geometry.positions,d=[];if(s.edge_vertices?.length)d.push(...s.edge_vertices.slice(0,1536));else for(let l of s.triangle_ids.slice(0,128))if(!(!Number.isInteger(l)||l<0||l>=r.geometry.triangles))for(let c=0;c<3;c++)d.push(...n.subarray(l*9+c*3,l*9+c*3+3),...n.subarray(l*9+(c+1)%3*3,l*9+(c+1)%3*3+3));if(d.some(l=>!Number.isFinite(l)))throw new Error("Invalid print highlight");return this.renderer.setDiagnostic(d.length?{model:r,positions:new Float32Array(d),signature:ge(r)}:null),this._invalidate(),d.length/6}function St(o,e){return`${String(o||"scene").split(/[\\/]/).pop().split(/[?#]/)[0].replace(/[<>:"|*\x00-\x1f\x7f]/g,"_").replace(/\.(?:stl|obj|3mf|step|stp|iges|igs|zip|vsb|vsj)$/i,"").replace(/[. ]+$/g,"").slice(0,160)||"scene"}.${e}`}function wo(o={}){let e={stp:"step",igs:"iges"},t=String(o.format||"").toLowerCase(),i=e[t]||t;if(!["stl","obj","3mf","step","iges"].includes(i))throw new Error("Export format must be stl, obj, 3mf, step or iges");let s=o.units??"mm";if(!["mm","inch"].includes(s))throw new Error("Export units must be mm or inch");for(let d of["apply_transforms","colors"])if(o[d]!==void 0&&typeof o[d]!="boolean")throw new Error(`${d} must be a boolean`);if(o.on_progress!==void 0&&typeof o.on_progress!="function")throw new Error("on_progress must be a function");let r=o.colors!==!1&&["obj","3mf"].includes(i),n=i==="obj"&&r?"zip":i;return{format:i,units:s,colors:r,apply_transforms:o.apply_transforms!==!1,filename:St(o.filename,n)}}function xo(o,e){let t=o.reduce((s,r)=>s+r.geometry.triangles,0);return 67108864+t*{stl:192,obj:2400,"3mf":3e3,step:12e3,iges:4e3}[e.format]+o.length*4096}async function Mo(o,e,t=()=>{},i){let s,r=0;if(o instanceof ArrayBuffer){if(o.byteLength>e)throw new Error("File size limit exceeded");return t(o.byteLength,o.byteLength),o}if(o instanceof Blob){if(r=o.size,r>e)throw new Error("File size limit exceeded");if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");t(0,r);let _=await o.arrayBuffer();if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");return t(r,r),_}else{let _=new URL(String(o),globalThis.location?.href);if(!["http:","https:","blob:"].includes(_.protocol))throw new Error("Unsupported file URL protocol");let w=await fetch(_,{signal:i,credentials:"same-origin"});if(!w.ok)throw new Error(`HTTP ${w.status}: ${_.pathname}`);r=Number(w.headers.get("content-length"))||0,s=w.body}if(r>e)throw new Error("File size limit exceeded");if(!s)throw new Error("Empty response body");let n=s.getReader(),d=[],l=0;try{for(;;){if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");let{done:_,value:w}=await n.read();if(_)break;if(l+=w.length,l>e)throw new Error("File size limit exceeded");d.push(w),t(l,Math.max(r,l))}}catch(_){throw await n.cancel().catch(()=>{}),_}finally{n.releaseLock()}let c=new Uint8Array(l),m=0;for(let _ of d)c.set(_,m),m+=_.length;return t(l,l),c.buffer}function Eo(o,e){if(!o?.isMesh&&!o?.geometry)throw new Error("Expected a Three.js mesh");if(o.isSkinnedMesh||o.isInstancedMesh||o.children?.length||o.morphTargetInfluences?.some(Boolean))throw new Error("Only a static standalone Three.js mesh is supported");let t=o.material;if(Array.isArray(t)||t?.isShaderMaterial||t?.map)throw new Error("Multiple, textured or shader materials need an application adapter");let i=o.geometry,s,r=null;if(i?.attributes?.position){let n=i.attributes.position,d=i.index,l=d?.count??n.count;if(l%3||l/3>e.max_triangles)throw new Error("Invalid or oversized mesh");s=new Float32Array(l*3),i.attributes.color&&(r=new Float32Array(l*3));for(let c=0;c<l;c++){let m=d?d.getX(c):c;for(let[_,w]of["getX","getY","getZ"].entries())s[c*3+_]=n[w](m),r&&(r[c*3+_]=i.attributes.color[w](m))}}else if(i?.vertices&&i?.faces){if(i.faces.length>e.max_triangles)throw new Error("Triangle limit exceeded");s=new Float32Array(i.faces.length*9),t?.vertexColors&&(r=new Float32Array(s.length)),i.faces.forEach((n,d)=>{for(let[l,c]of[n.a,n.b,n.c].entries()){let m=i.vertices[c];if(!m)throw new Error("Invalid mesh index");s.set([m.x,m.y,m.z],d*9+l*3),r&&r.set([n.color.r,n.color.g,n.color.b],d*9+l*3)}})}else throw new Error("Unsupported Three.js geometry");return Xe(s,r,e)}function ko(o,e){if(!e?.Mesh||!e?.BufferGeometry||!e?.BufferAttribute)throw new Error("get_model_mesh requires options.three or globalThis.THREE; use get_model_data for native geometry");let t=o.geometry,i=new e.BufferGeometry,s=t.positions.slice(),r=o.scaleVector,n=o.center;for(let m=0;m<s.length;m++)s[m]=(s[m]-n[m%3])*r[m%3];let d=(m,_)=>{let w=new e.BufferAttribute(_,3);i.setAttribute?i.setAttribute(m,w):i.addAttribute(m,w)};d("position",s),t.colors&&!o.color&&d("color",t.colors.slice()),i.computeVertexNormals();let l=new e.MeshLambertMaterial({color:o.color==="transparent"?"#909090":o.color||(t.colors?"#ffffff":"#909090"),vertexColors:t.colors&&!o.color?e.VertexColors??!0:!1,side:e.DoubleSide,transparent:o.opacity<1||o.color==="transparent",opacity:o.color==="transparent"?0:o.opacity,wireframe:o.display==="wireframe",flatShading:o.display!=="smooth"}),c=new e.Mesh(i,l);return c.position.set(o.x,o.y,o.z),c.rotation.set(o.rotationx,o.rotationy,o.rotationz),c}var Le=["x","y","z","rotationx","rotationy","rotationz","scalex","scaley","scalez"],Wo=["id","filename","remote_url","remote_format","orig_filename","local_file","mesh","data","x","y","z","display","color","scale","scalex","scaley","scalez","rotationx","rotationy","rotationz","opacity","view_edges","smooth_shading","section","units","animation"],Q=(o,e)=>{if(typeof o!="number"||!Number.isFinite(o))throw new Error(`${e} must be a finite number`);return o},me=(o,e)=>{if(Q(o,e),o<=0)throw new Error(`${e} must be positive`);return o},At=(o,e)=>[Q(o.x,`${e}.x`),Q(o.y,`${e}.y`),Q(o.z,`${e}.z`)],tt=o=>({x:o[0],y:o[1],z:o[2]}),ot=o=>String(o||"model.stl").split(/[\\/]/).pop().split(/[?#]/)[0];function Ce(o,e){let t=document.createElement("a"),i=URL.createObjectURL(o);t.href=i,t.download=ot(e),t.click(),setTimeout(()=>URL.revokeObjectURL(i),1e3)}function So(o){let e={};for(let t of Wo)Object.hasOwn(o,t)&&(e[t]=o[t]);return e}var _e=class o{static version="2.0.0-rc.4";static workerURL=null;constructor(e,t={}){if(!e?.appendChild)throw new TypeError("StlViewer requires a DOM container");this.parent_element=e,this.options={...t},this.error=null,this.ready=!1,this.killsign=!1,this.models=[],this.models_ref=Object.create(null),this.models_count=0,this.max_model_id=-1,this.load_status=Object.create(null),this.load_session=0,this._models=new Map,this._pending=new Map,this._jobs=new Map,this._derived=new Set,this._memoryReservations=new Map,this._exportBytes=0,this._loadTail=Promise.resolve(),this._sceneLoads=new Set,this._aborters=new Set,this._generation=0,this._sessionActive=!1,this._frame=0,this._lastTime=0,this.grid=null,this._gridAuto=!1,this.camera={position:[0,0,100],target:[0,0,0],up:[0,1,0],near:.01,far:1e4},this.when_ready=new Promise((i,s)=>{this._readyResolve=i,this._readyReject=s}),this.when_ready.catch(()=>{});for(let i of["ready_callback","all_loaded_callback","model_loaded_callback","load_error_callback","loading_progress_callback","on_model_drop"])this[i]=t[i]??null;this.onmousedown_callback=t.on_model_mousedown??t.on_model_mouseclick??null,queueMicrotask(()=>this._initialize())}_initialize(){if(!this.killsign)try{let e=this.options;if(this.limits=ao(e),this.bg_color=e.bg_color??e.bgcolor??"transparent",this.bg_color!=="transparent"&&Y(this.bg_color),e.worker_count!==void 0&&(!Number.isInteger(e.worker_count)||e.worker_count<1||e.worker_count>4))throw new Error("worker_count must be 1\u20134");if(e.worker_timeout!==void 0&&me(e.worker_timeout,"worker_timeout"),e.pixel_ratio!==void 0&&me(e.pixel_ratio,"pixel_ratio"),e.controls!==void 0&&e.controls!==0&&e.controls!==1)throw new Error("controls must be 0 or 1");this.center_models=e.center_models??!0,this.auto_resize=e.auto_resize??!0,this.auto_rotate=e.auto_rotate??!1,this.mouse_zoom=e.mouse_zoom??!0,this.send_no_model_click_event=e.send_no_model_click_event??!1,this.zoom=e.zoom??-1,this.canvas_width=e.width??e.canvas_width??"100%",this.canvas_height=e.height??e.canvas_height??"100%",this.canvas=document.createElement("canvas"),this.canvas.style.cssText="display:block;touch-action:none;outline-offset:3px;",this._setSizeStyle(),this.canvas.tabIndex=0,this.canvas.setAttribute("aria-label","3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."),this.parent_element.appendChild(this.canvas),this.renderer=new Ze(this.canvas,()=>this._message("Graphics interrupted. Waiting for the browser to restore rendering\u2026"),()=>{this._removeMessage(),this._invalidate()},i=>this._error(i)),this.controls=new Qe(this.canvas,this.camera,()=>this._invalidate(),(i,s)=>this._pick(i,s),e.controls??0),this.controls.noZoom=!this.mouse_zoom;let t=e.worker_url??o.workerURL;if(e.worker_url===void 0&&typeof e.load_three_files=="string"&&(t=new URL("viewstl.worker.js",new URL(e.load_three_files||"./",document.baseURI)).href),!t&&!e.worker_factory)throw new Error("Specify worker_url when using the CommonJS build");this._pool=new $e(t,{count:e.worker_count??2,timeout:e.worker_timeout??12e4,factory:e.worker_factory}),this._drop=i=>{i.preventDefault();let s=[...i.dataTransfer.files];for(let r of s)this._callback("on_model_drop",r.name),this.add_model({local_file:r})},this._drag=i=>i.preventDefault(),this.set_drag_and_drop(e.allow_drag_and_drop??!0),this.set_auto_resize(this.auto_resize),this.do_resize(),this.camera.position=[e.camerax??0,e.cameray??0,this.zoom>=0?Math.max(this.zoom,1e-6):e.cameraz??100],e.camera_state&&(this.set_camera_state(e.camera_state),this._restoreCamera=e.camera_state),e.grid&&this.set_grid(!0),this._key=i=>{if(!this.controls.enabled)return;let s={ArrowLeft:()=>this.controls.orbit(-.12,0),ArrowRight:()=>this.controls.orbit(.12,0),ArrowUp:()=>this.controls.orbit(0,-.12),ArrowDown:()=>this.controls.orbit(0,.12),"+":()=>this.controls.zoom(.9),"-":()=>this.controls.zoom(1.1),f:()=>this.set_auto_zoom()};s[i.key]&&(i.preventDefault(),s[i.key]())},this.canvas.addEventListener("keydown",this._key),this.ready=!0,this._readyResolve(this),this._callback("ready_callback"),e.models&&this.add_models(e.models),this._invalidate()}catch(e){this.ready=!1,this._readyReject(e),this._error(e),this._message(e.message),this._pool?.dispose(),this.controls?.dispose(),this.renderer?.dispose(),this._resizeObserver?.disconnect()}}_callback(e,...t){let i=this[e];if(typeof i=="function")try{i(...t)}catch(s){console.error(`ViewSTL ${e} callback failed`,s)}}_error(e){return this.error=e?.message??String(e),this._callback("load_error_callback",this.error),null}_message(e){this._removeMessage(),this._messageElement=document.createElement("p"),this._messageElement.setAttribute("role","status"),this._messageElement.textContent=e,this.parent_element.appendChild(this._messageElement)}_removeMessage(){this._messageElement?.remove(),this._messageElement=null}_setSizeStyle(){this.canvas.style.width=typeof this.canvas_width=="number"?`${this.canvas_width}px`:this.canvas_width,this.canvas.style.height=typeof this.canvas_height=="number"?`${this.canvas_height}px`:this.canvas_height}_guard(e){if(this.killsign)return null;try{return e()}catch(t){return this._error(t)}}_model(e){let t=this._models.get(Number(e));if(!t)throw new Error(`Model id not found: ${e}`);return t}_invalidate(){let e=this.renderer?.diagnostic;e&&ge(e.model)!==e.signature&&this.renderer.setDiagnostic(null),!(this.killsign||!this.renderer||this.renderer.lost||this._frame)&&(this._frame=requestAnimationFrame(t=>{this._frame=0,this._render(t)}))}_render(e){let t=this._lastTime?Math.min((e-this._lastTime)/1e3,.1):0;this._lastTime=e;let i=!1;for(let s of this._models.values())s.geometry&&s._animation&&(this._animate(s,e),i||=!!s._animation);this.auto_rotate&&this.controls.enabled&&this.controls.orbit(t*.3,0),this._clip();try{this.renderer.draw([...this._models.values()].filter(s=>!s._sceneImportPending),this.camera,this.bg_color)}catch(s){this.renderer.clear(),this._error(s);return}i||this.auto_rotate&&this.controls.enabled?this._invalidate():this._lastTime=0}_bounds(){let e=[1/0,1/0,1/0],t=[-1/0,-1/0,-1/0],i=0;for(let s of this._models.values())if(s.geometry&&!s._sceneImportPending){i++;let{bounds:r}=s.geometry,n=X(s);for(let d=0;d<8;d++){let l=le(n,[0,1,2].map(c=>r[d&1<<c?"max":"min"][c]));for(let c=0;c<3;c++)e[c]=Math.min(e[c],l[c]),t[c]=Math.max(t[c],l[c])}}return i?{min:e,max:t,center:e.map((s,r)=>(s+t[r])/2),radius:Math.max(Math.hypot(...V(t,e))/2,1e-6)}:null}_clip(){let e=this._bounds(),t=Math.hypot(...V(this.camera.position,e?.center??this.camera.target)),i=e?.radius??10;this.camera.near=Math.max(1e-6,Math.min(i*.001,Math.max(t-i,1e-6)*.1)),this.camera.far=Math.max(this.camera.near*100,t+i*4,100)}_progress(e,t){!this._models.has(e)||this.killsign||(this.load_status[e]=t,this._callback("loading_progress_callback",this.load_status,this.load_session))}add_models(e){return this._guard(()=>{if(!Array.isArray(e))throw new Error("models must be an array");for(let t of e)this.add_model(t)})}add_model(e){return this._guard(()=>{if(!e||typeof e!="object")throw new Error("Invalid model");let t=So(e),i=t.filename??t.local_file?.name??"model.stl";if(/\.vsj(?:[?#]|$)/i.test(i))return this.load_vsj(t.local_file??i);if(/\.vsb(?:[?#]|$)/i.test(i))return this.load_vsb(t.local_file??i);let s=t.id===void 0||t.id===-1?this.max_model_id+1:t.id;if(!Number.isSafeInteger(s)||s<0||this._models.has(s))throw new Error(`Invalid or duplicate model id: ${s}`);let r={...t,id:s,filename:t.filename??t.local_file?.name??"",x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,scalex:1,scaley:1,scalez:1,opacity:1,units:"mm",display:"flat",view_edges:!1,color:null,unitScale:1,center:[0,0,0],geometry:null};for(let l of Le)r[l]=t[l]??(l.startsWith("scale")?t.scale??1:0);for(let l of Le)Q(r[l],l),l.startsWith("scale")&&me(r[l],l);if(r.opacity=t.opacity??t.mesh?.material?.opacity??1,Q(r.opacity,"opacity"),r.opacity<0||r.opacity>1)throw new Error("Opacity must be between 0 and 1");if(r.display=t.display??"flat",Object.assign(r,He(r.display,t.smooth_shading??r.display==="smooth")),r.section=qe(t.section),r.units=t.units??"mm",!["mm","inch"].includes(r.units))throw new Error("Invalid units");r.color=t.color??(t.mesh?.material?.color?.getHexString?"#"+t.mesh.material.color.getHexString():null),r.color&&r.color!=="transparent"&&Y(r.color),r.view_edges=!!t.view_edges,Object.defineProperty(r,"scaleVector",{get:()=>[r.scalex*r.unitScale,r.scaley*r.unitScale,r.scalez*r.unitScale]}),this.max_model_id=Math.max(s,this.max_model_id),this._models.set(s,r),this.models_ref[s]=this.models.length,this.models.push(r),this.models_count=this._models.size,this._sessionActive=!0,this.load_status[s]={loaded:0,total:0};let n=this._generation,d=this._loadModel(r,t,n).then(()=>{n!==this._generation||this._models.get(s)!==r||(this._pending.delete(s),this._finishSession(),this._callback("model_loaded_callback",s))}).catch(l=>{n===this._generation&&this._models.get(s)===r&&(this._pending.delete(s),this._deleteModel(r),l.name!=="AbortError"&&this._error(l),this._finishSession())}).finally(()=>{this._jobs.delete(r)});this._pending.set(s,d)})}_occupiedMemory(e){let t=(this._exportBytes||0)+(this._sceneBytes||0)+(this._printBytes||0)+(this._printCache?4194304:0);for(let i of this._models.values())if(i!==e){let s=this._memoryReservations?.get(i);t+=Math.max(ro(i.geometry),[...s?.values()||[]].reduce((r,n)=>r+n.bytes,0))}return t}_reserveMemory(e,t,i="derive"){this._memoryReservations??=new Map;let s=this._memoryReservations.get(e);s||this._memoryReservations.set(e,s=new Map);let r={};return s.set(r,{bytes:t,kind:i}),()=>{s.delete(r),!s.size&&this._memoryReservations.get(e)===s&&this._memoryReservations.delete(e)}}async _loadModel(e,t,i){let s=()=>{};if(this.options.max_memory_bytes!==void 0){let r=this._loadTail;this._loadTail=new Promise(n=>{s=n}),await r}try{await this._readModel(e,t,i)}finally{s()}}async _readModel(e,t,i){if(await this.when_ready,this.killsign||i!==this._generation||this._models.get(e.id)!==e)return;let s=Math.max(1,this.limits.max_memory_bytes-this._occupiedMemory(e)),r={...this.limits,max_memory_bytes:s};K(r,67108864,"scene");let n=this.limits.max_memory_bytes<Number.MAX_SAFE_INTEGER?this._reserveMemory(e,s-67108864,"parse"):()=>{};try{let d=wt(e);if(t.mesh)e.geometry=Eo(t.mesh,r);else if(t.data)e.geometry=Xe(new Float32Array(t.data.positions),t.data.colors?new Float32Array(t.data.colors):null,r);else{let l=t.local_file??t.filename??t.remote_url;if(!l)throw new Error("A filename, local_file or mesh is required");let c;if(t.remote_url){let _=new URL(t.remote_url);if(!["https:","http:"].includes(_.protocol)||_.username||_.password)throw new Error("Use a public HTTP or HTTPS file URL without login credentials.");if(!this.options.url_import_endpoint)throw new Error("Configure url_import_endpoint to import remote URLs through a relay.");if(c=new URL(this.options.url_import_endpoint,document.baseURI).href,new URL(c).origin!==location.origin)throw new Error("The URL relay must use the same origin as this page.")}let m=this._pool.run({kind:"parse",remote:t.remote_url,format:t.remote_format,relay:c,source:typeof l=="string"?new URL(l,document.baseURI).href:l,name:e.filename,limits:r,features:d.smooth||d.edges||d.wire?d:null},_=>this._progress(e.id,_));this._jobs.set(e,m),e.geometry=await m,t.remote_url&&(e.filename=e.geometry.source_name,delete e.remote_url,delete e.remote_format)}if(this.killsign||i!==this._generation||this._models.get(e.id)!==e){e.geometry=null;return}this.center_models&&(e.center=e.geometry.bounds.min.map((l,c)=>(l+e.geometry.bounds.max[c])/2)),(t.mesh||t.data)&&(d.smooth||d.edges||d.wire)&&await this._derive(e,d),t.animation&&this.animate_model(e.id,t.animation),delete e.mesh,delete e.data,this._invalidate()}finally{n()}}_finishSession(){this.killsign||this._pending.size||this._sceneLoads.size||!this._sessionActive||(this._sessionActive=!1,this._restoreCamera?(this.set_camera_state(this._restoreCamera),this._restoreCamera=null):this.zoom===-1&&this.set_auto_zoom(),this._gridAuto&&this.set_grid(!0),this.load_session++,this._callback("all_loaded_callback"),this._invalidate())}_deleteModel(e){for(let[t,i]of this._printJobs||[])i.has(e)&&t.abort();this._printCache=null,this.renderer?.selection===e&&this.renderer.setSelection(null),this.renderer?.remove(e),this._jobs.get(e)?.cancel?.();for(let t of e._deriveJobs||[])t.cancel?.();this._models.delete(e.id),this._memoryReservations.delete(e),this.models[this.models_ref[e.id]]=null,delete this.models_ref[e.id],delete this.load_status[e.id],this.models_count=this._models.size,this._invalidate()}remove_model(e){return this._guard(()=>{let t=this._model(e);this._deleteModel(t),this._pending.delete(t.id),this._finishSession()})}clean(){if(!this.killsign){this._generation++,this._printCache=null,this._pool?.clear();for(let e of this._aborters)e.abort();this._aborters.clear(),this._models.clear(),this._pending.clear(),this._jobs.clear(),this._derived.clear(),this._memoryReservations.clear(),this._sceneLoads.clear(),this.models=[],this.models_ref=Object.create(null),this.models_count=0,this.max_model_id=-1,this.load_status=Object.create(null),this._sessionActive=!1,this.renderer?.clear(),this._restoreCamera=null,this._invalidate()}}dispose(){if(!this.killsign){this.clean(),this.killsign=!0,this.ready=!1,this._readyReject(new Error("Viewer disposed")),cancelAnimationFrame(this._frame),this._frame=0,this._pool?.dispose(),this.controls?.dispose(),this._resizeObserver?.disconnect(),this._windowResize&&window.removeEventListener("resize",this._windowResize),this.renderer?.dispose(),this.canvas&&(this.canvas.removeEventListener("drop",this._drop),this.canvas.removeEventListener("dragover",this._drag),this.canvas.removeEventListener("keydown",this._key),this.canvas.remove()),this._removeMessage();for(let e of Object.keys(this))e.endsWith("_callback")&&(this[e]=null);this.options={}}}async when_idle(){for(await this.when_ready;this._pending.size||this._sceneLoads.size||this._derived.size;)await Promise.allSettled([...this._pending.values(),...this._sceneLoads,...this._derived]);return this}_derive(e,t){let i=this._occupiedMemory(e)+[...this._memoryReservations?.get(e)?.values()||[]].filter(m=>m.kind==="derive").reduce((m,_)=>m+_.bytes,0),s=no(e.geometry.triangles,{...t,smooth:t.smooth||!!e.geometry.smoothNormals,edges:t.edges||!!e.geometry.edges,wire:t.wire||!!e.geometry.wire});K(this.limits,i+s,"view mode");let r=this._reserveMemory(e,s-67108864),n=this._generation,d=e._deriveRevision=(e._deriveRevision||0)+1,l;try{let m=e.geometry.positions.slice();l=this._pool.run({kind:"derive",positions:m,features:t,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-i}},null,[m.buffer])}catch(m){throw r(),m}(e._deriveJobs??=new Set).add(l);let c=l.then(m=>!this.killsign&&n===this._generation&&d===e._deriveRevision&&this._models.get(e.id)===e?(Object.assign(e.geometry,m),this.renderer.remove(e),this._invalidate(),!0):!1).catch(m=>{m.name!=="AbortError"&&n===this._generation&&this._error(m)}).finally(()=>{r(),e._deriveJobs.delete(l),this._derived.delete(c)});return this._derived.add(c),c}set_view(e,t){return this._guard(()=>{let i=this._model(e),s=i._requestedView||i,r=He(t.display??s.display,t.smooth_shading??s.smooth_shading),n=wt({...i,...r}),d=i.geometry,c=d&&(n.smooth&&!d.smoothNormals||n.wire&&!d.wire||n.edges&&(!d.edges||d.edgeAngle!==n.edgeAngle))?this._derive(i,n):null,m=(i._viewRevision||0)+1;i._viewRevision=m,i._requestedView=r;let _=w=>{this._models.get(e)!==i||i._viewRevision!==m||(w&&Object.assign(i,r),delete i._requestedView,this._invalidate())};if(!c)_(!0);else{let w=c.then(_).finally(()=>this._derived.delete(w));this._derived.add(w)}})}set_display(e,t){return this.set_view(e,{display:t,...t==="flat"||t==="smooth"?{smooth_shading:t==="smooth"}:{}})}set_smooth(e,t){return this._guard(()=>{let i=this._model(e),s=i._requestedView||i;return this.set_view(e,{display:s.display==="smooth"?"flat":s.display,smooth_shading:t})})}set_section(e,t){return this._guard(()=>{this._model(e).section=qe(t),this._invalidate()})}set_color(e,t){return this._guard(()=>{t!==null&&t!=="transparent"&&Y(t);let i=this._model(e);i.color=t,this._invalidate()})}set_opacity(e,t){return this._guard(()=>{if(Q(t,"opacity"),t<0||t>1)throw new Error("Opacity must be between 0 and 1");this._model(e).opacity=t,this._invalidate()})}set_position(e,t,i,s){return this._guard(()=>{let r=this._model(e);[t,i,s].forEach((n,d)=>{n!==void 0&&(r[["x","y","z"][d]]=Q(n,"position"))}),this._invalidate()})}set_scale(e,t,i=t,s=t,r=!1){return this._guard(()=>{let n=this._model(e);if([t,i,s].forEach(d=>me(d,"scale")),r){if(t/n.scalex!==i/n.scaley||t/n.scalex!==s/n.scalez)throw new Error("keep_prev_scale requires a uniform geometry factor");n.unitScale*=t/n.scalex}else[n.scalex,n.scaley,n.scalez]=[t,i,s];n._stats=null,this._invalidate()})}set_selection(e){return this._guard(()=>{this.renderer?.setSelection(e===null?null:this._model(e)),this._invalidate()})}set_rotation(e,t,i,s,r=!1){return this._guard(()=>{let n=this._model(e);[t,i,s].forEach((d,l)=>{let c=["rotationx","rotationy","rotationz"][l];d!==void 0&&(n[c]=Q(d,"rotation")+(r?n[c]:0))}),this._invalidate()})}rotate(e,t,i,s){return this.set_rotation(e,t,i,s,!0)}set_edges(e,t){return this._guard(()=>{let i=this._model(e);i.view_edges=!!t,t&&i.geometry&&!i.geometry.edges&&this._derive(i,{edges:!0}),this._invalidate()})}set_grid(e,t,i=10){return this._guard(()=>{if(this._gridAuto=!!e&&t===void 0,!e)this.grid=null,this.renderer?.setGrid(null);else{let s=this._bounds();if(t??=s&&Math.max(...V(s.max,s.min))*1.2||10,me(t,"grid size"),!Number.isInteger(i)||i<1||i>1e3)throw new Error("Grid divisions must be 1\u20131000");this.grid={size:t,divisions:i},this.renderer?.setGrid(this.grid)}this._invalidate()})}set_infinite_grid(e,t=1,i=0){return this._guard(()=>{me(t,"grid spacing"),Q(i,"grid height"),this._gridAuto=!1,this.grid=e?{infinite:!0,spacing:t,height:i}:null,this.renderer?.setGrid(this.grid),this._invalidate()})}set_model_units(e,t,i=!1){return this._guard(()=>{if(!["mm","inch"].includes(t))throw new Error("Units must be mm or inch");let s=this._model(e);i&&s.units!==t&&(s.unitScale*=t==="inch"?25.4:1/25.4),s.units=t,s._stats=null,this._invalidate()})}set_bg_color(e){return this._guard(()=>{e!=="transparent"&&Y(e),this.bg_color=e,this._invalidate()})}set_center_models(e){this.center_models=!!e}set_zoom(e=-1){return this._guard(()=>{if(Q(e,"zoom"),e===-1)this.zoom=-1,this.set_auto_zoom();else{if(e<0)throw new Error("Zoom must be non-negative or -1");this.zoom=e,this.camera.position[2]=Math.max(e,1e-6),this._invalidate()}})}set_auto_zoom(){let e=this._bounds();if(!e)return;this.zoom=-1;let t=(this.canvas?.clientWidth||1)/(this.canvas?.clientHeight||1),i=Math.min(Math.PI/8,Math.atan(Math.tan(Math.PI/8)*t)),s=H(V(this.camera.position,this.camera.target));this.camera.target=e.center,this.camera.position=re(e.center,oe(Math.hypot(...s)?s:[0,0,1],e.radius/Math.sin(i)*1.08)),this._invalidate()}set_auto_rotate(e){this.auto_rotate=!!e,this._invalidate()}set_mouse_zoom(e){this.mouse_zoom=!!e,this.controls&&(this.controls.noZoom=!e)}set_auto_resize(e){this.auto_resize=!!e,this._resizeObserver?.disconnect(),this._windowResize&&window.removeEventListener("resize",this._windowResize),!(!e||!this.canvas)&&(typeof ResizeObserver<"u"&&(this._resizeObserver=new ResizeObserver(()=>this.do_resize()),this._resizeObserver.observe(this.parent_element)),this._windowResize=()=>this.do_resize(),window.addEventListener("resize",this._windowResize))}do_resize(){if(!this.renderer||this.killsign)return;let e=this.canvas.getBoundingClientRect();this.renderer.resize(e.width||this.parent_element.clientWidth||300,e.height||this.parent_element.clientHeight||150,this.options.pixel_ratio??Math.min(globalThis.devicePixelRatio||1,2)),this._invalidate()}set_on_model_mousedown(e){this.onmousedown_callback=e}set_on_model_mouseclick(e){this.set_on_model_mousedown(e)}set_drag_and_drop(e){this.allow_drag_and_drop=!!e,this.canvas&&(this.canvas.removeEventListener("drop",this._drop),this.canvas.removeEventListener("dragover",this._drag),e&&(this.canvas.addEventListener("drop",this._drop),this.canvas.addEventListener("dragover",this._drag)))}get_camera_state(){return{position:tt(this.camera.position),up:tt(this.camera.up),target:tt(this.camera.target)}}set_camera_state(e){return this._guard(()=>{let t=e.position?At(e.position,"position"):this.camera.position,i=e.up?At(e.up,"up"):this.camera.up,s=e.target?At(e.target,"target"):this.camera.target;if(!Math.hypot(...i)||!Math.hypot(...V(t,s)))throw new Error("Degenerate camera state");this.camera.position=t,this.camera.up=H(i),this.camera.target=s,this._invalidate()})}set_camera(e,t,i){return this.set_camera_state({position:{x:e??this.camera.position[0],y:t??this.camera.position[1],z:i??this.camera.position[2]}})}pick_model(e,t){if(!this.ready||this.killsign||!Number.isFinite(e)||!Number.isFinite(t))return null;let i=this.canvas.getBoundingClientRect();if(!i.width||!i.height||e<i.left||e>i.right||t<i.top||t>i.bottom)return null;let s=fe(Ye(this.camera,i.width/i.height)),r=le(s,[(e-i.left)/i.width*2-1,1-(t-i.top)/i.height*2,1]),n=H(V(r,this.camera.position)),d=null,l=1/0;for(let c of this._models.values())if(c.geometry&&c.opacity>0&&c.color!=="transparent"){let m=fe(X(c)),_=lo(c.geometry,le(m,this.camera.position),le(m,n,0),We(c.geometry.bounds,c.section));_<l&&(l=_,d=c.id)}return d===null?null:{id:d,distance:l}}_pick(e,t){if(!this.onmousedown_callback)return;let i=this.pick_model(e.clientX,e.clientY);(i||this.send_no_model_click_event)&&this._callback("onmousedown_callback",i?.id??null,e,i?.distance??0,t)}get_model_info(e){return this._guard(()=>{let t=this._model(e),i=t.geometry;if(!i)return null;let s=t.scaleVector,r=s.join(",");(!t._stats||t._stats.key!==r)&&(t._stats={key:r,...co(i,s)});let n=t.units==="inch"?1/25.4:1;return{name:t.filename,orig_filename:t.orig_filename??null,position:{x:t.x,y:t.y,z:t.z},dims:tt(i.bounds.min.map((d,l)=>(i.bounds.max[l]-d)*s[l])),rotation:{x:t.rotationx,y:t.rotationy,z:t.rotationz},display:t.display,smooth_shading:t.smooth_shading,section:t.section?{...t.section}:null,color:t.color??(i.colors?"#ffffff":"#909090"),scale:{x:t.scalex,y:t.scaley,z:t.scalez},volume:t._stats.volume*n**3,area:t._stats.area*n**2,triangles:i.triangles,bounds:{min:[...i.bounds.min],max:[...i.bounds.max]},has_colors:!!i.colors,source_units:i.units||null,units:t.units,opacity:t.opacity,file_bytes:i.file_bytes??null}})}get_model_data(e){return this._guard(()=>{let t=this._model(e);return t.geometry?{positions:t.geometry.positions.slice(),colors:t.geometry.colors?.slice()??null}:null})}get_model_mesh(e){return this._guard(()=>{let t=this._model(e);return t.geometry?ko(t,this.options.three??globalThis.THREE):null})}get_stl_bin(e){return this._guard(()=>{let t=this._model(e);return t.geometry?ho(t.geometry,t.scaleVector,t.center):null})}check_printability(e={}){return bo.call(this,e)}is_printability_current(e){return et(this,e)}show_printability(e,t){return vo.call(this,e,t)}clear_printability(){this.renderer?.setDiagnostic(null),this._invalidate()}async export_models(e={}){let t=wo(e),i=e.signal,s=()=>{throw new DOMException("Export cancelled","AbortError")};if(i?.aborted&&s(),await this.when_ready,i?.aborted&&s(),this.killsign)throw new Error("Viewer disposed");let r=e.model_ids;if(r==="selected"){let f=this.renderer?.selection;r=f?[f.id]:[]}else r===void 0&&(r=[...this._models.keys()]);if(!Array.isArray(r)||r.some(f=>!Number.isSafeInteger(f))||new Set(r).size!==r.length)throw new Error("model_ids must be an array of unique model IDs or 'selected'");if(!r.length)throw new Error("No models to export");let n=r.map(f=>{let u=this._model(f);if(!u.geometry)throw new Error(`Model ${f} has not finished loading`);return u});if(n.reduce((f,u)=>f+u.geometry.triangles,0)>this.limits.max_triangles)throw new Error("Export triangle limit exceeded");let l=xo(n,t),c=this._occupiedMemory();K(this.limits,c+l,"export"),this._exportBytes=(this._exportBytes||0)+l;let m,_,w;try{let f=n.map(p=>{let y=t.apply_transforms?p:{...p,center:[0,0,0],x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,scaleVector:[p.unitScale,p.unitScale,p.unitScale]},g=X(y),A=t.units==="inch"?1/25.4:1;for(let k=0;k<4;k++)for(let E=0;E<3;E++)g[k*4+E]*=A;return{id:p.id,name:ot(p.filename),matrix:g,positions:p.geometry.positions.slice(),colors:t.colors?p.geometry.colors?.slice():null,color:t.colors&&p.color&&p.color!=="transparent"?Y(p.color):null,opacity:p.color==="transparent"?0:p.opacity}}),u=f.flatMap(p=>[p.positions.buffer,...p.colors?[p.colors.buffer]:[]]);m=this._pool.run({kind:"export",models:f,options:t,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-c}},p=>{try{e.on_progress?.(p)}catch(y){w=y,m.cancel()}},u),_=()=>m.cancel(),i?.addEventListener("abort",_,{once:!0}),i?.aborted&&_();try{return await m}catch(p){throw w||p}}finally{_&&i?.removeEventListener("abort",_),this._exportBytes-=l}}async download_models(e={}){let t=await this.export_models(e);if(e.signal?.aborted)throw new DOMException("Export cancelled","AbortError");return Ce(t.blob,t.filename),t}download_model(e,t){let i=this.get_stl_bin(e);i&&Ce(new Blob([i],{type:"model/stl"}),t??this._model(e).filename??"model.stl")}async export_scene(e={}){let t=String(e.format||"").toLowerCase();if(!["vsb","vsj"].includes(t))throw new Error("Scene format must be vsb or vsj");if(e.force_basename!==void 0&&typeof e.force_basename!="boolean")throw new Error("force_basename must be a boolean");if(e.on_progress!==void 0&&typeof e.on_progress!="function")throw new Error("on_progress must be a function");let i=e.signal,s=()=>{if(i?.aborted)throw new DOMException("Export cancelled","AbortError")};if(s(),await this.when_ready,s(),this.killsign)throw new Error("Viewer disposed");if(this._pending.size||this._sceneLoads.size)throw new Error("Wait for models to finish opening before saving the scene");let r=[...this._models.values()];if(!r.length)throw new Error("No models to export");if(r.some(u=>!u.geometry))throw new Error("A model has not finished loading");if(r.length+(t==="vsb"?1:0)>this.limits.max_archive_entries)throw new Error("Scene model count limit exceeded");let n=r.reduce((u,p)=>u+p.geometry.triangles,0);if(n>this.limits.max_triangles)throw new Error("Scene triangle limit exceeded");let d=this.get_vsj(!0,!!e.force_basename,t==="vsb"),l=JSON.stringify(d),c=new Blob([l]).size;if(c>this.limits.max_file_bytes)throw new Error("Scene manifest size limit exceeded");if(t==="vsb"&&c+r.length*84+n*50>this.limits.max_uncompressed_bytes)throw new Error("Scene archive limit exceeded");let m=67108864+l.length*8+(t==="vsb"?n*512:0);K(this.limits,this._occupiedMemory()+m,"scene export"),this._exportBytes+=m;let _,w,f;try{e.on_progress?.({loaded:0,total:r.length,phase:"exporting"}),s();let u;if(t==="vsj")u=new Blob([l],{type:"application/json"}),e.on_progress?.({loaded:r.length,total:r.length,phase:"complete"});else{let p=r.map(y=>({id:y.id,triangles:y.geometry.triangles,positions:y.geometry.positions.slice(),colors:y.geometry.colors?.slice()||null,scale:y.scaleVector,center:[...y.center]}));_=this._pool.run({kind:"export-scene",scene:d,models:p,limits:this.limits},y=>{try{e.on_progress?.(y)}catch(g){f=g,_.cancel()}},p.flatMap(y=>[y.positions.buffer,...y.colors?[y.colors.buffer]:[]])),w=()=>_.cancel(),i?.addEventListener("abort",w,{once:!0}),i?.aborted&&w();try{u=await _}catch(y){throw f||y}}return s(),{blob:u,format:t,filename:St(e.filename,t),model_ids:r.map(p=>p.id),triangles:n,representation:"scene"}}finally{w&&i?.removeEventListener("abort",w),this._exportBytes-=m}}async download_scene(e={}){let t=await this.export_scene(e);if(e.signal?.aborted||this.killsign)throw new DOMException("Export cancelled","AbortError");return Ce(t.blob,t.filename),t}get_vsj(e=!1,t=!1,i=!1){let s={canvas_height:this.canvas_height,bg_color:this.bg_color,camera_state:this.get_camera_state(),auto_rotate:this.auto_rotate,mouse_zoom:this.mouse_zoom,auto_resize:this.auto_resize,center_models:this.center_models,models:[]};this.grid&&(s.grid=!0,this.grid.infinite&&(s.infinite_grid={spacing:this.grid.spacing,height:this.grid.height}));for(let r of this._models.values())if(r.geometry){let n={id:i?-1:r.id,filename:i?`${r.id}.stl`:t?ot(r.filename):r.filename,units:r.units,display:r.display,opacity:r.opacity,view_edges:r.view_edges,smooth_shading:r.smooth_shading,section:r.section?{...r.section}:null};i&&(n.orig_filename=ot(r.orig_filename||r.filename));for(let d of Le)d.startsWith("scale")||(n[d]=r[d]);i||(n.scalex=r.scalex*r.unitScale,n.scaley=r.scaley*r.unitScale,n.scalez=r.scalez*r.unitScale),r.color&&(n.color=r.color),r.animation&&(n.animation=structuredClone(r.animation)),s.models.push(n)}return e?s:JSON.stringify(s)}download_vsj(e="1.vsj"){Ce(new Blob([this.get_vsj()],{type:"application/json"}),e)}get_vsb(){return{vsj:this.get_vsj(!0,!0,!0),files:[...this._models.values()].filter(e=>e.geometry).map(e=>({id:e.id,bin:this.get_stl_bin(e.id)}))}}async get_vsb_blob(){await this.when_ready;let e=this.get_vsb(),t=this._pool.run({kind:"zip",scene:e,limits:this.limits},null,e.files.map(i=>i.bin));return new Blob([await t],{type:"application/zip"})}download_vsb(e="1.vsb"){return this.get_vsb_blob().then(t=>Ce(t,/\.vsb$/i.test(e)?e:`${e}.vsb`)).catch(t=>this._error(t))}import_scene(e,t={}){return go.call(this,e,t)}_sceneLoad(e,t){let i=this._generation;this._sessionActive=!0;let s=(async()=>{if(await this.when_ready,i!==this._generation||this.killsign)return;let r,n,d=document.baseURI;if(typeof e=="string"&&(d=new URL(e,document.baseURI).href),t){if(n=await this._pool.run({kind:"unzip",source:typeof e=="string"?d:e,limits:this.limits}),!Object.hasOwn(n,"json_data.vsj"))throw new Error("VSB manifest json_data.vsj is missing");r=JSON.parse(new TextDecoder().decode(n["json_data.vsj"]))}else{let l=new AbortController;this._aborters.add(l);try{r=JSON.parse(new TextDecoder().decode(await Mo(typeof e=="string"?d:e,this.limits.max_file_bytes,()=>{},l.signal)))}finally{this._aborters.delete(l)}}if(!(i!==this._generation||this.killsign)){if(!r||!Array.isArray(r.models)||r.models.length>this.limits.max_archive_entries)throw new Error("Invalid or oversized scene manifest");r.bg_color!==void 0&&this.set_bg_color(r.bg_color),r.center_models!==void 0&&this.set_center_models(r.center_models),r.auto_rotate!==void 0&&this.set_auto_rotate(r.auto_rotate),r.mouse_zoom!==void 0&&this.set_mouse_zoom(r.mouse_zoom),r.auto_resize!==void 0&&this.set_auto_resize(r.auto_resize),r.canvas_height!==void 0&&(this.canvas_height=r.canvas_height,this._setSizeStyle(),this.do_resize()),r.infinite_grid?this.set_infinite_grid(!0,r.infinite_grid.spacing,r.infinite_grid.height):r.grid&&this.set_grid(!0),r.camera_state&&(this._restoreCamera=r.camera_state);for(let l of r.models){if(!l||typeof l!="object")throw new Error("Invalid scene model");let c=So(l);if(delete c.mesh,delete c.data,delete c.local_file,n){if(typeof c.filename!="string"||!Object.hasOwn(n,c.filename))throw new Error("VSB model file is missing");c.local_file=new File([n[c.filename]],c.filename)}else c.filename&&(c.filename=new URL(c.filename,d).href);this.add_model(c)}}})().catch(r=>{i===this._generation&&r.name!=="AbortError"&&this._error(r)}).finally(()=>{this._sceneLoads.delete(s),this._finishSession()});this._sceneLoads.add(s)}load_vsj(e){return this._sceneLoad(e,!1)}load_vsb(e){return this._sceneLoad(e,!0)}animate_model(e,t){return this._guard(()=>{let i=this._model(e);if(!t){i.animation=null,i._animation=null;return}let s=structuredClone(t),r=[];for(let n of["delta","exact"])if(s[n]){let d=s[n];me(d.msec??300,"animation duration");let l={};for(let c of Le){let m=d[c]??(c.startsWith("scale")?d.scale:void 0);m!==void 0&&(Q(m,c),n==="exact"&&c.startsWith("scale")&&me(m,c),l[c]=m)}r.push({mode:n,values:l,duration:d.msec??300,loop:n==="delta"&&!!d.loop,from:Object.fromEntries(Le.map(c=>[c,i[c]]))})}if(!r.length)throw new Error("Animation needs delta or exact");i.animation=s,i._animation={start:null,tracks:r},this._invalidate()})}_animate(e,t){let i=e._animation;i.start??=t;let s=!1;for(let r of i.tracks){let n=(t-i.start)/r.duration,d=r.loop?n:Math.min(n,1);s||=r.loop||n<1;for(let[l,c]of Object.entries(r.values))e[l]=l.startsWith("scale")?Math.max(1e-6,r.mode==="delta"?r.from[l]+c*d:r.from[l]+(c-r.from[l])*d):r.mode==="delta"?r.from[l]+c*d:r.from[l]+(c-r.from[l])*d}e._stats=null,s||(e._animation=null,e.animation=null)}async get_image_blob(e="image/png"){await this.when_idle(),this._clip(),this.renderer.draw([...this._models.values()],this.camera,this.bg_color,!1);let t=new Promise((i,s)=>this.canvas.toBlob(r=>r?i(r):s(new Error("Screenshot failed")),e));return this._invalidate(),t}};var zt=new WeakMap,Yo=["background-color","background-value","cancel-load","clear-scene","close-colors","close-info","close-model","close-scene","close-section","close-view","color-mode-note","colors-button","colors-panel","depth-controls","depth-model-button","dismiss-notice","drop-message","drop-overlay","empty-state","file-heading","file-input","filename","finish-move","fit-button","grid-button","image-button","info-button","info-dimensions","info-panel","info-size","info-triangles","live-status","loading-label","loading-state","memory-budget","model-color","model-color-value","model-controls","model-list","model-tool","move-controls","move-gesture-message","move-mode","move-mode-name","move-model-button","notice","notice-message","open-label","position-details","position-error","position-units","position-x","position-y","position-z","reset-position","reset-rotation","restore-color","rotate-controls","rotate-model-button","rotation-error","rotation-x","rotation-y","rotation-z","sample-button","scene-arrangement","scene-button","scene-count","scene-panel","section-axis","section-button","section-enabled","section-flip","section-panel","section-position","section-value","smooth-shading","unit-note","view-button","view-description","view-label","view-panel","view-status","viewer","workspace"];function Ao(o,e={}){if(!o?.querySelectorAll)throw new TypeError("A Document or HTMLElement root is required");if(zt.has(o))throw new Error("ViewSTL app is already mounted on this root");let t=o.ownerDocument||o,i=t.defaultView,s=i.URL,r=new Map([...o.querySelectorAll("[id]")].map(a=>[a.id,a])),n=a=>r.get((e.id_prefix||"")+a),d=Yo.filter(a=>!n(a));if(Et.some(a=>n(a))&&d.push(...Et.filter(a=>!n(a))),kt.some(a=>n(a))&&d.push(...kt.filter(a=>!n(a))),d.length)throw new Error("Missing ViewSTL controls: "+d.join(", "));let l=new i.AbortController,c=o===t?i:o,m=!1,_=null,w=null,f=()=>{if(m)throw new Error("ViewSTL app is disposed")},u=(a,h,b,S={})=>a.addEventListener(h,b,{...S,signal:l.signal}),p=Vt(i.navigator.deviceMemory),y=["view","section","info","scene","colors"],g=new Map,A=[],k=["#64a5b2","#dd766b","#edb14c","#7ca984","#927fbc","#5a8ec7","#89949d"],E,x,C=0,I=0,v=null,F=0,z=null,P,L,D,M=!1,R=!1,j=!1,O=0,we=1,J=!1,q="move",W,ee,ue="transparent",zo={flat:"Solid",edges:"Solid + edges",wireframe:"Wireframe",xray:"X-ray",mesh:"Mesh overlay",studio:"Studio",orientation:"Face orientation"},ye=[...o.querySelectorAll('[name="view-mode"]')];e.id_prefix&&ye.forEach(a=>a.name=e.id_prefix+"view-mode");let it=e.sample_url,Ie=new Intl.NumberFormat(void 0,{maximumFractionDigits:2});function B(a){m||(n("live-status").textContent=a)}function ne(a){m||(n("notice-message").textContent=a,n("notice").hidden=!1)}function xe(){x?.set_selection(!R&&g.has(v)&&(J||!n("scene-panel").hidden)?v:null)}function Rt(a){n(a+"-panel").hidden=!0,n(a+"-button").setAttribute("aria-expanded","false"),xe()}function ce(){y.forEach(Rt)}function Pe(a){let h=n(a+"-panel").hidden;ce(),n(a+"-panel").hidden=!h,n(a+"-button").setAttribute("aria-expanded",String(h)),xe()}function Me(a){return a==null?"Sample model":a<1024**2?`${Ie.format(a/1024)} KiB`:a<1024**3?`${Ie.format(a/1024**2)} MiB`:`${Ie.format(a/1024**3)} GiB`}function rt(){let a=g.get(v),h=/^#[a-f0-9]{6}$/i.test(a?.color)?a.color:"#ffffff";n("model-color").value=h,n("model-color-value").textContent=a?.color===null?"File colors":h.toUpperCase(),n("restore-color").textContent=a?.embedded?"Restore file colors":"Restore original color",o.querySelectorAll("[data-model-color]").forEach(b=>b.setAttribute("aria-pressed",String(b.dataset.modelColor===a?.color))),o.querySelectorAll("[data-background]").forEach(b=>b.setAttribute("aria-pressed",String(b.dataset.background===ue))),n("background-color").value=ue==="transparent"?"#fafbfb":ue,n("background-value").textContent=ue==="transparent"?"Default":ue.toUpperCase()}function Lt(){let a=v===null?null:x.get_model_info(v),h=a?.display==="smooth"?"flat":a?.display||"flat";for(let T of ye)T.checked=T.value===h,T.disabled=R;n("view-label").textContent=zo[h],n("smooth-shading").checked=a?.smooth_shading||!1,n("smooth-shading").disabled=R||h==="wireframe",n("view-status").textContent=h==="wireframe"?"Smooth shading is available in surface views.":"Shading changes the appearance, not the geometry.";let b=h==="orientation"?"Blue: front faces \xB7 Red: back faces":"";n("view-description").textContent=b,n("view-description").hidden=!b,n("color-mode-note").hidden=h!=="orientation";let S=a?.section;n("section-enabled").checked=!!S,n("section-axis").value=S?.axis||"z",n("section-position").value=(S?.position??.5)*100,n("section-value").textContent=n("section-position").value+"%",n("section-flip").setAttribute("aria-pressed",String(S?.flip||!1)),n("section-button").setAttribute("aria-pressed",String(!!S)),o.querySelectorAll("[data-selected-name]").forEach(T=>T.textContent=g.get(v)?.name||""),rt(),st(),P?.sync(),L?.sync()}function Ct(){if(v===null)return;let a=x.get_model_info(v),h=g.get(v),b=h.units==="mm"?" mm":"";n("info-triangles").textContent=a.triangles.toLocaleString(),n("info-dimensions").textContent=[a.dims.x,a.dims.y,a.dims.z].map(S=>Ie.format(S)).join(" \xD7 ")+b,n("info-size").textContent=Me(h.size),n("unit-note").textContent=b?"Dimensions are shown in millimetres.":"Dimensions are in model units. STL and OBJ files do not specify physical units."}function It(){n("model-list").replaceChildren(...[...g.values()].map(a=>{let h=t.createElement("li"),b=t.createElement("button"),S=t.createElement("button"),T=t.createElement("button");return h.className="model-row",h.classList.toggle("selected",a.id===v),b.className="model-swatch",b.dataset.colorModel=a.id,b.style.backgroundColor=a.color||"#b7c5c9",b.setAttribute("aria-label","Color for "+a.name),b.title="Change color",S.className="model-pick",S.dataset.selectModel=a.id,S.textContent=a.name,S.title=a.name,S.setAttribute("aria-pressed",String(a.id===v)),T.className="icon-button",T.dataset.removeModel=a.id,T.textContent="\xD7",T.setAttribute("aria-label","Remove "+a.name),h.append(b,S,T),h})),n("scene-count").textContent=g.size,n("scene-arrangement").disabled=g.size===0}function be(){let a=g.size>0;n("workspace").classList.toggle("has-models",a),n("viewer").hidden=!a&&!R,n("empty-state").hidden=a||R,n("file-heading").hidden=!a,n("model-controls").hidden=!a,n("filename").textContent=g.get(v)?.name||"",n("filename").title=n("filename").textContent,n("open-label").textContent=a?"Add models":"Open files",n("drop-message").textContent=a?"Add to your scene.":"Drop it here.",It(),Lt(),Ct(),a||ce(),xe()}function Te(a){a&&P?.close(),!m&&(a&&G(!1),R=a,n("loading-state").hidden=!a,n("workspace").setAttribute("aria-busy",String(a)),be())}function Fe(a){P?.close(),g.has(a)&&(G(!1),I++,v=a,be())}function Ro(){return Qt([...g.values()],n("scene-arrangement").value)}function Oe(){x?.set_infinite_grid(j,we,O),n("grid-button").setAttribute("aria-pressed",String(j))}function nt(){if(G(!1),n("scene-arrangement").value==="original")for(let h of g.values())h.rotation={...h.originalRotation||{x:0,y:0,z:0}};let a=Ro();for(let h of a)g.get(h.id).position={x:h.x,y:h.y,z:h.z},at(g.get(h.id));(n("scene-arrangement").value!=="custom"||!g.size)&&(O=$t([...g.values()],a)),st(),Oe(),x?.set_auto_zoom()}function st(){let a=g.get(v);for(let h of["x","y","z"]){let b=n("position-"+h);b.value=Number((a?.position?.[h]||0).toPrecision(12)),b.disabled=!a,b.removeAttribute("aria-invalid")}n("position-error").hidden=!0,n("position-units").textContent=a?.units==="mm"?"Offset from file coordinates, in mm.":"Offset from file coordinates, in model units.",n("move-model-button").disabled=!a||R,n("reset-position").disabled=!a,Tt();for(let h of["rotate-model-button","depth-model-button","depth-nearer","depth-farther"])n(h).disabled=!a||R}function Ee(a,h,b=!0){return!g.has(a)||!Object.values(h).every(S=>Number.isFinite(S)&&Math.abs(S)<=1e12)?!1:(g.get(a).position={...h},at(g.get(a)),n("scene-arrangement").value="custom",a===v&&b&&st(),!0)}function at(a){let h=gt(a),b=a.rotation;x.set_rotation(a.id,...["x","y","z"].map(S=>b[S]%360*Math.PI/180)),x.set_position(a.id,h.x,h.y,h.z)}function Pt(){let a=g.get(v);return a?{...a,center:Xt(a),radius:Kt(a)}:null}function Tt(){let a=g.get(v);for(let h of["x","y","z"]){let b=n("rotation-"+h);b.value=Number((a?.rotation?.[h]||0).toPrecision(10)),b.disabled=!a,b.removeAttribute("aria-invalid")}n("rotation-error").hidden=!0,n("reset-rotation").disabled=!a}function Ne(a,h,b=!0){return!g.has(a)||!Object.values(h).every(S=>Number.isFinite(S)&&Math.abs(S)<=36e4)?!1:(g.get(a).rotation={...h},at(g.get(a)),n("scene-arrangement").value="custom",b&&a===v&&Tt(),!0)}let Lo={move:"Move",rotate:"Rotate",depth:"Depth"},Ft={move:"Drag to move the selected model",rotate:"Drag to rotate the selected model",depth:"Drag up for nearer, down for farther"};function Co(){x?.canvas&&(x.canvas.dataset.modelTool=q),o.querySelectorAll("[data-transform-tab]").forEach(a=>a.setAttribute("aria-pressed",String(a.dataset.transformTab===q)));for(let a of["move","rotate","depth"])n(a+"-controls").hidden=a!==q,n(a+"-model-button").setAttribute("aria-pressed",String(J&&a===q));n("model-tool").value=q,n("move-gesture-message").textContent=Ft[q]+" \xB7 Scroll to adjust depth"}function G(a,h=q){W?.setEnabled(!1),q=h,J=!!a&&v!==null&&!R,W?.setEnabled(J),Co(),n("move-mode").hidden=!J,n("move-mode-name").textContent=g.get(v)?.name||"",n("workspace").classList.toggle("moving-model-mode",J),x?.canvas&&x.canvas.setAttribute("aria-label",J?Ft[q]+". Scroll changes only its depth. Escape exits.":"3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."),J&&(ce(),x.canvas.focus({preventScroll:!0}),B(Lo[q]+" tool for "+g.get(v).name+". Done or Escape returns to viewing.")),xe()}for(let a of["x","y","z"]){let h=n("rotation-"+a);u(h,"input",()=>{v===null||!h.value.trim()||Ne(v,{...g.get(v).rotation,[a]:h.valueAsNumber},!1)&&(h.removeAttribute("aria-invalid"),n("rotation-error").hidden=!0)}),u(h,"change",()=>{v!==null&&(!h.value.trim()||!Ne(v,{...g.get(v).rotation,[a]:h.valueAsNumber})?(h.setAttribute("aria-invalid","true"),n("rotation-error").hidden=!1):B("Model rotation updated."))})}u(n("reset-rotation"),"click",()=>{G(!1),v!==null&&(Ne(v,{...g.get(v).originalRotation||{x:0,y:0,z:0}}),B("Selected model rotation reset."))});for(let a of["move","rotate","depth"])u(n(a+"-model-button"),"click",()=>G(!J||q!==a,a));for(let[a,h]of[["depth-nearer",-.12],["depth-farther",.12]])u(n(a),"click",()=>{let b=Pt();b&&(Ee(b.id,Ae(b,x.get_camera_state(),h)),B(h<0?"Selected model moved nearer.":"Selected model moved farther."))});o.querySelectorAll("[data-transform-tab]").forEach(a=>u(a,"click",()=>G(!1,a.dataset.transformTab))),u(n("model-tool"),"change",()=>G(!0,n("model-tool").value));for(let a of["x","y","z"]){let h=n("position-"+a);u(h,"input",()=>{v===null||!h.value.trim()||Ee(v,{...g.get(v).position,[a]:h.valueAsNumber},!1)&&(h.removeAttribute("aria-invalid"),n("position-error").hidden=!0)}),u(h,"change",()=>{v!==null&&(!h.value.trim()||!Ee(v,{...g.get(v).position,[a]:h.valueAsNumber})?(h.setAttribute("aria-invalid","true"),n("position-error").hidden=!1):B("Model position updated."))})}u(n("finish-move"),"click",()=>{G(!1),x?.canvas.focus({preventScroll:!0})}),u(n("reset-position"),"click",()=>{G(!1),v!==null&&(Ee(v,{...g.get(v).originalPosition||{x:0,y:0,z:0}}),B("Selected model restored to its original position."))}),u(i,"blur",()=>{J&&G(!1)});function De(){C++,A.length=0,D?.abort(),z!==null&&(x?.remove_model(z),z=null),Te(!1),B("Opening cancelled.")}function Ot(){P?.close(),G(!1),De(),I++,x?.clean(),g.clear(),v=null,j=!1,O=0,we=1,n("scene-arrangement").value="original",Oe(),n("notice").hidden=!0,be(),B("Scene cleared.")}function lt(a){if(!g.has(a))return;P?.close();let h=g.get(a).name;G(!1),x.remove_model(a),g.delete(a),I++,v===a&&(v=[...g.keys()].at(-1)??null),nt(),be(),B(h+" removed.")}async function Nt(){return E||(E=Promise.resolve().then(async()=>{if(m)throw new Error("ViewSTL app is disposed");if(x=new _e(n("viewer"),{...p.limits,worker_count:1,worker_timeout:e.worker_timeout??6e5,worker_url:e.worker_url,worker_factory:e.worker_factory,url_import_endpoint:e.url_import_endpoint,center_models:!1,bg_color:ue,allow_drag_and_drop:!1,camera_state:{position:{x:90,y:-130,z:90},target:{x:0,y:0,z:0},up:{x:0,y:0,z:1}},loading_progress_callback:a=>{if(m||!R||D)return;let h=a[z];n("loading-label").textContent=h?.phase==="loading-cad"?"Loading the CAD reader\u2026":h?.phase==="parsing-cad"?"Preparing CAD surfaces\u2026":h?.loaded&&!h.total?`Downloading your file\u2026 ${Me(h.loaded)}`:h?.total&&h.loaded<h.total?`Reading your file\u2026 ${Math.min(99,Math.floor(100*h.loaded/h.total))}%`:"Preparing your model\u2026"},model_loaded_callback:a=>{w=a},load_error_callback:a=>{ne(/limit exceeded|safety budget/i.test(a)?`This scene or view mode exceeds the estimated memory budget for this device (${Me(p.budget)}). Remove a model, use Solid view without smoothing, or try a device with more memory.`:`Couldn\u2019t display this model. ${a}`)}}),await x.when_ready,m)throw new Error("ViewSTL app is disposed");return W=to(x.canvas,{getCamera:()=>x.get_camera_state(),getModel:Pt,getTool:()=>q,onRotate:(a,h)=>Ne(a,h),onMove:(a,h)=>Ee(a,h),onStart:()=>{ee=n("scene-arrangement").value},onEnd:(a,h)=>{h&&(n("scene-arrangement").value=ee),B(h?"Move cancelled.":"Model updated.")}}),u(x.canvas,"dblclick",a=>{if(R||a.button!==0)return;let h=x.pick_model(a.clientX,a.clientY);!h||!g.has(h.id)||(a.preventDefault(),Fe(h.id),ce(),n("scene-panel").hidden=!1,n("scene-button").setAttribute("aria-expanded","true"),n("position-details").open=!0,xe(),B(g.get(h.id).name+" selected. Choose Move, Rotate or Depth."))}),x}).catch(a=>{throw W?.dispose(),W=null,x?.dispose(),x=null,E=null,a})),E}function je(a){f();let h=[];for(let S of a){if(!/\.(stl|obj|3mf|step|stp|iges|igs|vsb|vsj)$/i.test(S.name)){ne("Choose STL, OBJ, 3MF, STEP/STP, IGES/IGS, VSB or VSJ files.");continue}if(S.size===0){ne("This file is empty. Choose a file containing a 3D model.");continue}h.push(S)}let b=h.filter(S=>pe(S.name));if(b.length){let S={files:h.filter(T=>!pe(T.name)),used:new Set,failed:!1};for(let T of b)A.push({name:T.name,sceneFile:T,batch:S});A.push({remaining:S})}else A.push(...h);return A.length&&!M&&(_=Dt()),_||Promise.resolve()}function ct(a,h=""){if(f(),!e.url_import_endpoint)throw new Error("URL imports are not configured.");let b=new s(a.trim());if(!["https:","http:"].includes(b.protocol)||b.username||b.password)throw new Error("Use a public HTTP or HTTPS file URL without login credentials.");if(h&&!/^(stl|obj|3mf|step|stp|iges|igs|vsb|vsj)$/.test(h))throw new Error("Choose a supported 3D format.");b.hash="";let S=decodeURIComponent(b.pathname.split("/").at(-1)||"Remote model").slice(0,200);return A.push({name:S,remote:b.href,format:h,size:0,scene:["vsb","vsj"].includes(h)?h:pe(S)}),M||(_=Dt()),_||Promise.resolve()}n("url-form")&&n("url-dialog")&&u(n("url-form"),"submit",a=>{a.preventDefault();try{let h=ct(n("url-input").value,n("url-format").value);n("url-input").value="",n("url-error").hidden=!0,n("url-dialog").close(),h.catch(b=>ne(b.message))}catch(h){n("url-error").textContent=h.message,n("url-error").hidden=!1}});async function Dt(){if(!M){M=!0;try{for(;A.length;){let a=A.shift(),h=C;if(a.remaining){a.remaining.failed||A.unshift(...a.remaining.files.filter(b=>!a.remaining.used.has(b)));continue}Te(!0),n("notice").hidden=!0,n("loading-label").textContent="Opening "+a.name+"\u2026",B("Opening "+a.name);try{if(a.sceneFile||a.scene){await Io(a,h);continue}let b=await Gt(a);if(h!==C)continue;let S=[...g.values()].reduce((U,he)=>U+he.estimated,0);if(b+S>p.budget||a.size>p.limits.max_file_bytes)throw Error(`Adding this file would exceed the estimated ${Me(p.budget)} scene memory budget. Remove a model or try a device with more memory.`);let T=await Nt();if(h!==C)continue;T.do_resize(),F=Math.max(F,T.max_model_id+1);let $=F++;if(z=$,w=null,T.add_model({id:$,display:"flat",...a.remote?{remote_url:a.remote,remote_format:a.format}:a.url?{filename:a.url}:{local_file:a}}),await T.when_idle(),h!==C)continue;z=null;let se=w===$?T.get_model_info($):null;if(!se){B(a.name+" could not be opened.");continue}a.remote&&(a.name=se.name,a.size=se.file_bytes,delete a.remote);let de=se.has_colors,N=de?null:k[g.size%k.length];N&&T.set_color($,N),g.set($,{id:$,name:a.name,size:a.size,units:se.source_units||(/\.3mf$/i.test(a.name)?"mm":null),bounds:se.bounds,position:{x:0,y:0,z:0},rotation:{x:0,y:0,z:0},embedded:de,originalColor:N,color:N,estimated:se.triangles*224,sourceFile:a instanceof i.File?a:null}),v=$,I++,nt(),be(),B(a.name+" is ready."),T.canvas.focus({preventScroll:!0})}catch(b){a.batch&&(a.batch.failed=!0),h===C&&(z!==null&&(x?.remove_model(z),z=null),ne(/WebGL/i.test(b.message)?"3D viewing needs WebGL 2. Try enabling hardware acceleration in your browser, or use another device.":b.message))}}}finally{M=!1,Te(!1)}}}async function Io(a,h){let b=await Nt();if(h!==C)return;let S=new i.AbortController;D=S;try{let T=a.batch?.files||[],$=[...new Set([...g.values()].map(N=>N.sourceFile).filter(Boolean))],se=[...T,...a.sceneFile?$.filter(N=>!T.some(U=>U.name===N.name)&&$.filter(U=>U.name===N.name).length===1):[]],de=await b.import_scene(a.sceneFile||a.remote,{format:a.scene||pe(a.name),signal:S.signal,files:se,on_progress(N){h!==C||m||(n("loading-label").textContent=N.phase==="opening-model"?`Opening scene model ${N.loaded+1} of ${N.total}\u2026`:N.phase==="complete"?"Restoring scene\u2026":N.total?`Reading scene\u2026 ${Math.min(99,Math.floor(N.loaded/N.total*100))}%`:"Reading scene\u2026")}});if(h!==C)return;for(let N of de.used_files)a.batch?.used.add(N);for(let N of de.model_ids){let U=b._models.get(N),he=U.geometry,Po=Object.fromEntries(["min","max"].map(ve=>[ve,he.bounds[ve].map((Fo,Ut)=>(Fo-U.center[Ut])*U.scaleVector[Ut])])),ae={id:N,name:String(U.orig_filename||U.filename).split(/[\\/]/).pop().split(/[?#]/)[0],size:he.file_bytes??U.local_file?.size??null,units:he.units||U.units,bounds:Po,position:{x:0,y:0,z:0},rotation:{x:U.rotationx*180/Math.PI,y:U.rotationy*180/Math.PI,z:U.rotationz*180/Math.PI},embedded:!!he.colors,color:U.color||(he.colors?null:"#909090"),estimated:he.triangles*224,sourceFile:de.used_files.includes(U.local_file)?U.local_file:null},To=gt(ae);for(let ve of["x","y","z"])ae.position[ve]=U[ve]-To[ve];ae.originalPosition={...ae.position},ae.originalRotation={...ae.rotation},ae.originalColor=ae.color,g.set(N,ae)}F=Math.max(F,b.max_model_id+1),v=de.model_ids.at(-1),I++,n("scene-arrangement").value="custom",j=!!b.grid,O=b.grid?.height||0,we=b.grid?.spacing||1,n("grid-button").setAttribute("aria-pressed",String(j)),ht(b.bg_color),be(),B(a.name+" opened. "+de.model_ids.length+" models added."),b.canvas.focus({preventScroll:!0})}finally{D===S&&(D=null)}}o.querySelectorAll("[data-open-file]").forEach(a=>u(a,"click",()=>n("file-input").click())),u(n("file-input"),"change",()=>{let a=[...n("file-input").files];n("file-input").value="",je(a)}),n("sample-button").hidden=!it,u(n("sample-button"),"click",()=>{it&&je([{name:e.sample_name||"Sample.stl",url:it}])}),u(n("close-model"),"click",()=>lt(v)),u(n("cancel-load"),"click",De),u(n("clear-scene"),"click",Ot),u(n("dismiss-notice"),"click",()=>{n("notice").hidden=!0}),u(n("fit-button"),"click",()=>x?.set_auto_zoom()),u(n("scene-arrangement"),"change",nt),u(n("model-list"),"click",a=>{let h=a.target.closest("button");h&&(h.dataset.removeModel!==void 0?lt(Number(h.dataset.removeModel)):h.dataset.selectModel!==void 0?Fe(Number(h.dataset.selectModel)):h.dataset.colorModel!==void 0&&(Fe(Number(h.dataset.colorModel)),Pe("colors")))});async function jt(){if(v===null||R)return;let a=++I,h=ye.find(b=>b.checked).value;ye.forEach(b=>{b.disabled=!0}),n("smooth-shading").disabled=!0,n("view-status").textContent="Preparing view\u2026",n("notice").hidden=!0;try{x.set_view(v,{display:h,smooth_shading:n("smooth-shading").checked}),await x.when_idle()}finally{a===I&&(Lt(),B(n("view-label").textContent+" view ready."))}}ye.forEach(a=>u(a,"change",jt)),u(n("smooth-shading"),"change",jt);for(let a of y)u(n(a+"-button"),"click",()=>{a==="info"&&Ct(),Pe(a),a==="section"&&!n("section-panel").hidden&&!n("section-enabled").checked&&(n("section-enabled").checked=!0,ke())}),u(n("close-"+a),"click",()=>{Rt(a),a==="colors"&&Pe("view"),n(a+"-button").focus()});function ke(){if(v===null)return;let a=n("section-enabled").checked;x.set_section(v,a?{axis:n("section-axis").value,position:Number(n("section-position").value)/100,flip:n("section-flip").getAttribute("aria-pressed")==="true"}:null),n("section-value").textContent=n("section-position").value+"%",n("section-button").setAttribute("aria-pressed",String(a))}u(n("section-enabled"),"change",ke),u(n("section-axis"),"change",ke),u(n("section-position"),"input",ke),u(n("section-flip"),"click",()=>{n("section-flip").setAttribute("aria-pressed",String(n("section-flip").getAttribute("aria-pressed")!=="true")),ke()}),u(n("grid-button"),"click",()=>{j=!j,Oe()});function dt(a){v!==null&&(x.set_color(v,a),g.get(v).color=a,rt(),It())}u(n("model-color"),"input",()=>dt(n("model-color").value)),u(n("restore-color"),"click",()=>dt(g.get(v)?.originalColor)),o.querySelectorAll("[data-model-color]").forEach(a=>u(a,"click",()=>dt(a.dataset.modelColor)));function ht(a){ue=a;let h=a==="transparent"?[255,255,255]:[1,3,5].map(b=>parseInt(a.slice(b,b+2),16));n("workspace").classList.toggle("dark-background",h[0]*.2126+h[1]*.7152+h[2]*.0722<115),x?.set_bg_color(a),rt()}u(n("background-color"),"input",()=>ht(n("background-color").value)),o.querySelectorAll("[data-background]").forEach(a=>u(a,"click",()=>ht(a.dataset.background))),u(n("image-button"),"click",async()=>{if(v===null)return;let a=g.size>1?"viewstl-scene.png":g.get(v).name.replace(/\.[^.]+$/,"")+".png";try{let h=await x.get_image_blob("image/png");if(m)return;let b=s.createObjectURL(h),S=t.createElement("a");S.href=b,S.download=a,S.click(),setTimeout(()=>s.revokeObjectURL(b),1e3),B("Image ready to download.")}catch{ne("The image could not be saved. Please try again.")}});let Be=0,mt=a=>{let h=vt(a.dataTransfer);return h==="files"?h:!e.url_import_endpoint||a.target?.isContentEditable||a.target?.closest?.("input, textarea")?null:h},Ue=()=>{Be=0,n("drop-overlay").hidden=!0};u(c,"dragenter",a=>{mt(a)&&(a.preventDefault(),Be++,n("drop-overlay").hidden=!1)}),u(c,"dragover",a=>{mt(a)&&(a.preventDefault(),a.dataTransfer.dropEffect="copy")}),u(c,"dragleave",a=>{Be>0&&--Be===0&&Ue()}),u(c,"drop",a=>{let h=mt(a);if(Ue(),!!h){a.preventDefault();try{if(h==="files")je([...a.dataTransfer.files]).catch(b=>ne(b.message));else for(let b of oo(a.dataTransfer))ct(b).catch(S=>ne(S.message))}catch(b){ne(b.message)}}}),u(c,"dragend",Ue),u(i,"blur",Ue),o.querySelectorAll("[data-dialog]").forEach(a=>u(a,"click",()=>n(a.dataset.dialog).showModal())),o.querySelectorAll("dialog").forEach(a=>u(a,"click",h=>{if(h.target===a){let b=a.getBoundingClientRect();(h.clientX<b.left||h.clientX>b.right||h.clientY<b.top||h.clientY>b.bottom)&&(a===n("save-dialog")?P.close():a.close())}})),u(o,"keydown",a=>{if(a.key!=="Escape"||n("save-dialog")?.open)return;if(J){G(!1),B("Move tool closed.");return}let h=y.find(b=>!n(b+"-panel").hidden);h&&(ce(),h==="colors"&&Pe("view"),n(h+"-button").focus())}),u(o,"pointerdown",a=>{a.target.closest(".view-panel,.info-panel,.toolbar,.scene-heading-button,.move-mode")||ce()}),n("memory-budget").textContent=`Scene memory budget: ${Me(p.budget)}. Half of ${p.reported?"the memory estimate reported by your browser":"a conservative 2 GiB estimate because this browser does not report device memory"} is kept in reserve. This is an estimate, not a guarantee of available memory.`,n("scene-arrangement").value="original",n("notice").hidden=!0,n("drop-overlay").hidden=!0,n("save-button")&&(P=uo({$:n,listen:u,window:i,getState:()=>({busy:R,selectedId:v,models:[...g.values()]}),getViewer:()=>x,onOpen:()=>{G(!1),ce()},announce:B})),n("print-button")&&(L=fo({$:n,listen:u,window:i,getState:()=>({busy:R,selectedId:v,models:[...g.values()]}),getViewer:()=>x,onOpen:()=>{G(!1),ce()},announce:B})),Oe(),Te(!1),G(!1);let Bt={openFiles:je,openURL:ct,cancelLoading(){f(),De()},clear(){f(),Ot()},select(a){f(),Fe(a)},remove(a){f(),lt(a)},getState(){return f(),{selectedId:v,busy:R,arrangement:n("scene-arrangement").value,memoryBudget:p.budget,models:[...g.values()].map(a=>({id:a.id,name:a.name,size:a.size,units:a.units,position:{...a.position},rotation:{...a.rotation},color:a.color}))}},dispose(){m||(P?.dispose(),L?.dispose(),De(),G(!1),I++,m=!0,l.abort(),W?.dispose(),x?.dispose(),g.clear(),e.id_prefix&&ye.forEach(a=>a.name="view-mode"),zt.delete(o))}};return zt.set(o,Bt),Bt}var Jo=globalThis.document?.currentScript;_e.workerURL=new URL("viewstl.worker.js",Jo?.src||document.baseURI).href;globalThis.StlViewer=_e;globalThis.mountViewstlApp=Ao;})();
@@ -1850,7 +1850,7 @@ function modelOptions(input) {
1850
1850
  return out;
1851
1851
  }
1852
1852
  var StlViewer = class _StlViewer {
1853
- static version = "2.0.0-rc.3";
1853
+ static version = "2.0.0-rc.4";
1854
1854
  static workerURL = null;
1855
1855
  constructor(parent, options = {}) {
1856
1856
  if (!parent?.appendChild)
@@ -1823,7 +1823,7 @@ function modelOptions(input) {
1823
1823
  return out;
1824
1824
  }
1825
1825
  var StlViewer = class _StlViewer {
1826
- static version = "2.0.0-rc.3";
1826
+ static version = "2.0.0-rc.4";
1827
1827
  static workerURL = null;
1828
1828
  constructor(parent, options = {}) {
1829
1829
  if (!parent?.appendChild)
@@ -48,4 +48,4 @@ if(!unlit){
48
48
  if(appearance==4)color=(gl_FrontFacing?vec3(0.12,0.55,0.72):vec3(0.9,0.24,0.32))*(0.55+0.45*l);
49
49
  if(dot(section.xyz,section.xyz)>0.0 && abs(cut)<max(fwidth(cut)*1.2,0.0000001)){color=vec3(0.93,0.57,0.17);alpha=opacity;}
50
50
  }
51
- result=vec4(color,alpha);}`,Ze=class{constructor(e,t,i,s){if(this.canvas=e,this.domElement=e,this.resources=new Map,this.frames=0,this.lost=!1,this.gl=e.getContext("webgl2",{alpha:!0,antialias:!0,premultipliedAlpha:!1,preserveDrawingBuffer:!1}),!this.gl)throw new Error("WebGL2 is unavailable. Enable browser graphics acceleration or try another browser/device.");this.lostHandler=r=>{r.preventDefault(),this.lost=!0,this.resources.clear(),this.gridResource=null,this.infiniteGrid=null,this.selectionResource=null,this.diagnosticResource=null,t()},this.restoredHandler=()=>{try{this.initialize(),this.lost=!1,i()}catch(r){s(r)}},e.addEventListener("webglcontextlost",this.lostHandler),e.addEventListener("webglcontextrestored",this.restoredHandler),this.initialize()}initialize(){let e=this.gl,t=[],i;try{for(let[s,r]of[[e.VERTEX_SHADER,Ho],[e.FRAGMENT_SHADER,qo]]){let n=e.createShader(s);if(t.push(n),e.shaderSource(n,r),e.compileShader(n),!e.getShaderParameter(n,e.COMPILE_STATUS))throw new Error(`Shader compilation failed: ${e.getShaderInfoLog(n)}`)}i=e.createProgram();for(let s of t)e.attachShader(i,s);if(e.linkProgram(i),!e.getProgramParameter(i,e.LINK_STATUS))throw new Error(`Shader link failed: ${e.getProgramInfoLog(i)}`);this.program=i,this.uniforms=Object.fromEntries(["mvp","modelMatrix","normalMatrix","tint","light","eye","opacity","unlit","section","appearance","useColors"].map(s=>[s,e.getUniformLocation(i,s)]))}catch(s){throw i&&e.deleteProgram(i),s}finally{for(let s of t)e.deleteShader(s)}}upload(e,t,i){let s=this.gl,r=s.createVertexArray(),n=[];if(!r)throw new Error("Graphics memory is unavailable for this model.");s.bindVertexArray(r);try{for(let[d,l]of[[0,e],[1,t],[2,i]])if(l){let c=s.createBuffer();if(!c)throw new Error("Graphics memory is unavailable for this model.");n.push(c),s.bindBuffer(s.ARRAY_BUFFER,c),s.bufferData(s.ARRAY_BUFFER,l,s.STATIC_DRAW);let m=s.getError();if(m!==s.NO_ERROR)throw new Error(m===s.OUT_OF_MEMORY?"Not enough graphics memory for this model. Try closing other tabs or using another device.":"The browser could not upload this model to the graphics device.");s.enableVertexAttribArray(d),s.vertexAttribPointer(d,3,s.FLOAT,!1,0,0)}return s.bindVertexArray(null),{vao:r,buffers:n,count:e.length/3}}catch(d){s.bindVertexArray(null);for(let l of n)s.deleteBuffer(l);throw s.deleteVertexArray(r),d}}free(e){if(e){for(let t of e.buffers)this.gl.deleteBuffer(t);this.gl.deleteVertexArray(e.vao)}}remove(e){this.diagnostic?.model===e&&this.setDiagnostic(null);let t=this.resources.get(e);if(t)for(let i of Object.values(t))this.free(i);this.resources.delete(e)}clear(){this.setDiagnostic(null),this.setSelection(null);for(let e of this.resources.keys())this.remove(e);this.free(this.gridResource),this.gridResource=null,this.infiniteGrid?.dispose(),this.infiniteGrid=null}get liveBuffers(){let e=(this.gridResource?.buffers.length||0)+(this.selectionResource?.buffers.length||0)+(this.diagnosticResource?.buffers.length||0);for(let t of this.resources.values())for(let i of Object.values(t))e+=i.buffers.length;return e}setDiagnostic(e){this.free(this.diagnosticResource),this.diagnosticResource=null,this.diagnostic=e}drawDiagnostic(e){let t=this.gl,i=this.uniforms,s=this.diagnostic,r=X(s.model);this.diagnosticResource??=this.upload(s.positions),t.uniformMatrix4fv(i.mvp,!1,new Float32Array(ze(e,r))),t.uniformMatrix4fv(i.modelMatrix,!1,new Float32Array(r)),t.uniform4fv(i.section,[0,0,0,0]),t.uniform1i(i.appearance,0),t.uniform1i(i.useColors,0),t.uniform1i(i.unlit,1),t.uniform3fv(i.tint,[.85,.16,.06]),t.uniform1f(i.opacity,1),t.bindVertexArray(this.diagnosticResource.vao),t.disable(t.DEPTH_TEST),t.drawArrays(t.LINES,0,this.diagnosticResource.count),t.enable(t.DEPTH_TEST)}setSelection(e){this.selection!==e&&(this.free(this.selectionResource),this.selectionResource=null,this.selection=e)}drawSelection(e,t){let i=this.gl,s=this.uniforms,r=X(this.selection);this.selectionResource??=this.upload(po(this.selection.geometry.bounds)),i.uniformMatrix4fv(s.mvp,!1,new Float32Array(ze(e,r))),i.uniformMatrix4fv(s.modelMatrix,!1,new Float32Array(r)),i.uniformMatrix3fv(s.normalMatrix,!1,new Float32Array([1,0,0,0,1,0,0,0,1])),i.uniform4fv(s.section,[0,0,0,0]),i.uniform1i(s.appearance,0),i.uniform1i(s.useColors,0),i.uniform1i(s.unlit,1);let n=t==="transparent"?[1,1,1]:Y(t);i.uniform3fv(s.tint,n[0]+n[1]+n[2]<1.5?[.42,.81,.96]:[.08,.43,.65]),i.bindVertexArray(this.selectionResource.vao),i.disable(i.DEPTH_TEST),i.uniform1f(s.opacity,.18),i.drawArrays(i.LINES,0,this.selectionResource.count),i.enable(i.DEPTH_TEST),i.uniform1f(s.opacity,.9),i.drawArrays(i.LINES,0,this.selectionResource.count)}resource(e,t){let i=this.resources.get(e);if(i||(i={},this.resources.set(e,i)),!i[t]){let s=e.geometry;i[t]=t==="edges"||t==="wire"?this.upload(s[t]):this.upload(s.positions,t==="smooth"?s.smoothNormals:s.normals,s.colors)}return i[t]}setGrid(e){this.grid=e,this.free(this.gridResource),this.gridResource=null}resize(e,t,i=1){let s=this.gl,r=s.getParameter(s.MAX_RENDERBUFFER_SIZE);this.canvas.width=Math.min(r,Math.max(1,Math.round(e*i))),this.canvas.height=Math.min(r,Math.max(1,Math.round(t*i)))}draw(e,t,i,s=!0){if(this.lost)return;let r=this.gl,n=this.uniforms;r.viewport(0,0,this.canvas.width,this.canvas.height),r.clearColor(...i==="transparent"?[0,0,0,0]:[...Y(i),1]),r.depthMask(!0),r.clear(r.COLOR_BUFFER_BIT|r.DEPTH_BUFFER_BIT),r.enable(r.DEPTH_TEST),r.disable(r.CULL_FACE),r.useProgram(this.program),r.vertexAttrib3f(1,0,0,1),r.vertexAttrib3f(2,1,1,1),r.uniform3fv(n.light,H(V(t.position,t.target))),r.uniform3fv(n.eye,t.position);let d=Ye(t,this.canvas.width/this.canvas.height),l=e.filter(f=>f.geometry&&f.opacity>0&&f.color!=="transparent"),c=f=>{let u=le(X(f),f.geometry.bounds.min.map((y,g)=>(y+f.geometry.bounds.max[g])/2)),p=V(u,t.position);return te(p,p)},m=l.filter(f=>f.opacity>=1&&f.display!=="xray"),_=l.filter(f=>f.opacity<1||f.display==="xray").sort((f,u)=>c(u)-c(f)),w=f=>{let u=X(f),p=fe(u),y=f.geometry;r.uniformMatrix4fv(n.mvp,!1,new Float32Array(ze(d,u))),r.uniformMatrix4fv(n.modelMatrix,!1,new Float32Array(u)),r.uniform4fv(n.section,We(y.bounds,f.section)),r.uniform1i(n.appearance,{mesh:1,xray:2,studio:3,orientation:4}[f.display]||0),r.uniformMatrix3fv(n.normalMatrix,!1,new Float32Array([p[0],p[4],p[8],p[1],p[5],p[9],p[2],p[6],p[10]])),r.uniform3fv(n.tint,f.color&&f.color!=="transparent"?Y(f.color):y.colors?[1,1,1]:Y("#909090")),r.uniform1f(n.opacity,f.opacity),r.uniform1i(n.useColors,+(!f.color&&!!y.colors));let g=f.display==="wireframe"&&y.wire,A=g?"wire":(f.smooth_shading||f.display==="smooth")&&y.smoothNormals?"smooth":"flat";r.uniform1i(n.unlit,+!!g);let k=f.view_edges||f.display==="edges";k&&(r.enable(r.POLYGON_OFFSET_FILL),r.polygonOffset(1,1));let E=this.resource(f,A);if(r.bindVertexArray(E.vao),f.display==="xray"?(r.enable(r.CULL_FACE),r.cullFace(r.FRONT),r.drawArrays(r.TRIANGLES,0,E.count),r.cullFace(r.BACK),r.drawArrays(r.TRIANGLES,0,E.count),r.disable(r.CULL_FACE)):r.drawArrays(g?r.LINES:r.TRIANGLES,0,E.count),r.disable(r.POLYGON_OFFSET_FILL),k&&y.edges){r.uniform1i(n.useColors,0),r.uniform3fv(n.tint,[.08,.09,.11]),r.uniform1i(n.unlit,1);let x=this.resource(f,"edges");r.bindVertexArray(x.vao),r.drawArrays(r.LINES,0,x.count)}};r.disable(r.BLEND);for(let f of m)w(f);if(this.grid?.infinite)this.infiniteGrid??=new Ke(r),this.infiniteGrid.draw(d,t,this.grid,i),r.useProgram(this.program);else if(this.grid){if(!this.gridResource){let{size:f,divisions:u}=this.grid,p=[];for(let y=0;y<=u;y++){let g=-f/2+f*y/u;p.push(-f/2,g,0,f/2,g,0,g,-f/2,0,g,f/2,0)}this.gridResource=this.upload(new Float32Array(p))}r.uniformMatrix4fv(n.mvp,!1,new Float32Array(d)),r.uniform4fv(n.section,[0,0,0,0]),r.uniform1i(n.appearance,0),r.uniform1i(n.useColors,0),r.uniform3fv(n.tint,[.7,.73,.78]),r.uniform1f(n.opacity,1),r.uniform1i(n.unlit,1),r.bindVertexArray(this.gridResource.vao),r.drawArrays(r.LINES,0,this.gridResource.count)}r.enable(r.BLEND),r.blendFuncSeparate(r.SRC_ALPHA,r.ONE_MINUS_SRC_ALPHA,r.ONE,r.ONE_MINUS_SRC_ALPHA),r.depthMask(!1);for(let f of _)w(f);s&&this.selection&&l.includes(this.selection)&&this.drawSelection(d,i),s&&this.diagnostic&&e.includes(this.diagnostic.model)&&this.drawDiagnostic(d),r.depthMask(!0),r.disable(r.BLEND),r.bindVertexArray(null),this.frames++}dispose(){this.canvas.removeEventListener("webglcontextlost",this.lostHandler),this.canvas.removeEventListener("webglcontextrestored",this.restoredHandler),this.clear(),this.gl.deleteProgram(this.program),this.gl.getExtension("WEBGL_lose_context")?.loseContext()}};var Qe=class{constructor(e,t,i,s,r=0){this.enabled=!0,this.noZoom=!1,this.type=r,this.camera=t,this.invalidate=i,this.pointers=new Map,this.cleanups=[],this.canvas=e;let n=(l,c,m)=>{e.addEventListener(l,c,m),this.cleanups.push(()=>e.removeEventListener(l,c,m))};n("pointerdown",l=>{this.enabled&&(l.preventDefault(),e.focus({preventScroll:!0}),e.setPointerCapture(l.pointerId),this.pointers.set(l.pointerId,{x:l.clientX,y:l.clientY,button:l.button}),s(l,l.pointerType==="touch"?20:l.button===2?3:1))}),n("pointermove",l=>{let c=this.pointers.get(l.pointerId);if(!c||!this.enabled)return;let m={x:l.clientX,y:l.clientY,button:c.button};if(this.pointers.size===2){let _=[...this.pointers.entries()].find(([u])=>u!==l.pointerId)[1],w=Math.hypot(c.x-_.x,c.y-_.y),f=Math.hypot(m.x-_.x,m.y-_.y);w>0&&f>0&&this.zoom(w/f),this.pan((m.x-c.x)/2,(m.y-c.y)/2)}else c.button===2||l.shiftKey||c.button===1?this.pan(m.x-c.x,m.y-c.y):this.orbit((m.x-c.x)/Math.max(1,e.clientHeight)*Math.PI,(m.y-c.y)/Math.max(1,e.clientHeight)*Math.PI);this.pointers.set(l.pointerId,m)});let d=l=>{this.pointers.delete(l.pointerId),e.hasPointerCapture(l.pointerId)&&e.releasePointerCapture(l.pointerId)};n("pointerup",d),n("pointercancel",d),n("lostpointercapture",l=>this.pointers.delete(l.pointerId)),n("wheel",l=>{if(!this.enabled||this.noZoom)return;l.preventDefault();let c=l.deltaY*(l.deltaMode===1?16:l.deltaMode===2?e.clientHeight:1);this.zoom(Math.exp(Math.max(-1,Math.min(1,c*.001))))},{passive:!1}),n("dblclick",l=>{this.enabled&&s(l,11)}),n("contextmenu",l=>l.preventDefault())}orbit(e,t){let i=this.camera,s=V(i.position,i.target),r=H(i.up);s=Je(s,r,-e);let n=H(ie(r,s)),d=Je(s,n,-t);if(this.type===1)s=d,i.up=H(Je(r,n,-t));else{let l=H(d),c=l.reduce((m,_,w)=>m+_*r[w],0);Math.abs(c)<.9995&&(s=d)}i.position=re(i.target,s),this.invalidate()}zoom(e){if(this.noZoom)return;let t=this.camera,i=V(t.position,t.target),s=Math.hypot(...i),r=Math.max(1e-6,Math.min(1e15,s*e));t.position=re(t.target,oe(i,r/(s||1))),this.invalidate()}pan(e,t){let i=this.camera,s=V(i.position,i.target),r=H(ie(i.up,s)),n=H(ie(s,r)),d=2*Math.hypot(...s)*Math.tan(Math.PI/8)/Math.max(1,this.canvas.clientHeight),l=re(oe(r,-e*d),oe(n,t*d));i.target=re(i.target,l),i.position=re(i.position,l),this.invalidate()}dispose(){for(let e of this.cleanups)e();this.pointers.clear()}};var $e=class{constructor(e,{count:t=2,timeout:i=12e4,factory:s}={}){this.url=e,this.count=Math.max(1,Math.min(4,t)),this.timeout=i,this.factory=s,this.queue=[],this.active=new Set,this.next=0,this.disposed=!1}run(e,t,i=[]){if(this.disposed)return Promise.reject(new Error("Viewer disposed"));let s=++this.next,r,n=new Promise((d,l)=>{r={id:s,task:e,progress:t,transfer:i,resolve:d,reject:l},this.queue.push(r)});return n.cancel=()=>this.cancel(r),this.pump(),n}cancel(e){e.done||(e.done=!0,this.queue=this.queue.filter(t=>t!==e),clearTimeout(e.timer),e.worker?.terminate(),this.active.delete(e),e.reject(new DOMException("Loading cancelled","AbortError")),this.pump())}pump(){for(;!this.disposed&&this.active.size<this.count&&this.queue.length;){let e=this.queue.shift();this.active.add(e);let t=(i,s)=>{e.done||(e.done=!0,clearTimeout(e.timer),e.worker?.terminate(),this.active.delete(e),i?e.reject(i):e.resolve(s),this.pump())};try{e.worker=this.factory?this.factory():new Worker(this.url,{type:"module",name:"ViewSTL parser"}),e.worker.onmessage=({data:i})=>{if(i.id===e.id){if(i.progress){e.progress?.(i.progress);return}t(i.error?new Error(i.error):null,i.result)}},e.worker.onerror=i=>{i.preventDefault(),t(new Error(i.message||"Worker failed; check worker URL and Content Security Policy"))},e.worker.onmessageerror=()=>t(new Error("Invalid worker response")),e.timer=setTimeout(()=>t(new Error("File processing timed out")),this.timeout),e.worker.postMessage({id:e.id,task:e.task},e.transfer)}catch(i){t(i)}}}clear(){for(let e of[...this.queue,...this.active])this.cancel(e)}dispose(){this.disposed=!0,this.clear()}};function go(o,e={}){let t=new AbortController,i=t.signal,s=this._generation,r=[],n=new Map,d=new Set,l=this._models.size>0,c,m=0,_,w=!1,f,u=()=>{t.abort(),c?.cancel?.();for(let[A,k]of n)this._models.get(A)===k&&this.remove_model(A)},p=()=>{if(i.aborted||s!==this._generation||this.killsign)throw new DOMException("Scene opening cancelled","AbortError")},y=A=>e.on_progress?.(A);e.signal?.addEventListener("abort",u,{once:!0}),e.signal?.aborted&&u(),i.addEventListener("abort",()=>c?.cancel?.(),{once:!0}),this._aborters.add(t);let g=(async()=>{await this.when_ready,p(),_=this.get_camera_state();let A=e.format||pe(typeof o=="string"?o:o.name);if(!["vsb","vsj"].includes(A))throw new Error("Choose VSB or VSJ for a scene");let k,E;if(typeof o=="string"){if(k=Mt(o,document.baseURI),!this.options.url_import_endpoint)throw new Error("Configure url_import_endpoint for scene URL imports");if(E=new URL(this.options.url_import_endpoint,document.baseURI).href,new URL(E).origin!==location.origin)throw new Error("The URL relay must use the same origin as this page")}let x=this.limits.max_memory_bytes-this._occupiedMemory();K(this.limits,this._occupiedMemory()+67108864,"scene opening");let C={...this.limits,max_memory_bytes:x,max_file_bytes:Math.min(this.limits.max_file_bytes,Math.floor((x-67108864)/4))};m=Math.max(0,x-67108864),this._sceneBytes=(this._sceneBytes||0)+m,c=this._pool.run({kind:"read-scene",source:o,remote:k,relay:E,format:A,name:`scene.${A}`,limits:C},M=>{try{y({...M,phase:"reading-scene"})}catch(R){f=R,c.cancel()}});let I;try{I=await c}catch(M){throw f||M}c=null,p();let{scene:v,files:F,base:z}=I;this._sceneBytes-=m,m=Object.values(F||{}).reduce((M,R)=>M+R.byteLength,0)+JSON.stringify(v).length*2,this._sceneBytes+=m;let P=e.files||[],L=[],D=v.models.map(M=>{let R={...M};if(F)R.local_file=new File([F[M.filename]],M.filename);else{let j=mo(M.filename,P);if(j)R.local_file=j,d.add(j);else{let O=Mt(M.filename,k?z:void 0);O?R.remote_url=O:L.push(M.filename)}}if(R.remote_url&&!this.options.url_import_endpoint)throw new Error("Scene URL references require a configured URL relay");return R});if(L.length)throw new Error("Choose the VSJ together with these model files: "+L.slice(0,8).join(", ")+(L.length>8?"\u2026":""));if(D.length+this._models.size>this.limits.max_archive_entries)throw new Error("Scene model count limit exceeded");for(let M=0;M<D.length;M++){p(),y({phase:"opening-model",loaded:M,total:D.length}),p();let R=D[M],j=this.max_model_id+1;this.add_model({...R,id:j});let O=this._models.get(j);if(!O)throw new Error(this.error||"Invalid scene model");if(r.push(j),n.set(j,O),O._sceneImportPending=!0,await this._pending.get(j),p(),this._models.get(j)!==O||!O.geometry)throw new Error(this.error||"A scene model could not be opened");let we=v.center_models??this.center_models?O.geometry.bounds.min.map((W,ee)=>(W+O.geometry.bounds.max[ee])/2):[0,0,0],J=O.center.map((W,ee)=>W-we[ee]),q=le(X({...O,x:0,y:0,z:0,center:[0,0,0],scaleVector:O.scaleVector}),J);if(["x","y","z"].forEach((W,ee)=>O[W]+=q[ee]),[...this._models.values()].reduce((W,ee)=>W+(ee.geometry?.triangles||0),0)>this.limits.max_triangles)throw new Error("Scene triangle limit exceeded")}y({phase:"complete",loaded:D.length,total:D.length}),p();for(let M of n.values())M._sceneImportPending=!1;return v.bg_color!==void 0&&this.set_bg_color(v.bg_color),v.infinite_grid?this.set_infinite_grid(!0,v.infinite_grid.spacing,v.infinite_grid.height):this.set_grid(!!v.grid),v.auto_rotate!==void 0&&this.set_auto_rotate(v.auto_rotate),this._restoreCamera=v.camera_state||(l?_:null),w=!0,{model_ids:r,scene:v,used_files:[...d]}})().catch(A=>{throw u(),A}).finally(()=>{e.signal?.removeEventListener("abort",u),this._aborters.delete(t),this._sceneBytes=(this._sceneBytes||0)-m,this._sceneLoads.delete(g),s===this._generation&&!this.killsign&&(!w&&_&&(this._restoreCamera=_),this._finishSession())});return this._sessionActive=!0,this._sceneLoads.add(g),g}function _o(o={}){let e=o.level??"basic";if(!["basic","detailed"].includes(e))throw new Error("Choose basic or detailed print checks");let t=(r,n,d=1e9)=>{if(!Number.isFinite(r)||r<=0||r>d)throw new Error(`Invalid ${n}`);return r},i=null;if(o.profile!=null){if(o.profile.technology!=="fdm")throw new Error("Only FDM profiles are supported");i={technology:"fdm"};for(let r of["min_wall_mm","line_width_mm"])o.profile[r]!=null&&(i[r]=t(o.profile[r],r));if(o.profile.overhang_degrees!=null&&(i.overhang_degrees=t(o.profile.overhang_degrees,"overhang angle",89.9)),o.profile.build_volume_mm!=null){if(!Array.isArray(o.profile.build_volume_mm)||o.profile.build_volume_mm.length!==3)throw new Error("Build volume requires X, Y and Z");i.build_volume_mm=o.profile.build_volume_mm.map(r=>t(r,"build volume"))}}let s=t(o.max_time_ms??3e4,"check time limit",12e4);return{level:e,profile:i,apply_transforms:o.apply_transforms!==!1,units_confirmed:o.units_confirmed===!0,max_time_ms:s}}var yo=o=>64*1024**2+o*2400,ge=o=>JSON.stringify([o.x,o.y,o.z,o.rotationx,o.rotationy,o.rotationz,o.scaleVector,o.center,o.units,o.unitScale]);var Re=new WeakMap;function et(o,e){let t=Re.get(e);return!!t&&t.viewer.deref()===o&&t.models.every(({model:i,geometry:s,signature:r})=>{let n=i.deref();return n&&o._models.get(n.id)===n&&n.geometry===s.deref()&&ge(n)===r})}async function bo(o={}){await this.when_ready;let e=_o(o);if(this.killsign)throw new Error("Viewer disposed");let t=o.model_ids;if(t==="selected"&&(t=this.renderer?.selection?[this.renderer.selection.id]:[]),t??=[...this._models.keys()],!Array.isArray(t)||!t.length||new Set(t).size!==t.length||t.some(u=>!Number.isSafeInteger(u)))throw new Error("Choose one or more unique model IDs");if(t.length>this.limits.max_archive_entries)throw new Error("Print check model limit exceeded");let i=t.map(u=>{let p=this._model(u);if(!p.geometry||p._sceneImportPending)throw new Error("Wait for models to finish opening");return{model:p,geometry:p.geometry,signature:ge(p)}}),s=JSON.stringify([t,e]);if(o.signal?.aborted)throw new DOMException("Print check cancelled","AbortError");if(this._printCache?.key===s&&et(this,this._printCache.report)){let u=structuredClone(this._printCache.report);return Re.set(u,Re.get(this._printCache.report)),u}if(this._printJobs?.size)throw new Error("A print check is already running");this._printJobs??=new Map;let r=new AbortController,n=()=>r.abort();o.signal?.addEventListener("abort",n,{once:!0}),this._aborters.add(r),this._printJobs.set(r,new Set(i.map(u=>u.model)));let d,l=0,c,m=()=>d?.cancel?.();r.signal.addEventListener("abort",m);let _=()=>{if(r.signal.aborted||this.killsign)throw new DOMException("Print check cancelled","AbortError");if(i.some(({model:u,geometry:p,signature:y})=>this._models.get(u.id)!==u||u.geometry!==p||ge(u)!==y))throw new Error("Model changed. Run the print check again.")},w=u=>{_(),o.on_progress?.(u)},f=[];try{this._printCache=null;let u=i.length*256*1024;K(this.limits,this._occupiedMemory()+u,"print report"),l=u,this._printBytes=(this._printBytes||0)+l;for(let A=0;A<i.length;A++){_();let{model:k,geometry:E}=i[A],x=yo(E.triangles),C=this._occupiedMemory();K(this.limits,C+x,"print check"),this._printBytes+=x,l+=x;let I=new Float32Array(E.positions.length);for(let F=0;F<I.length;F+=262144)_(),I.set(E.positions.subarray(F,F+262144),F),F+262144<I.length&&await new Promise(z=>setTimeout(z,0));let v=e.apply_transforms?k:{...k,x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,center:[0,0,0],scaleVector:[k.unitScale,k.unitScale,k.unitScale]};w({phase:"Starting",model_id:k.id,model_index:A,total_models:i.length}),d=this._pool.run({kind:"print-check",model:{id:k.id,name:k.orig_filename||k.filename||`Model ${k.id}`,positions:I,matrix:X(v),source_units:E.units||null},options:e,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-C}},F=>{try{w({...F,model_id:k.id,model_index:A,total_models:i.length})}catch(z){c=z,r.abort()}},[I.buffer]);try{f.push(await d)}catch(F){throw c||F}finally{d=null,this._printBytes-=x,l-=x}}_();let p={version:1,model_ids:t.slice(),settings:e,models:f,summary:f.some(A=>A.summary==="issues_found")?"issues_found":f.some(A=>A.summary==="incomplete")?"incomplete":"no_issues_found",scope:"Each model checked separately; no cross-model union or collision analysis.",estimated:!!e.profile},y={viewer:new WeakRef(this),models:i.map(({model:A,geometry:k,signature:E})=>({model:new WeakRef(A),geometry:new WeakRef(k),signature:E}))};Re.set(p,y);let g=structuredClone(p);return Re.set(g,y),JSON.stringify(g).length<2*1024*1024&&this._occupiedMemory()+4*1024**2<=this.limits.max_memory_bytes&&(this._printCache={key:s,report:g}),w({phase:"Complete",total_models:i.length}),p}finally{d?.cancel?.(),this._printBytes=(this._printBytes||0)-l,o.signal?.removeEventListener("abort",n),this._aborters.delete(r),this._printJobs.delete(r)}}function vo(o,{issue:e,model_id:t=o.model_ids[0]}={}){if(!et(this,o))throw new Error("Print report is stale. Run the check again.");let i=o.models.find(l=>l.model_id===t),s=i?.checks.find(l=>l.code===e);if(!s)throw new Error("Unknown print check issue");let r=this._model(t),n=r.geometry.positions,d=[];if(s.edge_vertices?.length)d.push(...s.edge_vertices.slice(0,1536));else for(let l of s.triangle_ids.slice(0,128))if(!(!Number.isInteger(l)||l<0||l>=r.geometry.triangles))for(let c=0;c<3;c++)d.push(...n.subarray(l*9+c*3,l*9+c*3+3),...n.subarray(l*9+(c+1)%3*3,l*9+(c+1)%3*3+3));if(d.some(l=>!Number.isFinite(l)))throw new Error("Invalid print highlight");return this.renderer.setDiagnostic(d.length?{model:r,positions:new Float32Array(d),signature:ge(r)}:null),this._invalidate(),d.length/6}function St(o,e){return`${String(o||"scene").split(/[\\/]/).pop().split(/[?#]/)[0].replace(/[<>:"|*\x00-\x1f\x7f]/g,"_").replace(/\.(?:stl|obj|3mf|step|stp|iges|igs|zip|vsb|vsj)$/i,"").replace(/[. ]+$/g,"").slice(0,160)||"scene"}.${e}`}function wo(o={}){let e={stp:"step",igs:"iges"},t=String(o.format||"").toLowerCase(),i=e[t]||t;if(!["stl","obj","3mf","step","iges"].includes(i))throw new Error("Export format must be stl, obj, 3mf, step or iges");let s=o.units??"mm";if(!["mm","inch"].includes(s))throw new Error("Export units must be mm or inch");for(let d of["apply_transforms","colors"])if(o[d]!==void 0&&typeof o[d]!="boolean")throw new Error(`${d} must be a boolean`);if(o.on_progress!==void 0&&typeof o.on_progress!="function")throw new Error("on_progress must be a function");let r=o.colors!==!1&&["obj","3mf"].includes(i),n=i==="obj"&&r?"zip":i;return{format:i,units:s,colors:r,apply_transforms:o.apply_transforms!==!1,filename:St(o.filename,n)}}function xo(o,e){let t=o.reduce((s,r)=>s+r.geometry.triangles,0);return 67108864+t*{stl:192,obj:2400,"3mf":3e3,step:12e3,iges:4e3}[e.format]+o.length*4096}async function Mo(o,e,t=()=>{},i){let s,r=0;if(o instanceof ArrayBuffer){if(o.byteLength>e)throw new Error("File size limit exceeded");return t(o.byteLength,o.byteLength),o}if(o instanceof Blob){if(r=o.size,r>e)throw new Error("File size limit exceeded");if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");t(0,r);let _=await o.arrayBuffer();if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");return t(r,r),_}else{let _=new URL(String(o),globalThis.location?.href);if(!["http:","https:","blob:"].includes(_.protocol))throw new Error("Unsupported file URL protocol");let w=await fetch(_,{signal:i,credentials:"same-origin"});if(!w.ok)throw new Error(`HTTP ${w.status}: ${_.pathname}`);r=Number(w.headers.get("content-length"))||0,s=w.body}if(r>e)throw new Error("File size limit exceeded");if(!s)throw new Error("Empty response body");let n=s.getReader(),d=[],l=0;try{for(;;){if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");let{done:_,value:w}=await n.read();if(_)break;if(l+=w.length,l>e)throw new Error("File size limit exceeded");d.push(w),t(l,Math.max(r,l))}}catch(_){throw await n.cancel().catch(()=>{}),_}finally{n.releaseLock()}let c=new Uint8Array(l),m=0;for(let _ of d)c.set(_,m),m+=_.length;return t(l,l),c.buffer}function Eo(o,e){if(!o?.isMesh&&!o?.geometry)throw new Error("Expected a Three.js mesh");if(o.isSkinnedMesh||o.isInstancedMesh||o.children?.length||o.morphTargetInfluences?.some(Boolean))throw new Error("Only a static standalone Three.js mesh is supported");let t=o.material;if(Array.isArray(t)||t?.isShaderMaterial||t?.map)throw new Error("Multiple, textured or shader materials need an application adapter");let i=o.geometry,s,r=null;if(i?.attributes?.position){let n=i.attributes.position,d=i.index,l=d?.count??n.count;if(l%3||l/3>e.max_triangles)throw new Error("Invalid or oversized mesh");s=new Float32Array(l*3),i.attributes.color&&(r=new Float32Array(l*3));for(let c=0;c<l;c++){let m=d?d.getX(c):c;for(let[_,w]of["getX","getY","getZ"].entries())s[c*3+_]=n[w](m),r&&(r[c*3+_]=i.attributes.color[w](m))}}else if(i?.vertices&&i?.faces){if(i.faces.length>e.max_triangles)throw new Error("Triangle limit exceeded");s=new Float32Array(i.faces.length*9),t?.vertexColors&&(r=new Float32Array(s.length)),i.faces.forEach((n,d)=>{for(let[l,c]of[n.a,n.b,n.c].entries()){let m=i.vertices[c];if(!m)throw new Error("Invalid mesh index");s.set([m.x,m.y,m.z],d*9+l*3),r&&r.set([n.color.r,n.color.g,n.color.b],d*9+l*3)}})}else throw new Error("Unsupported Three.js geometry");return Xe(s,r,e)}function ko(o,e){if(!e?.Mesh||!e?.BufferGeometry||!e?.BufferAttribute)throw new Error("get_model_mesh requires options.three or globalThis.THREE; use get_model_data for native geometry");let t=o.geometry,i=new e.BufferGeometry,s=t.positions.slice(),r=o.scaleVector,n=o.center;for(let m=0;m<s.length;m++)s[m]=(s[m]-n[m%3])*r[m%3];let d=(m,_)=>{let w=new e.BufferAttribute(_,3);i.setAttribute?i.setAttribute(m,w):i.addAttribute(m,w)};d("position",s),t.colors&&!o.color&&d("color",t.colors.slice()),i.computeVertexNormals();let l=new e.MeshLambertMaterial({color:o.color==="transparent"?"#909090":o.color||(t.colors?"#ffffff":"#909090"),vertexColors:t.colors&&!o.color?e.VertexColors??!0:!1,side:e.DoubleSide,transparent:o.opacity<1||o.color==="transparent",opacity:o.color==="transparent"?0:o.opacity,wireframe:o.display==="wireframe",flatShading:o.display!=="smooth"}),c=new e.Mesh(i,l);return c.position.set(o.x,o.y,o.z),c.rotation.set(o.rotationx,o.rotationy,o.rotationz),c}var Le=["x","y","z","rotationx","rotationy","rotationz","scalex","scaley","scalez"],Wo=["id","filename","remote_url","remote_format","orig_filename","local_file","mesh","data","x","y","z","display","color","scale","scalex","scaley","scalez","rotationx","rotationy","rotationz","opacity","view_edges","smooth_shading","section","units","animation"],Q=(o,e)=>{if(typeof o!="number"||!Number.isFinite(o))throw new Error(`${e} must be a finite number`);return o},me=(o,e)=>{if(Q(o,e),o<=0)throw new Error(`${e} must be positive`);return o},At=(o,e)=>[Q(o.x,`${e}.x`),Q(o.y,`${e}.y`),Q(o.z,`${e}.z`)],tt=o=>({x:o[0],y:o[1],z:o[2]}),ot=o=>String(o||"model.stl").split(/[\\/]/).pop().split(/[?#]/)[0];function Ce(o,e){let t=document.createElement("a"),i=URL.createObjectURL(o);t.href=i,t.download=ot(e),t.click(),setTimeout(()=>URL.revokeObjectURL(i),1e3)}function So(o){let e={};for(let t of Wo)Object.hasOwn(o,t)&&(e[t]=o[t]);return e}var _e=class o{static version="2.0.0-rc.3";static workerURL=null;constructor(e,t={}){if(!e?.appendChild)throw new TypeError("StlViewer requires a DOM container");this.parent_element=e,this.options={...t},this.error=null,this.ready=!1,this.killsign=!1,this.models=[],this.models_ref=Object.create(null),this.models_count=0,this.max_model_id=-1,this.load_status=Object.create(null),this.load_session=0,this._models=new Map,this._pending=new Map,this._jobs=new Map,this._derived=new Set,this._memoryReservations=new Map,this._exportBytes=0,this._loadTail=Promise.resolve(),this._sceneLoads=new Set,this._aborters=new Set,this._generation=0,this._sessionActive=!1,this._frame=0,this._lastTime=0,this.grid=null,this._gridAuto=!1,this.camera={position:[0,0,100],target:[0,0,0],up:[0,1,0],near:.01,far:1e4},this.when_ready=new Promise((i,s)=>{this._readyResolve=i,this._readyReject=s}),this.when_ready.catch(()=>{});for(let i of["ready_callback","all_loaded_callback","model_loaded_callback","load_error_callback","loading_progress_callback","on_model_drop"])this[i]=t[i]??null;this.onmousedown_callback=t.on_model_mousedown??t.on_model_mouseclick??null,queueMicrotask(()=>this._initialize())}_initialize(){if(!this.killsign)try{let e=this.options;if(this.limits=ao(e),this.bg_color=e.bg_color??e.bgcolor??"transparent",this.bg_color!=="transparent"&&Y(this.bg_color),e.worker_count!==void 0&&(!Number.isInteger(e.worker_count)||e.worker_count<1||e.worker_count>4))throw new Error("worker_count must be 1\u20134");if(e.worker_timeout!==void 0&&me(e.worker_timeout,"worker_timeout"),e.pixel_ratio!==void 0&&me(e.pixel_ratio,"pixel_ratio"),e.controls!==void 0&&e.controls!==0&&e.controls!==1)throw new Error("controls must be 0 or 1");this.center_models=e.center_models??!0,this.auto_resize=e.auto_resize??!0,this.auto_rotate=e.auto_rotate??!1,this.mouse_zoom=e.mouse_zoom??!0,this.send_no_model_click_event=e.send_no_model_click_event??!1,this.zoom=e.zoom??-1,this.canvas_width=e.width??e.canvas_width??"100%",this.canvas_height=e.height??e.canvas_height??"100%",this.canvas=document.createElement("canvas"),this.canvas.style.cssText="display:block;touch-action:none;outline-offset:3px;",this._setSizeStyle(),this.canvas.tabIndex=0,this.canvas.setAttribute("aria-label","3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."),this.parent_element.appendChild(this.canvas),this.renderer=new Ze(this.canvas,()=>this._message("Graphics interrupted. Waiting for the browser to restore rendering\u2026"),()=>{this._removeMessage(),this._invalidate()},i=>this._error(i)),this.controls=new Qe(this.canvas,this.camera,()=>this._invalidate(),(i,s)=>this._pick(i,s),e.controls??0),this.controls.noZoom=!this.mouse_zoom;let t=e.worker_url??o.workerURL;if(e.worker_url===void 0&&typeof e.load_three_files=="string"&&(t=new URL("viewstl.worker.js",new URL(e.load_three_files||"./",document.baseURI)).href),!t&&!e.worker_factory)throw new Error("Specify worker_url when using the CommonJS build");this._pool=new $e(t,{count:e.worker_count??2,timeout:e.worker_timeout??12e4,factory:e.worker_factory}),this._drop=i=>{i.preventDefault();let s=[...i.dataTransfer.files];for(let r of s)this._callback("on_model_drop",r.name),this.add_model({local_file:r})},this._drag=i=>i.preventDefault(),this.set_drag_and_drop(e.allow_drag_and_drop??!0),this.set_auto_resize(this.auto_resize),this.do_resize(),this.camera.position=[e.camerax??0,e.cameray??0,this.zoom>=0?Math.max(this.zoom,1e-6):e.cameraz??100],e.camera_state&&(this.set_camera_state(e.camera_state),this._restoreCamera=e.camera_state),e.grid&&this.set_grid(!0),this._key=i=>{if(!this.controls.enabled)return;let s={ArrowLeft:()=>this.controls.orbit(-.12,0),ArrowRight:()=>this.controls.orbit(.12,0),ArrowUp:()=>this.controls.orbit(0,-.12),ArrowDown:()=>this.controls.orbit(0,.12),"+":()=>this.controls.zoom(.9),"-":()=>this.controls.zoom(1.1),f:()=>this.set_auto_zoom()};s[i.key]&&(i.preventDefault(),s[i.key]())},this.canvas.addEventListener("keydown",this._key),this.ready=!0,this._readyResolve(this),this._callback("ready_callback"),e.models&&this.add_models(e.models),this._invalidate()}catch(e){this.ready=!1,this._readyReject(e),this._error(e),this._message(e.message),this._pool?.dispose(),this.controls?.dispose(),this.renderer?.dispose(),this._resizeObserver?.disconnect()}}_callback(e,...t){let i=this[e];if(typeof i=="function")try{i(...t)}catch(s){console.error(`ViewSTL ${e} callback failed`,s)}}_error(e){return this.error=e?.message??String(e),this._callback("load_error_callback",this.error),null}_message(e){this._removeMessage(),this._messageElement=document.createElement("p"),this._messageElement.setAttribute("role","status"),this._messageElement.textContent=e,this.parent_element.appendChild(this._messageElement)}_removeMessage(){this._messageElement?.remove(),this._messageElement=null}_setSizeStyle(){this.canvas.style.width=typeof this.canvas_width=="number"?`${this.canvas_width}px`:this.canvas_width,this.canvas.style.height=typeof this.canvas_height=="number"?`${this.canvas_height}px`:this.canvas_height}_guard(e){if(this.killsign)return null;try{return e()}catch(t){return this._error(t)}}_model(e){let t=this._models.get(Number(e));if(!t)throw new Error(`Model id not found: ${e}`);return t}_invalidate(){let e=this.renderer?.diagnostic;e&&ge(e.model)!==e.signature&&this.renderer.setDiagnostic(null),!(this.killsign||!this.renderer||this.renderer.lost||this._frame)&&(this._frame=requestAnimationFrame(t=>{this._frame=0,this._render(t)}))}_render(e){let t=this._lastTime?Math.min((e-this._lastTime)/1e3,.1):0;this._lastTime=e;let i=!1;for(let s of this._models.values())s.geometry&&s._animation&&(this._animate(s,e),i||=!!s._animation);this.auto_rotate&&this.controls.enabled&&this.controls.orbit(t*.3,0),this._clip();try{this.renderer.draw([...this._models.values()].filter(s=>!s._sceneImportPending),this.camera,this.bg_color)}catch(s){this.renderer.clear(),this._error(s);return}i||this.auto_rotate&&this.controls.enabled?this._invalidate():this._lastTime=0}_bounds(){let e=[1/0,1/0,1/0],t=[-1/0,-1/0,-1/0],i=0;for(let s of this._models.values())if(s.geometry&&!s._sceneImportPending){i++;let{bounds:r}=s.geometry,n=X(s);for(let d=0;d<8;d++){let l=le(n,[0,1,2].map(c=>r[d&1<<c?"max":"min"][c]));for(let c=0;c<3;c++)e[c]=Math.min(e[c],l[c]),t[c]=Math.max(t[c],l[c])}}return i?{min:e,max:t,center:e.map((s,r)=>(s+t[r])/2),radius:Math.max(Math.hypot(...V(t,e))/2,1e-6)}:null}_clip(){let e=this._bounds(),t=Math.hypot(...V(this.camera.position,e?.center??this.camera.target)),i=e?.radius??10;this.camera.near=Math.max(1e-6,Math.min(i*.001,Math.max(t-i,1e-6)*.1)),this.camera.far=Math.max(this.camera.near*100,t+i*4,100)}_progress(e,t){!this._models.has(e)||this.killsign||(this.load_status[e]=t,this._callback("loading_progress_callback",this.load_status,this.load_session))}add_models(e){return this._guard(()=>{if(!Array.isArray(e))throw new Error("models must be an array");for(let t of e)this.add_model(t)})}add_model(e){return this._guard(()=>{if(!e||typeof e!="object")throw new Error("Invalid model");let t=So(e),i=t.filename??t.local_file?.name??"model.stl";if(/\.vsj(?:[?#]|$)/i.test(i))return this.load_vsj(t.local_file??i);if(/\.vsb(?:[?#]|$)/i.test(i))return this.load_vsb(t.local_file??i);let s=t.id===void 0||t.id===-1?this.max_model_id+1:t.id;if(!Number.isSafeInteger(s)||s<0||this._models.has(s))throw new Error(`Invalid or duplicate model id: ${s}`);let r={...t,id:s,filename:t.filename??t.local_file?.name??"",x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,scalex:1,scaley:1,scalez:1,opacity:1,units:"mm",display:"flat",view_edges:!1,color:null,unitScale:1,center:[0,0,0],geometry:null};for(let l of Le)r[l]=t[l]??(l.startsWith("scale")?t.scale??1:0);for(let l of Le)Q(r[l],l),l.startsWith("scale")&&me(r[l],l);if(r.opacity=t.opacity??t.mesh?.material?.opacity??1,Q(r.opacity,"opacity"),r.opacity<0||r.opacity>1)throw new Error("Opacity must be between 0 and 1");if(r.display=t.display??"flat",Object.assign(r,He(r.display,t.smooth_shading??r.display==="smooth")),r.section=qe(t.section),r.units=t.units??"mm",!["mm","inch"].includes(r.units))throw new Error("Invalid units");r.color=t.color??(t.mesh?.material?.color?.getHexString?"#"+t.mesh.material.color.getHexString():null),r.color&&r.color!=="transparent"&&Y(r.color),r.view_edges=!!t.view_edges,Object.defineProperty(r,"scaleVector",{get:()=>[r.scalex*r.unitScale,r.scaley*r.unitScale,r.scalez*r.unitScale]}),this.max_model_id=Math.max(s,this.max_model_id),this._models.set(s,r),this.models_ref[s]=this.models.length,this.models.push(r),this.models_count=this._models.size,this._sessionActive=!0,this.load_status[s]={loaded:0,total:0};let n=this._generation,d=this._loadModel(r,t,n).then(()=>{n!==this._generation||this._models.get(s)!==r||(this._pending.delete(s),this._finishSession(),this._callback("model_loaded_callback",s))}).catch(l=>{n===this._generation&&this._models.get(s)===r&&(this._pending.delete(s),this._deleteModel(r),l.name!=="AbortError"&&this._error(l),this._finishSession())}).finally(()=>{this._jobs.delete(r)});this._pending.set(s,d)})}_occupiedMemory(e){let t=(this._exportBytes||0)+(this._sceneBytes||0)+(this._printBytes||0)+(this._printCache?4194304:0);for(let i of this._models.values())if(i!==e){let s=this._memoryReservations?.get(i);t+=Math.max(ro(i.geometry),[...s?.values()||[]].reduce((r,n)=>r+n.bytes,0))}return t}_reserveMemory(e,t,i="derive"){this._memoryReservations??=new Map;let s=this._memoryReservations.get(e);s||this._memoryReservations.set(e,s=new Map);let r={};return s.set(r,{bytes:t,kind:i}),()=>{s.delete(r),!s.size&&this._memoryReservations.get(e)===s&&this._memoryReservations.delete(e)}}async _loadModel(e,t,i){let s=()=>{};if(this.options.max_memory_bytes!==void 0){let r=this._loadTail;this._loadTail=new Promise(n=>{s=n}),await r}try{await this._readModel(e,t,i)}finally{s()}}async _readModel(e,t,i){if(await this.when_ready,this.killsign||i!==this._generation||this._models.get(e.id)!==e)return;let s=Math.max(1,this.limits.max_memory_bytes-this._occupiedMemory(e)),r={...this.limits,max_memory_bytes:s};K(r,67108864,"scene");let n=this.limits.max_memory_bytes<Number.MAX_SAFE_INTEGER?this._reserveMemory(e,s-67108864,"parse"):()=>{};try{let d=wt(e);if(t.mesh)e.geometry=Eo(t.mesh,r);else if(t.data)e.geometry=Xe(new Float32Array(t.data.positions),t.data.colors?new Float32Array(t.data.colors):null,r);else{let l=t.local_file??t.filename??t.remote_url;if(!l)throw new Error("A filename, local_file or mesh is required");let c;if(t.remote_url){let _=new URL(t.remote_url);if(!["https:","http:"].includes(_.protocol)||_.username||_.password)throw new Error("Use a public HTTP or HTTPS file URL without login credentials.");if(!this.options.url_import_endpoint)throw new Error("Configure url_import_endpoint to import remote URLs through a relay.");if(c=new URL(this.options.url_import_endpoint,document.baseURI).href,new URL(c).origin!==location.origin)throw new Error("The URL relay must use the same origin as this page.")}let m=this._pool.run({kind:"parse",remote:t.remote_url,format:t.remote_format,relay:c,source:typeof l=="string"?new URL(l,document.baseURI).href:l,name:e.filename,limits:r,features:d.smooth||d.edges||d.wire?d:null},_=>this._progress(e.id,_));this._jobs.set(e,m),e.geometry=await m,t.remote_url&&(e.filename=e.geometry.source_name,delete e.remote_url,delete e.remote_format)}if(this.killsign||i!==this._generation||this._models.get(e.id)!==e){e.geometry=null;return}this.center_models&&(e.center=e.geometry.bounds.min.map((l,c)=>(l+e.geometry.bounds.max[c])/2)),(t.mesh||t.data)&&(d.smooth||d.edges||d.wire)&&await this._derive(e,d),t.animation&&this.animate_model(e.id,t.animation),delete e.mesh,delete e.data,this._invalidate()}finally{n()}}_finishSession(){this.killsign||this._pending.size||this._sceneLoads.size||!this._sessionActive||(this._sessionActive=!1,this._restoreCamera?(this.set_camera_state(this._restoreCamera),this._restoreCamera=null):this.zoom===-1&&this.set_auto_zoom(),this._gridAuto&&this.set_grid(!0),this.load_session++,this._callback("all_loaded_callback"),this._invalidate())}_deleteModel(e){for(let[t,i]of this._printJobs||[])i.has(e)&&t.abort();this._printCache=null,this.renderer?.selection===e&&this.renderer.setSelection(null),this.renderer?.remove(e),this._jobs.get(e)?.cancel?.();for(let t of e._deriveJobs||[])t.cancel?.();this._models.delete(e.id),this._memoryReservations.delete(e),this.models[this.models_ref[e.id]]=null,delete this.models_ref[e.id],delete this.load_status[e.id],this.models_count=this._models.size,this._invalidate()}remove_model(e){return this._guard(()=>{let t=this._model(e);this._deleteModel(t),this._pending.delete(t.id),this._finishSession()})}clean(){if(!this.killsign){this._generation++,this._printCache=null,this._pool?.clear();for(let e of this._aborters)e.abort();this._aborters.clear(),this._models.clear(),this._pending.clear(),this._jobs.clear(),this._derived.clear(),this._memoryReservations.clear(),this._sceneLoads.clear(),this.models=[],this.models_ref=Object.create(null),this.models_count=0,this.max_model_id=-1,this.load_status=Object.create(null),this._sessionActive=!1,this.renderer?.clear(),this._restoreCamera=null,this._invalidate()}}dispose(){if(!this.killsign){this.clean(),this.killsign=!0,this.ready=!1,this._readyReject(new Error("Viewer disposed")),cancelAnimationFrame(this._frame),this._frame=0,this._pool?.dispose(),this.controls?.dispose(),this._resizeObserver?.disconnect(),this._windowResize&&window.removeEventListener("resize",this._windowResize),this.renderer?.dispose(),this.canvas&&(this.canvas.removeEventListener("drop",this._drop),this.canvas.removeEventListener("dragover",this._drag),this.canvas.removeEventListener("keydown",this._key),this.canvas.remove()),this._removeMessage();for(let e of Object.keys(this))e.endsWith("_callback")&&(this[e]=null);this.options={}}}async when_idle(){for(await this.when_ready;this._pending.size||this._sceneLoads.size||this._derived.size;)await Promise.allSettled([...this._pending.values(),...this._sceneLoads,...this._derived]);return this}_derive(e,t){let i=this._occupiedMemory(e)+[...this._memoryReservations?.get(e)?.values()||[]].filter(m=>m.kind==="derive").reduce((m,_)=>m+_.bytes,0),s=no(e.geometry.triangles,{...t,smooth:t.smooth||!!e.geometry.smoothNormals,edges:t.edges||!!e.geometry.edges,wire:t.wire||!!e.geometry.wire});K(this.limits,i+s,"view mode");let r=this._reserveMemory(e,s-67108864),n=this._generation,d=e._deriveRevision=(e._deriveRevision||0)+1,l;try{let m=e.geometry.positions.slice();l=this._pool.run({kind:"derive",positions:m,features:t,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-i}},null,[m.buffer])}catch(m){throw r(),m}(e._deriveJobs??=new Set).add(l);let c=l.then(m=>!this.killsign&&n===this._generation&&d===e._deriveRevision&&this._models.get(e.id)===e?(Object.assign(e.geometry,m),this.renderer.remove(e),this._invalidate(),!0):!1).catch(m=>{m.name!=="AbortError"&&n===this._generation&&this._error(m)}).finally(()=>{r(),e._deriveJobs.delete(l),this._derived.delete(c)});return this._derived.add(c),c}set_view(e,t){return this._guard(()=>{let i=this._model(e),s=i._requestedView||i,r=He(t.display??s.display,t.smooth_shading??s.smooth_shading),n=wt({...i,...r}),d=i.geometry,c=d&&(n.smooth&&!d.smoothNormals||n.wire&&!d.wire||n.edges&&(!d.edges||d.edgeAngle!==n.edgeAngle))?this._derive(i,n):null,m=(i._viewRevision||0)+1;i._viewRevision=m,i._requestedView=r;let _=w=>{this._models.get(e)!==i||i._viewRevision!==m||(w&&Object.assign(i,r),delete i._requestedView,this._invalidate())};if(!c)_(!0);else{let w=c.then(_).finally(()=>this._derived.delete(w));this._derived.add(w)}})}set_display(e,t){return this.set_view(e,{display:t,...t==="flat"||t==="smooth"?{smooth_shading:t==="smooth"}:{}})}set_smooth(e,t){return this._guard(()=>{let i=this._model(e),s=i._requestedView||i;return this.set_view(e,{display:s.display==="smooth"?"flat":s.display,smooth_shading:t})})}set_section(e,t){return this._guard(()=>{this._model(e).section=qe(t),this._invalidate()})}set_color(e,t){return this._guard(()=>{t!==null&&t!=="transparent"&&Y(t);let i=this._model(e);i.color=t,this._invalidate()})}set_opacity(e,t){return this._guard(()=>{if(Q(t,"opacity"),t<0||t>1)throw new Error("Opacity must be between 0 and 1");this._model(e).opacity=t,this._invalidate()})}set_position(e,t,i,s){return this._guard(()=>{let r=this._model(e);[t,i,s].forEach((n,d)=>{n!==void 0&&(r[["x","y","z"][d]]=Q(n,"position"))}),this._invalidate()})}set_scale(e,t,i=t,s=t,r=!1){return this._guard(()=>{let n=this._model(e);if([t,i,s].forEach(d=>me(d,"scale")),r){if(t/n.scalex!==i/n.scaley||t/n.scalex!==s/n.scalez)throw new Error("keep_prev_scale requires a uniform geometry factor");n.unitScale*=t/n.scalex}else[n.scalex,n.scaley,n.scalez]=[t,i,s];n._stats=null,this._invalidate()})}set_selection(e){return this._guard(()=>{this.renderer?.setSelection(e===null?null:this._model(e)),this._invalidate()})}set_rotation(e,t,i,s,r=!1){return this._guard(()=>{let n=this._model(e);[t,i,s].forEach((d,l)=>{let c=["rotationx","rotationy","rotationz"][l];d!==void 0&&(n[c]=Q(d,"rotation")+(r?n[c]:0))}),this._invalidate()})}rotate(e,t,i,s){return this.set_rotation(e,t,i,s,!0)}set_edges(e,t){return this._guard(()=>{let i=this._model(e);i.view_edges=!!t,t&&i.geometry&&!i.geometry.edges&&this._derive(i,{edges:!0}),this._invalidate()})}set_grid(e,t,i=10){return this._guard(()=>{if(this._gridAuto=!!e&&t===void 0,!e)this.grid=null,this.renderer?.setGrid(null);else{let s=this._bounds();if(t??=s&&Math.max(...V(s.max,s.min))*1.2||10,me(t,"grid size"),!Number.isInteger(i)||i<1||i>1e3)throw new Error("Grid divisions must be 1\u20131000");this.grid={size:t,divisions:i},this.renderer?.setGrid(this.grid)}this._invalidate()})}set_infinite_grid(e,t=1,i=0){return this._guard(()=>{me(t,"grid spacing"),Q(i,"grid height"),this._gridAuto=!1,this.grid=e?{infinite:!0,spacing:t,height:i}:null,this.renderer?.setGrid(this.grid),this._invalidate()})}set_model_units(e,t,i=!1){return this._guard(()=>{if(!["mm","inch"].includes(t))throw new Error("Units must be mm or inch");let s=this._model(e);i&&s.units!==t&&(s.unitScale*=t==="inch"?25.4:1/25.4),s.units=t,s._stats=null,this._invalidate()})}set_bg_color(e){return this._guard(()=>{e!=="transparent"&&Y(e),this.bg_color=e,this._invalidate()})}set_center_models(e){this.center_models=!!e}set_zoom(e=-1){return this._guard(()=>{if(Q(e,"zoom"),e===-1)this.zoom=-1,this.set_auto_zoom();else{if(e<0)throw new Error("Zoom must be non-negative or -1");this.zoom=e,this.camera.position[2]=Math.max(e,1e-6),this._invalidate()}})}set_auto_zoom(){let e=this._bounds();if(!e)return;this.zoom=-1;let t=(this.canvas?.clientWidth||1)/(this.canvas?.clientHeight||1),i=Math.min(Math.PI/8,Math.atan(Math.tan(Math.PI/8)*t)),s=H(V(this.camera.position,this.camera.target));this.camera.target=e.center,this.camera.position=re(e.center,oe(Math.hypot(...s)?s:[0,0,1],e.radius/Math.sin(i)*1.08)),this._invalidate()}set_auto_rotate(e){this.auto_rotate=!!e,this._invalidate()}set_mouse_zoom(e){this.mouse_zoom=!!e,this.controls&&(this.controls.noZoom=!e)}set_auto_resize(e){this.auto_resize=!!e,this._resizeObserver?.disconnect(),this._windowResize&&window.removeEventListener("resize",this._windowResize),!(!e||!this.canvas)&&(typeof ResizeObserver<"u"&&(this._resizeObserver=new ResizeObserver(()=>this.do_resize()),this._resizeObserver.observe(this.parent_element)),this._windowResize=()=>this.do_resize(),window.addEventListener("resize",this._windowResize))}do_resize(){if(!this.renderer||this.killsign)return;let e=this.canvas.getBoundingClientRect();this.renderer.resize(e.width||this.parent_element.clientWidth||300,e.height||this.parent_element.clientHeight||150,this.options.pixel_ratio??Math.min(globalThis.devicePixelRatio||1,2)),this._invalidate()}set_on_model_mousedown(e){this.onmousedown_callback=e}set_on_model_mouseclick(e){this.set_on_model_mousedown(e)}set_drag_and_drop(e){this.allow_drag_and_drop=!!e,this.canvas&&(this.canvas.removeEventListener("drop",this._drop),this.canvas.removeEventListener("dragover",this._drag),e&&(this.canvas.addEventListener("drop",this._drop),this.canvas.addEventListener("dragover",this._drag)))}get_camera_state(){return{position:tt(this.camera.position),up:tt(this.camera.up),target:tt(this.camera.target)}}set_camera_state(e){return this._guard(()=>{let t=e.position?At(e.position,"position"):this.camera.position,i=e.up?At(e.up,"up"):this.camera.up,s=e.target?At(e.target,"target"):this.camera.target;if(!Math.hypot(...i)||!Math.hypot(...V(t,s)))throw new Error("Degenerate camera state");this.camera.position=t,this.camera.up=H(i),this.camera.target=s,this._invalidate()})}set_camera(e,t,i){return this.set_camera_state({position:{x:e??this.camera.position[0],y:t??this.camera.position[1],z:i??this.camera.position[2]}})}pick_model(e,t){if(!this.ready||this.killsign||!Number.isFinite(e)||!Number.isFinite(t))return null;let i=this.canvas.getBoundingClientRect();if(!i.width||!i.height||e<i.left||e>i.right||t<i.top||t>i.bottom)return null;let s=fe(Ye(this.camera,i.width/i.height)),r=le(s,[(e-i.left)/i.width*2-1,1-(t-i.top)/i.height*2,1]),n=H(V(r,this.camera.position)),d=null,l=1/0;for(let c of this._models.values())if(c.geometry&&c.opacity>0&&c.color!=="transparent"){let m=fe(X(c)),_=lo(c.geometry,le(m,this.camera.position),le(m,n,0),We(c.geometry.bounds,c.section));_<l&&(l=_,d=c.id)}return d===null?null:{id:d,distance:l}}_pick(e,t){if(!this.onmousedown_callback)return;let i=this.pick_model(e.clientX,e.clientY);(i||this.send_no_model_click_event)&&this._callback("onmousedown_callback",i?.id??null,e,i?.distance??0,t)}get_model_info(e){return this._guard(()=>{let t=this._model(e),i=t.geometry;if(!i)return null;let s=t.scaleVector,r=s.join(",");(!t._stats||t._stats.key!==r)&&(t._stats={key:r,...co(i,s)});let n=t.units==="inch"?1/25.4:1;return{name:t.filename,orig_filename:t.orig_filename??null,position:{x:t.x,y:t.y,z:t.z},dims:tt(i.bounds.min.map((d,l)=>(i.bounds.max[l]-d)*s[l])),rotation:{x:t.rotationx,y:t.rotationy,z:t.rotationz},display:t.display,smooth_shading:t.smooth_shading,section:t.section?{...t.section}:null,color:t.color??(i.colors?"#ffffff":"#909090"),scale:{x:t.scalex,y:t.scaley,z:t.scalez},volume:t._stats.volume*n**3,area:t._stats.area*n**2,triangles:i.triangles,bounds:{min:[...i.bounds.min],max:[...i.bounds.max]},has_colors:!!i.colors,source_units:i.units||null,units:t.units,opacity:t.opacity,file_bytes:i.file_bytes??null}})}get_model_data(e){return this._guard(()=>{let t=this._model(e);return t.geometry?{positions:t.geometry.positions.slice(),colors:t.geometry.colors?.slice()??null}:null})}get_model_mesh(e){return this._guard(()=>{let t=this._model(e);return t.geometry?ko(t,this.options.three??globalThis.THREE):null})}get_stl_bin(e){return this._guard(()=>{let t=this._model(e);return t.geometry?ho(t.geometry,t.scaleVector,t.center):null})}check_printability(e={}){return bo.call(this,e)}is_printability_current(e){return et(this,e)}show_printability(e,t){return vo.call(this,e,t)}clear_printability(){this.renderer?.setDiagnostic(null),this._invalidate()}async export_models(e={}){let t=wo(e),i=e.signal,s=()=>{throw new DOMException("Export cancelled","AbortError")};if(i?.aborted&&s(),await this.when_ready,i?.aborted&&s(),this.killsign)throw new Error("Viewer disposed");let r=e.model_ids;if(r==="selected"){let f=this.renderer?.selection;r=f?[f.id]:[]}else r===void 0&&(r=[...this._models.keys()]);if(!Array.isArray(r)||r.some(f=>!Number.isSafeInteger(f))||new Set(r).size!==r.length)throw new Error("model_ids must be an array of unique model IDs or 'selected'");if(!r.length)throw new Error("No models to export");let n=r.map(f=>{let u=this._model(f);if(!u.geometry)throw new Error(`Model ${f} has not finished loading`);return u});if(n.reduce((f,u)=>f+u.geometry.triangles,0)>this.limits.max_triangles)throw new Error("Export triangle limit exceeded");let l=xo(n,t),c=this._occupiedMemory();K(this.limits,c+l,"export"),this._exportBytes=(this._exportBytes||0)+l;let m,_,w;try{let f=n.map(p=>{let y=t.apply_transforms?p:{...p,center:[0,0,0],x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,scaleVector:[p.unitScale,p.unitScale,p.unitScale]},g=X(y),A=t.units==="inch"?1/25.4:1;for(let k=0;k<4;k++)for(let E=0;E<3;E++)g[k*4+E]*=A;return{id:p.id,name:ot(p.filename),matrix:g,positions:p.geometry.positions.slice(),colors:t.colors?p.geometry.colors?.slice():null,color:t.colors&&p.color&&p.color!=="transparent"?Y(p.color):null,opacity:p.color==="transparent"?0:p.opacity}}),u=f.flatMap(p=>[p.positions.buffer,...p.colors?[p.colors.buffer]:[]]);m=this._pool.run({kind:"export",models:f,options:t,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-c}},p=>{try{e.on_progress?.(p)}catch(y){w=y,m.cancel()}},u),_=()=>m.cancel(),i?.addEventListener("abort",_,{once:!0}),i?.aborted&&_();try{return await m}catch(p){throw w||p}}finally{_&&i?.removeEventListener("abort",_),this._exportBytes-=l}}async download_models(e={}){let t=await this.export_models(e);if(e.signal?.aborted)throw new DOMException("Export cancelled","AbortError");return Ce(t.blob,t.filename),t}download_model(e,t){let i=this.get_stl_bin(e);i&&Ce(new Blob([i],{type:"model/stl"}),t??this._model(e).filename??"model.stl")}async export_scene(e={}){let t=String(e.format||"").toLowerCase();if(!["vsb","vsj"].includes(t))throw new Error("Scene format must be vsb or vsj");if(e.force_basename!==void 0&&typeof e.force_basename!="boolean")throw new Error("force_basename must be a boolean");if(e.on_progress!==void 0&&typeof e.on_progress!="function")throw new Error("on_progress must be a function");let i=e.signal,s=()=>{if(i?.aborted)throw new DOMException("Export cancelled","AbortError")};if(s(),await this.when_ready,s(),this.killsign)throw new Error("Viewer disposed");if(this._pending.size||this._sceneLoads.size)throw new Error("Wait for models to finish opening before saving the scene");let r=[...this._models.values()];if(!r.length)throw new Error("No models to export");if(r.some(u=>!u.geometry))throw new Error("A model has not finished loading");if(r.length+(t==="vsb"?1:0)>this.limits.max_archive_entries)throw new Error("Scene model count limit exceeded");let n=r.reduce((u,p)=>u+p.geometry.triangles,0);if(n>this.limits.max_triangles)throw new Error("Scene triangle limit exceeded");let d=this.get_vsj(!0,!!e.force_basename,t==="vsb"),l=JSON.stringify(d),c=new Blob([l]).size;if(c>this.limits.max_file_bytes)throw new Error("Scene manifest size limit exceeded");if(t==="vsb"&&c+r.length*84+n*50>this.limits.max_uncompressed_bytes)throw new Error("Scene archive limit exceeded");let m=67108864+l.length*8+(t==="vsb"?n*512:0);K(this.limits,this._occupiedMemory()+m,"scene export"),this._exportBytes+=m;let _,w,f;try{e.on_progress?.({loaded:0,total:r.length,phase:"exporting"}),s();let u;if(t==="vsj")u=new Blob([l],{type:"application/json"}),e.on_progress?.({loaded:r.length,total:r.length,phase:"complete"});else{let p=r.map(y=>({id:y.id,triangles:y.geometry.triangles,positions:y.geometry.positions.slice(),colors:y.geometry.colors?.slice()||null,scale:y.scaleVector,center:[...y.center]}));_=this._pool.run({kind:"export-scene",scene:d,models:p,limits:this.limits},y=>{try{e.on_progress?.(y)}catch(g){f=g,_.cancel()}},p.flatMap(y=>[y.positions.buffer,...y.colors?[y.colors.buffer]:[]])),w=()=>_.cancel(),i?.addEventListener("abort",w,{once:!0}),i?.aborted&&w();try{u=await _}catch(y){throw f||y}}return s(),{blob:u,format:t,filename:St(e.filename,t),model_ids:r.map(p=>p.id),triangles:n,representation:"scene"}}finally{w&&i?.removeEventListener("abort",w),this._exportBytes-=m}}async download_scene(e={}){let t=await this.export_scene(e);if(e.signal?.aborted||this.killsign)throw new DOMException("Export cancelled","AbortError");return Ce(t.blob,t.filename),t}get_vsj(e=!1,t=!1,i=!1){let s={canvas_height:this.canvas_height,bg_color:this.bg_color,camera_state:this.get_camera_state(),auto_rotate:this.auto_rotate,mouse_zoom:this.mouse_zoom,auto_resize:this.auto_resize,center_models:this.center_models,models:[]};this.grid&&(s.grid=!0,this.grid.infinite&&(s.infinite_grid={spacing:this.grid.spacing,height:this.grid.height}));for(let r of this._models.values())if(r.geometry){let n={id:i?-1:r.id,filename:i?`${r.id}.stl`:t?ot(r.filename):r.filename,units:r.units,display:r.display,opacity:r.opacity,view_edges:r.view_edges,smooth_shading:r.smooth_shading,section:r.section?{...r.section}:null};i&&(n.orig_filename=ot(r.orig_filename||r.filename));for(let d of Le)d.startsWith("scale")||(n[d]=r[d]);i||(n.scalex=r.scalex*r.unitScale,n.scaley=r.scaley*r.unitScale,n.scalez=r.scalez*r.unitScale),r.color&&(n.color=r.color),r.animation&&(n.animation=structuredClone(r.animation)),s.models.push(n)}return e?s:JSON.stringify(s)}download_vsj(e="1.vsj"){Ce(new Blob([this.get_vsj()],{type:"application/json"}),e)}get_vsb(){return{vsj:this.get_vsj(!0,!0,!0),files:[...this._models.values()].filter(e=>e.geometry).map(e=>({id:e.id,bin:this.get_stl_bin(e.id)}))}}async get_vsb_blob(){await this.when_ready;let e=this.get_vsb(),t=this._pool.run({kind:"zip",scene:e,limits:this.limits},null,e.files.map(i=>i.bin));return new Blob([await t],{type:"application/zip"})}download_vsb(e="1.vsb"){return this.get_vsb_blob().then(t=>Ce(t,/\.vsb$/i.test(e)?e:`${e}.vsb`)).catch(t=>this._error(t))}import_scene(e,t={}){return go.call(this,e,t)}_sceneLoad(e,t){let i=this._generation;this._sessionActive=!0;let s=(async()=>{if(await this.when_ready,i!==this._generation||this.killsign)return;let r,n,d=document.baseURI;if(typeof e=="string"&&(d=new URL(e,document.baseURI).href),t){if(n=await this._pool.run({kind:"unzip",source:typeof e=="string"?d:e,limits:this.limits}),!Object.hasOwn(n,"json_data.vsj"))throw new Error("VSB manifest json_data.vsj is missing");r=JSON.parse(new TextDecoder().decode(n["json_data.vsj"]))}else{let l=new AbortController;this._aborters.add(l);try{r=JSON.parse(new TextDecoder().decode(await Mo(typeof e=="string"?d:e,this.limits.max_file_bytes,()=>{},l.signal)))}finally{this._aborters.delete(l)}}if(!(i!==this._generation||this.killsign)){if(!r||!Array.isArray(r.models)||r.models.length>this.limits.max_archive_entries)throw new Error("Invalid or oversized scene manifest");r.bg_color!==void 0&&this.set_bg_color(r.bg_color),r.center_models!==void 0&&this.set_center_models(r.center_models),r.auto_rotate!==void 0&&this.set_auto_rotate(r.auto_rotate),r.mouse_zoom!==void 0&&this.set_mouse_zoom(r.mouse_zoom),r.auto_resize!==void 0&&this.set_auto_resize(r.auto_resize),r.canvas_height!==void 0&&(this.canvas_height=r.canvas_height,this._setSizeStyle(),this.do_resize()),r.infinite_grid?this.set_infinite_grid(!0,r.infinite_grid.spacing,r.infinite_grid.height):r.grid&&this.set_grid(!0),r.camera_state&&(this._restoreCamera=r.camera_state);for(let l of r.models){if(!l||typeof l!="object")throw new Error("Invalid scene model");let c=So(l);if(delete c.mesh,delete c.data,delete c.local_file,n){if(typeof c.filename!="string"||!Object.hasOwn(n,c.filename))throw new Error("VSB model file is missing");c.local_file=new File([n[c.filename]],c.filename)}else c.filename&&(c.filename=new URL(c.filename,d).href);this.add_model(c)}}})().catch(r=>{i===this._generation&&r.name!=="AbortError"&&this._error(r)}).finally(()=>{this._sceneLoads.delete(s),this._finishSession()});this._sceneLoads.add(s)}load_vsj(e){return this._sceneLoad(e,!1)}load_vsb(e){return this._sceneLoad(e,!0)}animate_model(e,t){return this._guard(()=>{let i=this._model(e);if(!t){i.animation=null,i._animation=null;return}let s=structuredClone(t),r=[];for(let n of["delta","exact"])if(s[n]){let d=s[n];me(d.msec??300,"animation duration");let l={};for(let c of Le){let m=d[c]??(c.startsWith("scale")?d.scale:void 0);m!==void 0&&(Q(m,c),n==="exact"&&c.startsWith("scale")&&me(m,c),l[c]=m)}r.push({mode:n,values:l,duration:d.msec??300,loop:n==="delta"&&!!d.loop,from:Object.fromEntries(Le.map(c=>[c,i[c]]))})}if(!r.length)throw new Error("Animation needs delta or exact");i.animation=s,i._animation={start:null,tracks:r},this._invalidate()})}_animate(e,t){let i=e._animation;i.start??=t;let s=!1;for(let r of i.tracks){let n=(t-i.start)/r.duration,d=r.loop?n:Math.min(n,1);s||=r.loop||n<1;for(let[l,c]of Object.entries(r.values))e[l]=l.startsWith("scale")?Math.max(1e-6,r.mode==="delta"?r.from[l]+c*d:r.from[l]+(c-r.from[l])*d):r.mode==="delta"?r.from[l]+c*d:r.from[l]+(c-r.from[l])*d}e._stats=null,s||(e._animation=null,e.animation=null)}async get_image_blob(e="image/png"){await this.when_idle(),this._clip(),this.renderer.draw([...this._models.values()],this.camera,this.bg_color,!1);let t=new Promise((i,s)=>this.canvas.toBlob(r=>r?i(r):s(new Error("Screenshot failed")),e));return this._invalidate(),t}};var zt=new WeakMap,Yo=["background-color","background-value","cancel-load","clear-scene","close-colors","close-info","close-model","close-scene","close-section","close-view","color-mode-note","colors-button","colors-panel","depth-controls","depth-model-button","dismiss-notice","drop-message","drop-overlay","empty-state","file-heading","file-input","filename","finish-move","fit-button","grid-button","image-button","info-button","info-dimensions","info-panel","info-size","info-triangles","live-status","loading-label","loading-state","memory-budget","model-color","model-color-value","model-controls","model-list","model-tool","move-controls","move-gesture-message","move-mode","move-mode-name","move-model-button","notice","notice-message","open-label","position-details","position-error","position-units","position-x","position-y","position-z","reset-position","reset-rotation","restore-color","rotate-controls","rotate-model-button","rotation-error","rotation-x","rotation-y","rotation-z","sample-button","scene-arrangement","scene-button","scene-count","scene-panel","section-axis","section-button","section-enabled","section-flip","section-panel","section-position","section-value","smooth-shading","unit-note","view-button","view-description","view-label","view-panel","view-status","viewer","workspace"];function Ao(o,e={}){if(!o?.querySelectorAll)throw new TypeError("A Document or HTMLElement root is required");if(zt.has(o))throw new Error("ViewSTL app is already mounted on this root");let t=o.ownerDocument||o,i=t.defaultView,s=i.URL,r=new Map([...o.querySelectorAll("[id]")].map(a=>[a.id,a])),n=a=>r.get((e.id_prefix||"")+a),d=Yo.filter(a=>!n(a));if(Et.some(a=>n(a))&&d.push(...Et.filter(a=>!n(a))),kt.some(a=>n(a))&&d.push(...kt.filter(a=>!n(a))),d.length)throw new Error("Missing ViewSTL controls: "+d.join(", "));let l=new i.AbortController,c=o===t?i:o,m=!1,_=null,w=null,f=()=>{if(m)throw new Error("ViewSTL app is disposed")},u=(a,h,b,S={})=>a.addEventListener(h,b,{...S,signal:l.signal}),p=Vt(i.navigator.deviceMemory),y=["view","section","info","scene","colors"],g=new Map,A=[],k=["#64a5b2","#dd766b","#edb14c","#7ca984","#927fbc","#5a8ec7","#89949d"],E,x,C=0,I=0,v=null,F=0,z=null,P,L,D,M=!1,R=!1,j=!1,O=0,we=1,J=!1,q="move",W,ee,ue="transparent",zo={flat:"Solid",edges:"Solid + edges",wireframe:"Wireframe",xray:"X-ray",mesh:"Mesh overlay",studio:"Studio",orientation:"Face orientation"},ye=[...o.querySelectorAll('[name="view-mode"]')];e.id_prefix&&ye.forEach(a=>a.name=e.id_prefix+"view-mode");let it=e.sample_url,Ie=new Intl.NumberFormat(void 0,{maximumFractionDigits:2});function B(a){m||(n("live-status").textContent=a)}function ne(a){m||(n("notice-message").textContent=a,n("notice").hidden=!1)}function xe(){x?.set_selection(!R&&g.has(v)&&(J||!n("scene-panel").hidden)?v:null)}function Rt(a){n(a+"-panel").hidden=!0,n(a+"-button").setAttribute("aria-expanded","false"),xe()}function ce(){y.forEach(Rt)}function Pe(a){let h=n(a+"-panel").hidden;ce(),n(a+"-panel").hidden=!h,n(a+"-button").setAttribute("aria-expanded",String(h)),xe()}function Me(a){return a==null?"Sample model":a<1024**2?`${Ie.format(a/1024)} KiB`:a<1024**3?`${Ie.format(a/1024**2)} MiB`:`${Ie.format(a/1024**3)} GiB`}function rt(){let a=g.get(v),h=/^#[a-f0-9]{6}$/i.test(a?.color)?a.color:"#ffffff";n("model-color").value=h,n("model-color-value").textContent=a?.color===null?"File colors":h.toUpperCase(),n("restore-color").textContent=a?.embedded?"Restore file colors":"Restore original color",o.querySelectorAll("[data-model-color]").forEach(b=>b.setAttribute("aria-pressed",String(b.dataset.modelColor===a?.color))),o.querySelectorAll("[data-background]").forEach(b=>b.setAttribute("aria-pressed",String(b.dataset.background===ue))),n("background-color").value=ue==="transparent"?"#fafbfb":ue,n("background-value").textContent=ue==="transparent"?"Default":ue.toUpperCase()}function Lt(){let a=v===null?null:x.get_model_info(v),h=a?.display==="smooth"?"flat":a?.display||"flat";for(let T of ye)T.checked=T.value===h,T.disabled=R;n("view-label").textContent=zo[h],n("smooth-shading").checked=a?.smooth_shading||!1,n("smooth-shading").disabled=R||h==="wireframe",n("view-status").textContent=h==="wireframe"?"Smooth shading is available in surface views.":"Shading changes the appearance, not the geometry.";let b=h==="orientation"?"Blue: front faces \xB7 Red: back faces":"";n("view-description").textContent=b,n("view-description").hidden=!b,n("color-mode-note").hidden=h!=="orientation";let S=a?.section;n("section-enabled").checked=!!S,n("section-axis").value=S?.axis||"z",n("section-position").value=(S?.position??.5)*100,n("section-value").textContent=n("section-position").value+"%",n("section-flip").setAttribute("aria-pressed",String(S?.flip||!1)),n("section-button").setAttribute("aria-pressed",String(!!S)),o.querySelectorAll("[data-selected-name]").forEach(T=>T.textContent=g.get(v)?.name||""),rt(),st(),P?.sync(),L?.sync()}function Ct(){if(v===null)return;let a=x.get_model_info(v),h=g.get(v),b=h.units==="mm"?" mm":"";n("info-triangles").textContent=a.triangles.toLocaleString(),n("info-dimensions").textContent=[a.dims.x,a.dims.y,a.dims.z].map(S=>Ie.format(S)).join(" \xD7 ")+b,n("info-size").textContent=Me(h.size),n("unit-note").textContent=b?"Dimensions are shown in millimetres.":"Dimensions are in model units. STL and OBJ files do not specify physical units."}function It(){n("model-list").replaceChildren(...[...g.values()].map(a=>{let h=t.createElement("li"),b=t.createElement("button"),S=t.createElement("button"),T=t.createElement("button");return h.className="model-row",h.classList.toggle("selected",a.id===v),b.className="model-swatch",b.dataset.colorModel=a.id,b.style.backgroundColor=a.color||"#b7c5c9",b.setAttribute("aria-label","Color for "+a.name),b.title="Change color",S.className="model-pick",S.dataset.selectModel=a.id,S.textContent=a.name,S.title=a.name,S.setAttribute("aria-pressed",String(a.id===v)),T.className="icon-button",T.dataset.removeModel=a.id,T.textContent="\xD7",T.setAttribute("aria-label","Remove "+a.name),h.append(b,S,T),h})),n("scene-count").textContent=g.size,n("scene-arrangement").disabled=g.size===0}function be(){let a=g.size>0;n("workspace").classList.toggle("has-models",a),n("viewer").hidden=!a&&!R,n("empty-state").hidden=a||R,n("file-heading").hidden=!a,n("model-controls").hidden=!a,n("filename").textContent=g.get(v)?.name||"",n("filename").title=n("filename").textContent,n("open-label").textContent=a?"Add models":"Open files",n("drop-message").textContent=a?"Add to your scene.":"Drop it here.",It(),Lt(),Ct(),a||ce(),xe()}function Te(a){a&&P?.close(),!m&&(a&&G(!1),R=a,n("loading-state").hidden=!a,n("workspace").setAttribute("aria-busy",String(a)),be())}function Fe(a){P?.close(),g.has(a)&&(G(!1),I++,v=a,be())}function Ro(){return Qt([...g.values()],n("scene-arrangement").value)}function Oe(){x?.set_infinite_grid(j,we,O),n("grid-button").setAttribute("aria-pressed",String(j))}function nt(){if(G(!1),n("scene-arrangement").value==="original")for(let h of g.values())h.rotation={...h.originalRotation||{x:0,y:0,z:0}};let a=Ro();for(let h of a)g.get(h.id).position={x:h.x,y:h.y,z:h.z},at(g.get(h.id));(n("scene-arrangement").value!=="custom"||!g.size)&&(O=$t([...g.values()],a)),st(),Oe(),x?.set_auto_zoom()}function st(){let a=g.get(v);for(let h of["x","y","z"]){let b=n("position-"+h);b.value=Number((a?.position?.[h]||0).toPrecision(12)),b.disabled=!a,b.removeAttribute("aria-invalid")}n("position-error").hidden=!0,n("position-units").textContent=a?.units==="mm"?"Offset from file coordinates, in mm.":"Offset from file coordinates, in model units.",n("move-model-button").disabled=!a||R,n("reset-position").disabled=!a,Tt();for(let h of["rotate-model-button","depth-model-button","depth-nearer","depth-farther"])n(h).disabled=!a||R}function Ee(a,h,b=!0){return!g.has(a)||!Object.values(h).every(S=>Number.isFinite(S)&&Math.abs(S)<=1e12)?!1:(g.get(a).position={...h},at(g.get(a)),n("scene-arrangement").value="custom",a===v&&b&&st(),!0)}function at(a){let h=gt(a),b=a.rotation;x.set_rotation(a.id,...["x","y","z"].map(S=>b[S]%360*Math.PI/180)),x.set_position(a.id,h.x,h.y,h.z)}function Pt(){let a=g.get(v);return a?{...a,center:Xt(a),radius:Kt(a)}:null}function Tt(){let a=g.get(v);for(let h of["x","y","z"]){let b=n("rotation-"+h);b.value=Number((a?.rotation?.[h]||0).toPrecision(10)),b.disabled=!a,b.removeAttribute("aria-invalid")}n("rotation-error").hidden=!0,n("reset-rotation").disabled=!a}function Ne(a,h,b=!0){return!g.has(a)||!Object.values(h).every(S=>Number.isFinite(S)&&Math.abs(S)<=36e4)?!1:(g.get(a).rotation={...h},at(g.get(a)),n("scene-arrangement").value="custom",b&&a===v&&Tt(),!0)}let Lo={move:"Move",rotate:"Rotate",depth:"Depth"},Ft={move:"Drag to move the selected model",rotate:"Drag to rotate the selected model",depth:"Drag up for nearer, down for farther"};function Co(){x?.canvas&&(x.canvas.dataset.modelTool=q),o.querySelectorAll("[data-transform-tab]").forEach(a=>a.setAttribute("aria-pressed",String(a.dataset.transformTab===q)));for(let a of["move","rotate","depth"])n(a+"-controls").hidden=a!==q,n(a+"-model-button").setAttribute("aria-pressed",String(J&&a===q));n("model-tool").value=q,n("move-gesture-message").textContent=Ft[q]+" \xB7 Scroll to adjust depth"}function G(a,h=q){W?.setEnabled(!1),q=h,J=!!a&&v!==null&&!R,W?.setEnabled(J),Co(),n("move-mode").hidden=!J,n("move-mode-name").textContent=g.get(v)?.name||"",n("workspace").classList.toggle("moving-model-mode",J),x?.canvas&&x.canvas.setAttribute("aria-label",J?Ft[q]+". Scroll changes only its depth. Escape exits.":"3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."),J&&(ce(),x.canvas.focus({preventScroll:!0}),B(Lo[q]+" tool for "+g.get(v).name+". Done or Escape returns to viewing.")),xe()}for(let a of["x","y","z"]){let h=n("rotation-"+a);u(h,"input",()=>{v===null||!h.value.trim()||Ne(v,{...g.get(v).rotation,[a]:h.valueAsNumber},!1)&&(h.removeAttribute("aria-invalid"),n("rotation-error").hidden=!0)}),u(h,"change",()=>{v!==null&&(!h.value.trim()||!Ne(v,{...g.get(v).rotation,[a]:h.valueAsNumber})?(h.setAttribute("aria-invalid","true"),n("rotation-error").hidden=!1):B("Model rotation updated."))})}u(n("reset-rotation"),"click",()=>{G(!1),v!==null&&(Ne(v,{...g.get(v).originalRotation||{x:0,y:0,z:0}}),B("Selected model rotation reset."))});for(let a of["move","rotate","depth"])u(n(a+"-model-button"),"click",()=>G(!J||q!==a,a));for(let[a,h]of[["depth-nearer",-.12],["depth-farther",.12]])u(n(a),"click",()=>{let b=Pt();b&&(Ee(b.id,Ae(b,x.get_camera_state(),h)),B(h<0?"Selected model moved nearer.":"Selected model moved farther."))});o.querySelectorAll("[data-transform-tab]").forEach(a=>u(a,"click",()=>G(!1,a.dataset.transformTab))),u(n("model-tool"),"change",()=>G(!0,n("model-tool").value));for(let a of["x","y","z"]){let h=n("position-"+a);u(h,"input",()=>{v===null||!h.value.trim()||Ee(v,{...g.get(v).position,[a]:h.valueAsNumber},!1)&&(h.removeAttribute("aria-invalid"),n("position-error").hidden=!0)}),u(h,"change",()=>{v!==null&&(!h.value.trim()||!Ee(v,{...g.get(v).position,[a]:h.valueAsNumber})?(h.setAttribute("aria-invalid","true"),n("position-error").hidden=!1):B("Model position updated."))})}u(n("finish-move"),"click",()=>{G(!1),x?.canvas.focus({preventScroll:!0})}),u(n("reset-position"),"click",()=>{G(!1),v!==null&&(Ee(v,{...g.get(v).originalPosition||{x:0,y:0,z:0}}),B("Selected model restored to its original position."))}),u(i,"blur",()=>{J&&G(!1)});function De(){C++,A.length=0,D?.abort(),z!==null&&(x?.remove_model(z),z=null),Te(!1),B("Opening cancelled.")}function Ot(){P?.close(),G(!1),De(),I++,x?.clean(),g.clear(),v=null,j=!1,O=0,we=1,n("scene-arrangement").value="original",Oe(),n("notice").hidden=!0,be(),B("Scene cleared.")}function lt(a){if(!g.has(a))return;P?.close();let h=g.get(a).name;G(!1),x.remove_model(a),g.delete(a),I++,v===a&&(v=[...g.keys()].at(-1)??null),nt(),be(),B(h+" removed.")}async function Nt(){return E||(E=Promise.resolve().then(async()=>{if(m)throw new Error("ViewSTL app is disposed");if(x=new _e(n("viewer"),{...p.limits,worker_count:1,worker_timeout:e.worker_timeout??6e5,worker_url:e.worker_url,worker_factory:e.worker_factory,url_import_endpoint:e.url_import_endpoint,center_models:!1,bg_color:ue,allow_drag_and_drop:!1,camera_state:{position:{x:90,y:-130,z:90},target:{x:0,y:0,z:0},up:{x:0,y:0,z:1}},loading_progress_callback:a=>{if(m||!R||D)return;let h=a[z];n("loading-label").textContent=h?.phase==="loading-cad"?"Loading the CAD reader\u2026":h?.phase==="parsing-cad"?"Preparing CAD surfaces\u2026":h?.loaded&&!h.total?`Downloading your file\u2026 ${Me(h.loaded)}`:h?.total&&h.loaded<h.total?`Reading your file\u2026 ${Math.min(99,Math.floor(100*h.loaded/h.total))}%`:"Preparing your model\u2026"},model_loaded_callback:a=>{w=a},load_error_callback:a=>{ne(/limit exceeded|safety budget/i.test(a)?`This scene or view mode exceeds the estimated memory budget for this device (${Me(p.budget)}). Remove a model, use Solid view without smoothing, or try a device with more memory.`:`Couldn\u2019t display this model. ${a}`)}}),await x.when_ready,m)throw new Error("ViewSTL app is disposed");return W=to(x.canvas,{getCamera:()=>x.get_camera_state(),getModel:Pt,getTool:()=>q,onRotate:(a,h)=>Ne(a,h),onMove:(a,h)=>Ee(a,h),onStart:()=>{ee=n("scene-arrangement").value},onEnd:(a,h)=>{h&&(n("scene-arrangement").value=ee),B(h?"Move cancelled.":"Model updated.")}}),u(x.canvas,"dblclick",a=>{if(R||a.button!==0)return;let h=x.pick_model(a.clientX,a.clientY);!h||!g.has(h.id)||(a.preventDefault(),Fe(h.id),ce(),n("scene-panel").hidden=!1,n("scene-button").setAttribute("aria-expanded","true"),n("position-details").open=!0,xe(),B(g.get(h.id).name+" selected. Choose Move, Rotate or Depth."))}),x}).catch(a=>{throw W?.dispose(),W=null,x?.dispose(),x=null,E=null,a})),E}function je(a){f();let h=[];for(let S of a){if(!/\.(stl|obj|3mf|step|stp|iges|igs|vsb|vsj)$/i.test(S.name)){ne("Choose STL, OBJ, 3MF, STEP/STP, IGES/IGS, VSB or VSJ files.");continue}if(S.size===0){ne("This file is empty. Choose a file containing a 3D model.");continue}h.push(S)}let b=h.filter(S=>pe(S.name));if(b.length){let S={files:h.filter(T=>!pe(T.name)),used:new Set,failed:!1};for(let T of b)A.push({name:T.name,sceneFile:T,batch:S});A.push({remaining:S})}else A.push(...h);return A.length&&!M&&(_=Dt()),_||Promise.resolve()}function ct(a,h=""){if(f(),!e.url_import_endpoint)throw new Error("URL imports are not configured.");let b=new s(a.trim());if(!["https:","http:"].includes(b.protocol)||b.username||b.password)throw new Error("Use a public HTTP or HTTPS file URL without login credentials.");if(h&&!/^(stl|obj|3mf|step|stp|iges|igs|vsb|vsj)$/.test(h))throw new Error("Choose a supported 3D format.");b.hash="";let S=decodeURIComponent(b.pathname.split("/").at(-1)||"Remote model").slice(0,200);return A.push({name:S,remote:b.href,format:h,size:0,scene:["vsb","vsj"].includes(h)?h:pe(S)}),M||(_=Dt()),_||Promise.resolve()}n("url-form")&&n("url-dialog")&&u(n("url-form"),"submit",a=>{a.preventDefault();try{let h=ct(n("url-input").value,n("url-format").value);n("url-input").value="",n("url-error").hidden=!0,n("url-dialog").close(),h.catch(b=>ne(b.message))}catch(h){n("url-error").textContent=h.message,n("url-error").hidden=!1}});async function Dt(){if(!M){M=!0;try{for(;A.length;){let a=A.shift(),h=C;if(a.remaining){a.remaining.failed||A.unshift(...a.remaining.files.filter(b=>!a.remaining.used.has(b)));continue}Te(!0),n("notice").hidden=!0,n("loading-label").textContent="Opening "+a.name+"\u2026",B("Opening "+a.name);try{if(a.sceneFile||a.scene){await Io(a,h);continue}let b=await Gt(a);if(h!==C)continue;let S=[...g.values()].reduce((U,he)=>U+he.estimated,0);if(b+S>p.budget||a.size>p.limits.max_file_bytes)throw Error(`Adding this file would exceed the estimated ${Me(p.budget)} scene memory budget. Remove a model or try a device with more memory.`);let T=await Nt();if(h!==C)continue;T.do_resize(),F=Math.max(F,T.max_model_id+1);let $=F++;if(z=$,w=null,T.add_model({id:$,display:"flat",...a.remote?{remote_url:a.remote,remote_format:a.format}:a.url?{filename:a.url}:{local_file:a}}),await T.when_idle(),h!==C)continue;z=null;let se=w===$?T.get_model_info($):null;if(!se){B(a.name+" could not be opened.");continue}a.remote&&(a.name=se.name,a.size=se.file_bytes,delete a.remote);let de=se.has_colors,N=de?null:k[g.size%k.length];N&&T.set_color($,N),g.set($,{id:$,name:a.name,size:a.size,units:se.source_units||(/\.3mf$/i.test(a.name)?"mm":null),bounds:se.bounds,position:{x:0,y:0,z:0},rotation:{x:0,y:0,z:0},embedded:de,originalColor:N,color:N,estimated:se.triangles*224,sourceFile:a instanceof i.File?a:null}),v=$,I++,nt(),be(),B(a.name+" is ready."),T.canvas.focus({preventScroll:!0})}catch(b){a.batch&&(a.batch.failed=!0),h===C&&(z!==null&&(x?.remove_model(z),z=null),ne(/WebGL/i.test(b.message)?"3D viewing needs WebGL 2. Try enabling hardware acceleration in your browser, or use another device.":b.message))}}}finally{M=!1,Te(!1)}}}async function Io(a,h){let b=await Nt();if(h!==C)return;let S=new i.AbortController;D=S;try{let T=a.batch?.files||[],$=[...new Set([...g.values()].map(N=>N.sourceFile).filter(Boolean))],se=[...T,...a.sceneFile?$.filter(N=>!T.some(U=>U.name===N.name)&&$.filter(U=>U.name===N.name).length===1):[]],de=await b.import_scene(a.sceneFile||a.remote,{format:a.scene||pe(a.name),signal:S.signal,files:se,on_progress(N){h!==C||m||(n("loading-label").textContent=N.phase==="opening-model"?`Opening scene model ${N.loaded+1} of ${N.total}\u2026`:N.phase==="complete"?"Restoring scene\u2026":N.total?`Reading scene\u2026 ${Math.min(99,Math.floor(N.loaded/N.total*100))}%`:"Reading scene\u2026")}});if(h!==C)return;for(let N of de.used_files)a.batch?.used.add(N);for(let N of de.model_ids){let U=b._models.get(N),he=U.geometry,Po=Object.fromEntries(["min","max"].map(ve=>[ve,he.bounds[ve].map((Fo,Ut)=>(Fo-U.center[Ut])*U.scaleVector[Ut])])),ae={id:N,name:String(U.orig_filename||U.filename).split(/[\\/]/).pop().split(/[?#]/)[0],size:he.file_bytes??U.local_file?.size??null,units:he.units||U.units,bounds:Po,position:{x:0,y:0,z:0},rotation:{x:U.rotationx*180/Math.PI,y:U.rotationy*180/Math.PI,z:U.rotationz*180/Math.PI},embedded:!!he.colors,color:U.color||(he.colors?null:"#909090"),estimated:he.triangles*224,sourceFile:de.used_files.includes(U.local_file)?U.local_file:null},To=gt(ae);for(let ve of["x","y","z"])ae.position[ve]=U[ve]-To[ve];ae.originalPosition={...ae.position},ae.originalRotation={...ae.rotation},ae.originalColor=ae.color,g.set(N,ae)}F=Math.max(F,b.max_model_id+1),v=de.model_ids.at(-1),I++,n("scene-arrangement").value="custom",j=!!b.grid,O=b.grid?.height||0,we=b.grid?.spacing||1,n("grid-button").setAttribute("aria-pressed",String(j)),ht(b.bg_color),be(),B(a.name+" opened. "+de.model_ids.length+" models added."),b.canvas.focus({preventScroll:!0})}finally{D===S&&(D=null)}}o.querySelectorAll("[data-open-file]").forEach(a=>u(a,"click",()=>n("file-input").click())),u(n("file-input"),"change",()=>{let a=[...n("file-input").files];n("file-input").value="",je(a)}),n("sample-button").hidden=!it,u(n("sample-button"),"click",()=>{it&&je([{name:e.sample_name||"Sample.stl",url:it}])}),u(n("close-model"),"click",()=>lt(v)),u(n("cancel-load"),"click",De),u(n("clear-scene"),"click",Ot),u(n("dismiss-notice"),"click",()=>{n("notice").hidden=!0}),u(n("fit-button"),"click",()=>x?.set_auto_zoom()),u(n("scene-arrangement"),"change",nt),u(n("model-list"),"click",a=>{let h=a.target.closest("button");h&&(h.dataset.removeModel!==void 0?lt(Number(h.dataset.removeModel)):h.dataset.selectModel!==void 0?Fe(Number(h.dataset.selectModel)):h.dataset.colorModel!==void 0&&(Fe(Number(h.dataset.colorModel)),Pe("colors")))});async function jt(){if(v===null||R)return;let a=++I,h=ye.find(b=>b.checked).value;ye.forEach(b=>{b.disabled=!0}),n("smooth-shading").disabled=!0,n("view-status").textContent="Preparing view\u2026",n("notice").hidden=!0;try{x.set_view(v,{display:h,smooth_shading:n("smooth-shading").checked}),await x.when_idle()}finally{a===I&&(Lt(),B(n("view-label").textContent+" view ready."))}}ye.forEach(a=>u(a,"change",jt)),u(n("smooth-shading"),"change",jt);for(let a of y)u(n(a+"-button"),"click",()=>{a==="info"&&Ct(),Pe(a),a==="section"&&!n("section-panel").hidden&&!n("section-enabled").checked&&(n("section-enabled").checked=!0,ke())}),u(n("close-"+a),"click",()=>{Rt(a),a==="colors"&&Pe("view"),n(a+"-button").focus()});function ke(){if(v===null)return;let a=n("section-enabled").checked;x.set_section(v,a?{axis:n("section-axis").value,position:Number(n("section-position").value)/100,flip:n("section-flip").getAttribute("aria-pressed")==="true"}:null),n("section-value").textContent=n("section-position").value+"%",n("section-button").setAttribute("aria-pressed",String(a))}u(n("section-enabled"),"change",ke),u(n("section-axis"),"change",ke),u(n("section-position"),"input",ke),u(n("section-flip"),"click",()=>{n("section-flip").setAttribute("aria-pressed",String(n("section-flip").getAttribute("aria-pressed")!=="true")),ke()}),u(n("grid-button"),"click",()=>{j=!j,Oe()});function dt(a){v!==null&&(x.set_color(v,a),g.get(v).color=a,rt(),It())}u(n("model-color"),"input",()=>dt(n("model-color").value)),u(n("restore-color"),"click",()=>dt(g.get(v)?.originalColor)),o.querySelectorAll("[data-model-color]").forEach(a=>u(a,"click",()=>dt(a.dataset.modelColor)));function ht(a){ue=a;let h=a==="transparent"?[255,255,255]:[1,3,5].map(b=>parseInt(a.slice(b,b+2),16));n("workspace").classList.toggle("dark-background",h[0]*.2126+h[1]*.7152+h[2]*.0722<115),x?.set_bg_color(a),rt()}u(n("background-color"),"input",()=>ht(n("background-color").value)),o.querySelectorAll("[data-background]").forEach(a=>u(a,"click",()=>ht(a.dataset.background))),u(n("image-button"),"click",async()=>{if(v===null)return;let a=g.size>1?"viewstl-scene.png":g.get(v).name.replace(/\.[^.]+$/,"")+".png";try{let h=await x.get_image_blob("image/png");if(m)return;let b=s.createObjectURL(h),S=t.createElement("a");S.href=b,S.download=a,S.click(),setTimeout(()=>s.revokeObjectURL(b),1e3),B("Image ready to download.")}catch{ne("The image could not be saved. Please try again.")}});let Be=0,mt=a=>{let h=vt(a.dataTransfer);return h==="files"?h:!e.url_import_endpoint||a.target?.isContentEditable||a.target?.closest?.("input, textarea")?null:h},Ue=()=>{Be=0,n("drop-overlay").hidden=!0};u(c,"dragenter",a=>{mt(a)&&(a.preventDefault(),Be++,n("drop-overlay").hidden=!1)}),u(c,"dragover",a=>{mt(a)&&(a.preventDefault(),a.dataTransfer.dropEffect="copy")}),u(c,"dragleave",a=>{Be>0&&--Be===0&&Ue()}),u(c,"drop",a=>{let h=mt(a);if(Ue(),!!h){a.preventDefault();try{if(h==="files")je([...a.dataTransfer.files]).catch(b=>ne(b.message));else for(let b of oo(a.dataTransfer))ct(b).catch(S=>ne(S.message))}catch(b){ne(b.message)}}}),u(c,"dragend",Ue),u(i,"blur",Ue),o.querySelectorAll("[data-dialog]").forEach(a=>u(a,"click",()=>n(a.dataset.dialog).showModal())),o.querySelectorAll("dialog").forEach(a=>u(a,"click",h=>{if(h.target===a){let b=a.getBoundingClientRect();(h.clientX<b.left||h.clientX>b.right||h.clientY<b.top||h.clientY>b.bottom)&&(a===n("save-dialog")?P.close():a.close())}})),u(o,"keydown",a=>{if(a.key!=="Escape"||n("save-dialog")?.open)return;if(J){G(!1),B("Move tool closed.");return}let h=y.find(b=>!n(b+"-panel").hidden);h&&(ce(),h==="colors"&&Pe("view"),n(h+"-button").focus())}),u(o,"pointerdown",a=>{a.target.closest(".view-panel,.info-panel,.toolbar,.scene-heading-button,.move-mode")||ce()}),n("memory-budget").textContent=`Scene memory budget: ${Me(p.budget)}. Half of ${p.reported?"the memory estimate reported by your browser":"a conservative 2 GiB estimate because this browser does not report device memory"} is kept in reserve. This is an estimate, not a guarantee of available memory.`,n("scene-arrangement").value="original",n("notice").hidden=!0,n("drop-overlay").hidden=!0,n("save-button")&&(P=uo({$:n,listen:u,window:i,getState:()=>({busy:R,selectedId:v,models:[...g.values()]}),getViewer:()=>x,onOpen:()=>{G(!1),ce()},announce:B})),n("print-button")&&(L=fo({$:n,listen:u,window:i,getState:()=>({busy:R,selectedId:v,models:[...g.values()]}),getViewer:()=>x,onOpen:()=>{G(!1),ce()},announce:B})),Oe(),Te(!1),G(!1);let Bt={openFiles:je,openURL:ct,cancelLoading(){f(),De()},clear(){f(),Ot()},select(a){f(),Fe(a)},remove(a){f(),lt(a)},getState(){return f(),{selectedId:v,busy:R,arrangement:n("scene-arrangement").value,memoryBudget:p.budget,models:[...g.values()].map(a=>({id:a.id,name:a.name,size:a.size,units:a.units,position:{...a.position},rotation:{...a.rotation},color:a.color}))}},dispose(){m||(P?.dispose(),L?.dispose(),De(),G(!1),I++,m=!0,l.abort(),W?.dispose(),x?.dispose(),g.clear(),e.id_prefix&&ye.forEach(a=>a.name="view-mode"),zt.delete(o))}};return zt.set(o,Bt),Bt}var Jo=globalThis.document?.currentScript;_e.workerURL=new URL("viewstl.worker.js",Jo?.src||document.baseURI).href;globalThis.StlViewer=_e;globalThis.mountViewstlApp=Ao;})();
51
+ result=vec4(color,alpha);}`,Ze=class{constructor(e,t,i,s){if(this.canvas=e,this.domElement=e,this.resources=new Map,this.frames=0,this.lost=!1,this.gl=e.getContext("webgl2",{alpha:!0,antialias:!0,premultipliedAlpha:!1,preserveDrawingBuffer:!1}),!this.gl)throw new Error("WebGL2 is unavailable. Enable browser graphics acceleration or try another browser/device.");this.lostHandler=r=>{r.preventDefault(),this.lost=!0,this.resources.clear(),this.gridResource=null,this.infiniteGrid=null,this.selectionResource=null,this.diagnosticResource=null,t()},this.restoredHandler=()=>{try{this.initialize(),this.lost=!1,i()}catch(r){s(r)}},e.addEventListener("webglcontextlost",this.lostHandler),e.addEventListener("webglcontextrestored",this.restoredHandler),this.initialize()}initialize(){let e=this.gl,t=[],i;try{for(let[s,r]of[[e.VERTEX_SHADER,Ho],[e.FRAGMENT_SHADER,qo]]){let n=e.createShader(s);if(t.push(n),e.shaderSource(n,r),e.compileShader(n),!e.getShaderParameter(n,e.COMPILE_STATUS))throw new Error(`Shader compilation failed: ${e.getShaderInfoLog(n)}`)}i=e.createProgram();for(let s of t)e.attachShader(i,s);if(e.linkProgram(i),!e.getProgramParameter(i,e.LINK_STATUS))throw new Error(`Shader link failed: ${e.getProgramInfoLog(i)}`);this.program=i,this.uniforms=Object.fromEntries(["mvp","modelMatrix","normalMatrix","tint","light","eye","opacity","unlit","section","appearance","useColors"].map(s=>[s,e.getUniformLocation(i,s)]))}catch(s){throw i&&e.deleteProgram(i),s}finally{for(let s of t)e.deleteShader(s)}}upload(e,t,i){let s=this.gl,r=s.createVertexArray(),n=[];if(!r)throw new Error("Graphics memory is unavailable for this model.");s.bindVertexArray(r);try{for(let[d,l]of[[0,e],[1,t],[2,i]])if(l){let c=s.createBuffer();if(!c)throw new Error("Graphics memory is unavailable for this model.");n.push(c),s.bindBuffer(s.ARRAY_BUFFER,c),s.bufferData(s.ARRAY_BUFFER,l,s.STATIC_DRAW);let m=s.getError();if(m!==s.NO_ERROR)throw new Error(m===s.OUT_OF_MEMORY?"Not enough graphics memory for this model. Try closing other tabs or using another device.":"The browser could not upload this model to the graphics device.");s.enableVertexAttribArray(d),s.vertexAttribPointer(d,3,s.FLOAT,!1,0,0)}return s.bindVertexArray(null),{vao:r,buffers:n,count:e.length/3}}catch(d){s.bindVertexArray(null);for(let l of n)s.deleteBuffer(l);throw s.deleteVertexArray(r),d}}free(e){if(e){for(let t of e.buffers)this.gl.deleteBuffer(t);this.gl.deleteVertexArray(e.vao)}}remove(e){this.diagnostic?.model===e&&this.setDiagnostic(null);let t=this.resources.get(e);if(t)for(let i of Object.values(t))this.free(i);this.resources.delete(e)}clear(){this.setDiagnostic(null),this.setSelection(null);for(let e of this.resources.keys())this.remove(e);this.free(this.gridResource),this.gridResource=null,this.infiniteGrid?.dispose(),this.infiniteGrid=null}get liveBuffers(){let e=(this.gridResource?.buffers.length||0)+(this.selectionResource?.buffers.length||0)+(this.diagnosticResource?.buffers.length||0);for(let t of this.resources.values())for(let i of Object.values(t))e+=i.buffers.length;return e}setDiagnostic(e){this.free(this.diagnosticResource),this.diagnosticResource=null,this.diagnostic=e}drawDiagnostic(e){let t=this.gl,i=this.uniforms,s=this.diagnostic,r=X(s.model);this.diagnosticResource??=this.upload(s.positions),t.uniformMatrix4fv(i.mvp,!1,new Float32Array(ze(e,r))),t.uniformMatrix4fv(i.modelMatrix,!1,new Float32Array(r)),t.uniform4fv(i.section,[0,0,0,0]),t.uniform1i(i.appearance,0),t.uniform1i(i.useColors,0),t.uniform1i(i.unlit,1),t.uniform3fv(i.tint,[.85,.16,.06]),t.uniform1f(i.opacity,1),t.bindVertexArray(this.diagnosticResource.vao),t.disable(t.DEPTH_TEST),t.drawArrays(t.LINES,0,this.diagnosticResource.count),t.enable(t.DEPTH_TEST)}setSelection(e){this.selection!==e&&(this.free(this.selectionResource),this.selectionResource=null,this.selection=e)}drawSelection(e,t){let i=this.gl,s=this.uniforms,r=X(this.selection);this.selectionResource??=this.upload(po(this.selection.geometry.bounds)),i.uniformMatrix4fv(s.mvp,!1,new Float32Array(ze(e,r))),i.uniformMatrix4fv(s.modelMatrix,!1,new Float32Array(r)),i.uniformMatrix3fv(s.normalMatrix,!1,new Float32Array([1,0,0,0,1,0,0,0,1])),i.uniform4fv(s.section,[0,0,0,0]),i.uniform1i(s.appearance,0),i.uniform1i(s.useColors,0),i.uniform1i(s.unlit,1);let n=t==="transparent"?[1,1,1]:Y(t);i.uniform3fv(s.tint,n[0]+n[1]+n[2]<1.5?[.42,.81,.96]:[.08,.43,.65]),i.bindVertexArray(this.selectionResource.vao),i.disable(i.DEPTH_TEST),i.uniform1f(s.opacity,.18),i.drawArrays(i.LINES,0,this.selectionResource.count),i.enable(i.DEPTH_TEST),i.uniform1f(s.opacity,.9),i.drawArrays(i.LINES,0,this.selectionResource.count)}resource(e,t){let i=this.resources.get(e);if(i||(i={},this.resources.set(e,i)),!i[t]){let s=e.geometry;i[t]=t==="edges"||t==="wire"?this.upload(s[t]):this.upload(s.positions,t==="smooth"?s.smoothNormals:s.normals,s.colors)}return i[t]}setGrid(e){this.grid=e,this.free(this.gridResource),this.gridResource=null}resize(e,t,i=1){let s=this.gl,r=s.getParameter(s.MAX_RENDERBUFFER_SIZE);this.canvas.width=Math.min(r,Math.max(1,Math.round(e*i))),this.canvas.height=Math.min(r,Math.max(1,Math.round(t*i)))}draw(e,t,i,s=!0){if(this.lost)return;let r=this.gl,n=this.uniforms;r.viewport(0,0,this.canvas.width,this.canvas.height),r.clearColor(...i==="transparent"?[0,0,0,0]:[...Y(i),1]),r.depthMask(!0),r.clear(r.COLOR_BUFFER_BIT|r.DEPTH_BUFFER_BIT),r.enable(r.DEPTH_TEST),r.disable(r.CULL_FACE),r.useProgram(this.program),r.vertexAttrib3f(1,0,0,1),r.vertexAttrib3f(2,1,1,1),r.uniform3fv(n.light,H(V(t.position,t.target))),r.uniform3fv(n.eye,t.position);let d=Ye(t,this.canvas.width/this.canvas.height),l=e.filter(f=>f.geometry&&f.opacity>0&&f.color!=="transparent"),c=f=>{let u=le(X(f),f.geometry.bounds.min.map((y,g)=>(y+f.geometry.bounds.max[g])/2)),p=V(u,t.position);return te(p,p)},m=l.filter(f=>f.opacity>=1&&f.display!=="xray"),_=l.filter(f=>f.opacity<1||f.display==="xray").sort((f,u)=>c(u)-c(f)),w=f=>{let u=X(f),p=fe(u),y=f.geometry;r.uniformMatrix4fv(n.mvp,!1,new Float32Array(ze(d,u))),r.uniformMatrix4fv(n.modelMatrix,!1,new Float32Array(u)),r.uniform4fv(n.section,We(y.bounds,f.section)),r.uniform1i(n.appearance,{mesh:1,xray:2,studio:3,orientation:4}[f.display]||0),r.uniformMatrix3fv(n.normalMatrix,!1,new Float32Array([p[0],p[4],p[8],p[1],p[5],p[9],p[2],p[6],p[10]])),r.uniform3fv(n.tint,f.color&&f.color!=="transparent"?Y(f.color):y.colors?[1,1,1]:Y("#909090")),r.uniform1f(n.opacity,f.opacity),r.uniform1i(n.useColors,+(!f.color&&!!y.colors));let g=f.display==="wireframe"&&y.wire,A=g?"wire":(f.smooth_shading||f.display==="smooth")&&y.smoothNormals?"smooth":"flat";r.uniform1i(n.unlit,+!!g);let k=f.view_edges||f.display==="edges";k&&(r.enable(r.POLYGON_OFFSET_FILL),r.polygonOffset(1,1));let E=this.resource(f,A);if(r.bindVertexArray(E.vao),f.display==="xray"?(r.enable(r.CULL_FACE),r.cullFace(r.FRONT),r.drawArrays(r.TRIANGLES,0,E.count),r.cullFace(r.BACK),r.drawArrays(r.TRIANGLES,0,E.count),r.disable(r.CULL_FACE)):r.drawArrays(g?r.LINES:r.TRIANGLES,0,E.count),r.disable(r.POLYGON_OFFSET_FILL),k&&y.edges){r.uniform1i(n.useColors,0),r.uniform3fv(n.tint,[.08,.09,.11]),r.uniform1i(n.unlit,1);let x=this.resource(f,"edges");r.bindVertexArray(x.vao),r.drawArrays(r.LINES,0,x.count)}};r.disable(r.BLEND);for(let f of m)w(f);if(this.grid?.infinite)this.infiniteGrid??=new Ke(r),this.infiniteGrid.draw(d,t,this.grid,i),r.useProgram(this.program);else if(this.grid){if(!this.gridResource){let{size:f,divisions:u}=this.grid,p=[];for(let y=0;y<=u;y++){let g=-f/2+f*y/u;p.push(-f/2,g,0,f/2,g,0,g,-f/2,0,g,f/2,0)}this.gridResource=this.upload(new Float32Array(p))}r.uniformMatrix4fv(n.mvp,!1,new Float32Array(d)),r.uniform4fv(n.section,[0,0,0,0]),r.uniform1i(n.appearance,0),r.uniform1i(n.useColors,0),r.uniform3fv(n.tint,[.7,.73,.78]),r.uniform1f(n.opacity,1),r.uniform1i(n.unlit,1),r.bindVertexArray(this.gridResource.vao),r.drawArrays(r.LINES,0,this.gridResource.count)}r.enable(r.BLEND),r.blendFuncSeparate(r.SRC_ALPHA,r.ONE_MINUS_SRC_ALPHA,r.ONE,r.ONE_MINUS_SRC_ALPHA),r.depthMask(!1);for(let f of _)w(f);s&&this.selection&&l.includes(this.selection)&&this.drawSelection(d,i),s&&this.diagnostic&&e.includes(this.diagnostic.model)&&this.drawDiagnostic(d),r.depthMask(!0),r.disable(r.BLEND),r.bindVertexArray(null),this.frames++}dispose(){this.canvas.removeEventListener("webglcontextlost",this.lostHandler),this.canvas.removeEventListener("webglcontextrestored",this.restoredHandler),this.clear(),this.gl.deleteProgram(this.program),this.gl.getExtension("WEBGL_lose_context")?.loseContext()}};var Qe=class{constructor(e,t,i,s,r=0){this.enabled=!0,this.noZoom=!1,this.type=r,this.camera=t,this.invalidate=i,this.pointers=new Map,this.cleanups=[],this.canvas=e;let n=(l,c,m)=>{e.addEventListener(l,c,m),this.cleanups.push(()=>e.removeEventListener(l,c,m))};n("pointerdown",l=>{this.enabled&&(l.preventDefault(),e.focus({preventScroll:!0}),e.setPointerCapture(l.pointerId),this.pointers.set(l.pointerId,{x:l.clientX,y:l.clientY,button:l.button}),s(l,l.pointerType==="touch"?20:l.button===2?3:1))}),n("pointermove",l=>{let c=this.pointers.get(l.pointerId);if(!c||!this.enabled)return;let m={x:l.clientX,y:l.clientY,button:c.button};if(this.pointers.size===2){let _=[...this.pointers.entries()].find(([u])=>u!==l.pointerId)[1],w=Math.hypot(c.x-_.x,c.y-_.y),f=Math.hypot(m.x-_.x,m.y-_.y);w>0&&f>0&&this.zoom(w/f),this.pan((m.x-c.x)/2,(m.y-c.y)/2)}else c.button===2||l.shiftKey||c.button===1?this.pan(m.x-c.x,m.y-c.y):this.orbit((m.x-c.x)/Math.max(1,e.clientHeight)*Math.PI,(m.y-c.y)/Math.max(1,e.clientHeight)*Math.PI);this.pointers.set(l.pointerId,m)});let d=l=>{this.pointers.delete(l.pointerId),e.hasPointerCapture(l.pointerId)&&e.releasePointerCapture(l.pointerId)};n("pointerup",d),n("pointercancel",d),n("lostpointercapture",l=>this.pointers.delete(l.pointerId)),n("wheel",l=>{if(!this.enabled||this.noZoom)return;l.preventDefault();let c=l.deltaY*(l.deltaMode===1?16:l.deltaMode===2?e.clientHeight:1);this.zoom(Math.exp(Math.max(-1,Math.min(1,c*.001))))},{passive:!1}),n("dblclick",l=>{this.enabled&&s(l,11)}),n("contextmenu",l=>l.preventDefault())}orbit(e,t){let i=this.camera,s=V(i.position,i.target),r=H(i.up);s=Je(s,r,-e);let n=H(ie(r,s)),d=Je(s,n,-t);if(this.type===1)s=d,i.up=H(Je(r,n,-t));else{let l=H(d),c=l.reduce((m,_,w)=>m+_*r[w],0);Math.abs(c)<.9995&&(s=d)}i.position=re(i.target,s),this.invalidate()}zoom(e){if(this.noZoom)return;let t=this.camera,i=V(t.position,t.target),s=Math.hypot(...i),r=Math.max(1e-6,Math.min(1e15,s*e));t.position=re(t.target,oe(i,r/(s||1))),this.invalidate()}pan(e,t){let i=this.camera,s=V(i.position,i.target),r=H(ie(i.up,s)),n=H(ie(s,r)),d=2*Math.hypot(...s)*Math.tan(Math.PI/8)/Math.max(1,this.canvas.clientHeight),l=re(oe(r,-e*d),oe(n,t*d));i.target=re(i.target,l),i.position=re(i.position,l),this.invalidate()}dispose(){for(let e of this.cleanups)e();this.pointers.clear()}};var $e=class{constructor(e,{count:t=2,timeout:i=12e4,factory:s}={}){this.url=e,this.count=Math.max(1,Math.min(4,t)),this.timeout=i,this.factory=s,this.queue=[],this.active=new Set,this.next=0,this.disposed=!1}run(e,t,i=[]){if(this.disposed)return Promise.reject(new Error("Viewer disposed"));let s=++this.next,r,n=new Promise((d,l)=>{r={id:s,task:e,progress:t,transfer:i,resolve:d,reject:l},this.queue.push(r)});return n.cancel=()=>this.cancel(r),this.pump(),n}cancel(e){e.done||(e.done=!0,this.queue=this.queue.filter(t=>t!==e),clearTimeout(e.timer),e.worker?.terminate(),this.active.delete(e),e.reject(new DOMException("Loading cancelled","AbortError")),this.pump())}pump(){for(;!this.disposed&&this.active.size<this.count&&this.queue.length;){let e=this.queue.shift();this.active.add(e);let t=(i,s)=>{e.done||(e.done=!0,clearTimeout(e.timer),e.worker?.terminate(),this.active.delete(e),i?e.reject(i):e.resolve(s),this.pump())};try{e.worker=this.factory?this.factory():new Worker(this.url,{type:"module",name:"ViewSTL parser"}),e.worker.onmessage=({data:i})=>{if(i.id===e.id){if(i.progress){e.progress?.(i.progress);return}t(i.error?new Error(i.error):null,i.result)}},e.worker.onerror=i=>{i.preventDefault(),t(new Error(i.message||"Worker failed; check worker URL and Content Security Policy"))},e.worker.onmessageerror=()=>t(new Error("Invalid worker response")),e.timer=setTimeout(()=>t(new Error("File processing timed out")),this.timeout),e.worker.postMessage({id:e.id,task:e.task},e.transfer)}catch(i){t(i)}}}clear(){for(let e of[...this.queue,...this.active])this.cancel(e)}dispose(){this.disposed=!0,this.clear()}};function go(o,e={}){let t=new AbortController,i=t.signal,s=this._generation,r=[],n=new Map,d=new Set,l=this._models.size>0,c,m=0,_,w=!1,f,u=()=>{t.abort(),c?.cancel?.();for(let[A,k]of n)this._models.get(A)===k&&this.remove_model(A)},p=()=>{if(i.aborted||s!==this._generation||this.killsign)throw new DOMException("Scene opening cancelled","AbortError")},y=A=>e.on_progress?.(A);e.signal?.addEventListener("abort",u,{once:!0}),e.signal?.aborted&&u(),i.addEventListener("abort",()=>c?.cancel?.(),{once:!0}),this._aborters.add(t);let g=(async()=>{await this.when_ready,p(),_=this.get_camera_state();let A=e.format||pe(typeof o=="string"?o:o.name);if(!["vsb","vsj"].includes(A))throw new Error("Choose VSB or VSJ for a scene");let k,E;if(typeof o=="string"){if(k=Mt(o,document.baseURI),!this.options.url_import_endpoint)throw new Error("Configure url_import_endpoint for scene URL imports");if(E=new URL(this.options.url_import_endpoint,document.baseURI).href,new URL(E).origin!==location.origin)throw new Error("The URL relay must use the same origin as this page")}let x=this.limits.max_memory_bytes-this._occupiedMemory();K(this.limits,this._occupiedMemory()+67108864,"scene opening");let C={...this.limits,max_memory_bytes:x,max_file_bytes:Math.min(this.limits.max_file_bytes,Math.floor((x-67108864)/4))};m=Math.max(0,x-67108864),this._sceneBytes=(this._sceneBytes||0)+m,c=this._pool.run({kind:"read-scene",source:o,remote:k,relay:E,format:A,name:`scene.${A}`,limits:C},M=>{try{y({...M,phase:"reading-scene"})}catch(R){f=R,c.cancel()}});let I;try{I=await c}catch(M){throw f||M}c=null,p();let{scene:v,files:F,base:z}=I;this._sceneBytes-=m,m=Object.values(F||{}).reduce((M,R)=>M+R.byteLength,0)+JSON.stringify(v).length*2,this._sceneBytes+=m;let P=e.files||[],L=[],D=v.models.map(M=>{let R={...M};if(F)R.local_file=new File([F[M.filename]],M.filename);else{let j=mo(M.filename,P);if(j)R.local_file=j,d.add(j);else{let O=Mt(M.filename,k?z:void 0);O?R.remote_url=O:L.push(M.filename)}}if(R.remote_url&&!this.options.url_import_endpoint)throw new Error("Scene URL references require a configured URL relay");return R});if(L.length)throw new Error("Choose the VSJ together with these model files: "+L.slice(0,8).join(", ")+(L.length>8?"\u2026":""));if(D.length+this._models.size>this.limits.max_archive_entries)throw new Error("Scene model count limit exceeded");for(let M=0;M<D.length;M++){p(),y({phase:"opening-model",loaded:M,total:D.length}),p();let R=D[M],j=this.max_model_id+1;this.add_model({...R,id:j});let O=this._models.get(j);if(!O)throw new Error(this.error||"Invalid scene model");if(r.push(j),n.set(j,O),O._sceneImportPending=!0,await this._pending.get(j),p(),this._models.get(j)!==O||!O.geometry)throw new Error(this.error||"A scene model could not be opened");let we=v.center_models??this.center_models?O.geometry.bounds.min.map((W,ee)=>(W+O.geometry.bounds.max[ee])/2):[0,0,0],J=O.center.map((W,ee)=>W-we[ee]),q=le(X({...O,x:0,y:0,z:0,center:[0,0,0],scaleVector:O.scaleVector}),J);if(["x","y","z"].forEach((W,ee)=>O[W]+=q[ee]),[...this._models.values()].reduce((W,ee)=>W+(ee.geometry?.triangles||0),0)>this.limits.max_triangles)throw new Error("Scene triangle limit exceeded")}y({phase:"complete",loaded:D.length,total:D.length}),p();for(let M of n.values())M._sceneImportPending=!1;return v.bg_color!==void 0&&this.set_bg_color(v.bg_color),v.infinite_grid?this.set_infinite_grid(!0,v.infinite_grid.spacing,v.infinite_grid.height):this.set_grid(!!v.grid),v.auto_rotate!==void 0&&this.set_auto_rotate(v.auto_rotate),this._restoreCamera=v.camera_state||(l?_:null),w=!0,{model_ids:r,scene:v,used_files:[...d]}})().catch(A=>{throw u(),A}).finally(()=>{e.signal?.removeEventListener("abort",u),this._aborters.delete(t),this._sceneBytes=(this._sceneBytes||0)-m,this._sceneLoads.delete(g),s===this._generation&&!this.killsign&&(!w&&_&&(this._restoreCamera=_),this._finishSession())});return this._sessionActive=!0,this._sceneLoads.add(g),g}function _o(o={}){let e=o.level??"basic";if(!["basic","detailed"].includes(e))throw new Error("Choose basic or detailed print checks");let t=(r,n,d=1e9)=>{if(!Number.isFinite(r)||r<=0||r>d)throw new Error(`Invalid ${n}`);return r},i=null;if(o.profile!=null){if(o.profile.technology!=="fdm")throw new Error("Only FDM profiles are supported");i={technology:"fdm"};for(let r of["min_wall_mm","line_width_mm"])o.profile[r]!=null&&(i[r]=t(o.profile[r],r));if(o.profile.overhang_degrees!=null&&(i.overhang_degrees=t(o.profile.overhang_degrees,"overhang angle",89.9)),o.profile.build_volume_mm!=null){if(!Array.isArray(o.profile.build_volume_mm)||o.profile.build_volume_mm.length!==3)throw new Error("Build volume requires X, Y and Z");i.build_volume_mm=o.profile.build_volume_mm.map(r=>t(r,"build volume"))}}let s=t(o.max_time_ms??3e4,"check time limit",12e4);return{level:e,profile:i,apply_transforms:o.apply_transforms!==!1,units_confirmed:o.units_confirmed===!0,max_time_ms:s}}var yo=o=>64*1024**2+o*2400,ge=o=>JSON.stringify([o.x,o.y,o.z,o.rotationx,o.rotationy,o.rotationz,o.scaleVector,o.center,o.units,o.unitScale]);var Re=new WeakMap;function et(o,e){let t=Re.get(e);return!!t&&t.viewer.deref()===o&&t.models.every(({model:i,geometry:s,signature:r})=>{let n=i.deref();return n&&o._models.get(n.id)===n&&n.geometry===s.deref()&&ge(n)===r})}async function bo(o={}){await this.when_ready;let e=_o(o);if(this.killsign)throw new Error("Viewer disposed");let t=o.model_ids;if(t==="selected"&&(t=this.renderer?.selection?[this.renderer.selection.id]:[]),t??=[...this._models.keys()],!Array.isArray(t)||!t.length||new Set(t).size!==t.length||t.some(u=>!Number.isSafeInteger(u)))throw new Error("Choose one or more unique model IDs");if(t.length>this.limits.max_archive_entries)throw new Error("Print check model limit exceeded");let i=t.map(u=>{let p=this._model(u);if(!p.geometry||p._sceneImportPending)throw new Error("Wait for models to finish opening");return{model:p,geometry:p.geometry,signature:ge(p)}}),s=JSON.stringify([t,e]);if(o.signal?.aborted)throw new DOMException("Print check cancelled","AbortError");if(this._printCache?.key===s&&et(this,this._printCache.report)){let u=structuredClone(this._printCache.report);return Re.set(u,Re.get(this._printCache.report)),u}if(this._printJobs?.size)throw new Error("A print check is already running");this._printJobs??=new Map;let r=new AbortController,n=()=>r.abort();o.signal?.addEventListener("abort",n,{once:!0}),this._aborters.add(r),this._printJobs.set(r,new Set(i.map(u=>u.model)));let d,l=0,c,m=()=>d?.cancel?.();r.signal.addEventListener("abort",m);let _=()=>{if(r.signal.aborted||this.killsign)throw new DOMException("Print check cancelled","AbortError");if(i.some(({model:u,geometry:p,signature:y})=>this._models.get(u.id)!==u||u.geometry!==p||ge(u)!==y))throw new Error("Model changed. Run the print check again.")},w=u=>{_(),o.on_progress?.(u)},f=[];try{this._printCache=null;let u=i.length*256*1024;K(this.limits,this._occupiedMemory()+u,"print report"),l=u,this._printBytes=(this._printBytes||0)+l;for(let A=0;A<i.length;A++){_();let{model:k,geometry:E}=i[A],x=yo(E.triangles),C=this._occupiedMemory();K(this.limits,C+x,"print check"),this._printBytes+=x,l+=x;let I=new Float32Array(E.positions.length);for(let F=0;F<I.length;F+=262144)_(),I.set(E.positions.subarray(F,F+262144),F),F+262144<I.length&&await new Promise(z=>setTimeout(z,0));let v=e.apply_transforms?k:{...k,x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,center:[0,0,0],scaleVector:[k.unitScale,k.unitScale,k.unitScale]};w({phase:"Starting",model_id:k.id,model_index:A,total_models:i.length}),d=this._pool.run({kind:"print-check",model:{id:k.id,name:k.orig_filename||k.filename||`Model ${k.id}`,positions:I,matrix:X(v),source_units:E.units||null},options:e,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-C}},F=>{try{w({...F,model_id:k.id,model_index:A,total_models:i.length})}catch(z){c=z,r.abort()}},[I.buffer]);try{f.push(await d)}catch(F){throw c||F}finally{d=null,this._printBytes-=x,l-=x}}_();let p={version:1,model_ids:t.slice(),settings:e,models:f,summary:f.some(A=>A.summary==="issues_found")?"issues_found":f.some(A=>A.summary==="incomplete")?"incomplete":"no_issues_found",scope:"Each model checked separately; no cross-model union or collision analysis.",estimated:!!e.profile},y={viewer:new WeakRef(this),models:i.map(({model:A,geometry:k,signature:E})=>({model:new WeakRef(A),geometry:new WeakRef(k),signature:E}))};Re.set(p,y);let g=structuredClone(p);return Re.set(g,y),JSON.stringify(g).length<2*1024*1024&&this._occupiedMemory()+4*1024**2<=this.limits.max_memory_bytes&&(this._printCache={key:s,report:g}),w({phase:"Complete",total_models:i.length}),p}finally{d?.cancel?.(),this._printBytes=(this._printBytes||0)-l,o.signal?.removeEventListener("abort",n),this._aborters.delete(r),this._printJobs.delete(r)}}function vo(o,{issue:e,model_id:t=o.model_ids[0]}={}){if(!et(this,o))throw new Error("Print report is stale. Run the check again.");let i=o.models.find(l=>l.model_id===t),s=i?.checks.find(l=>l.code===e);if(!s)throw new Error("Unknown print check issue");let r=this._model(t),n=r.geometry.positions,d=[];if(s.edge_vertices?.length)d.push(...s.edge_vertices.slice(0,1536));else for(let l of s.triangle_ids.slice(0,128))if(!(!Number.isInteger(l)||l<0||l>=r.geometry.triangles))for(let c=0;c<3;c++)d.push(...n.subarray(l*9+c*3,l*9+c*3+3),...n.subarray(l*9+(c+1)%3*3,l*9+(c+1)%3*3+3));if(d.some(l=>!Number.isFinite(l)))throw new Error("Invalid print highlight");return this.renderer.setDiagnostic(d.length?{model:r,positions:new Float32Array(d),signature:ge(r)}:null),this._invalidate(),d.length/6}function St(o,e){return`${String(o||"scene").split(/[\\/]/).pop().split(/[?#]/)[0].replace(/[<>:"|*\x00-\x1f\x7f]/g,"_").replace(/\.(?:stl|obj|3mf|step|stp|iges|igs|zip|vsb|vsj)$/i,"").replace(/[. ]+$/g,"").slice(0,160)||"scene"}.${e}`}function wo(o={}){let e={stp:"step",igs:"iges"},t=String(o.format||"").toLowerCase(),i=e[t]||t;if(!["stl","obj","3mf","step","iges"].includes(i))throw new Error("Export format must be stl, obj, 3mf, step or iges");let s=o.units??"mm";if(!["mm","inch"].includes(s))throw new Error("Export units must be mm or inch");for(let d of["apply_transforms","colors"])if(o[d]!==void 0&&typeof o[d]!="boolean")throw new Error(`${d} must be a boolean`);if(o.on_progress!==void 0&&typeof o.on_progress!="function")throw new Error("on_progress must be a function");let r=o.colors!==!1&&["obj","3mf"].includes(i),n=i==="obj"&&r?"zip":i;return{format:i,units:s,colors:r,apply_transforms:o.apply_transforms!==!1,filename:St(o.filename,n)}}function xo(o,e){let t=o.reduce((s,r)=>s+r.geometry.triangles,0);return 67108864+t*{stl:192,obj:2400,"3mf":3e3,step:12e3,iges:4e3}[e.format]+o.length*4096}async function Mo(o,e,t=()=>{},i){let s,r=0;if(o instanceof ArrayBuffer){if(o.byteLength>e)throw new Error("File size limit exceeded");return t(o.byteLength,o.byteLength),o}if(o instanceof Blob){if(r=o.size,r>e)throw new Error("File size limit exceeded");if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");t(0,r);let _=await o.arrayBuffer();if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");return t(r,r),_}else{let _=new URL(String(o),globalThis.location?.href);if(!["http:","https:","blob:"].includes(_.protocol))throw new Error("Unsupported file URL protocol");let w=await fetch(_,{signal:i,credentials:"same-origin"});if(!w.ok)throw new Error(`HTTP ${w.status}: ${_.pathname}`);r=Number(w.headers.get("content-length"))||0,s=w.body}if(r>e)throw new Error("File size limit exceeded");if(!s)throw new Error("Empty response body");let n=s.getReader(),d=[],l=0;try{for(;;){if(i?.aborted)throw new DOMException("Loading cancelled","AbortError");let{done:_,value:w}=await n.read();if(_)break;if(l+=w.length,l>e)throw new Error("File size limit exceeded");d.push(w),t(l,Math.max(r,l))}}catch(_){throw await n.cancel().catch(()=>{}),_}finally{n.releaseLock()}let c=new Uint8Array(l),m=0;for(let _ of d)c.set(_,m),m+=_.length;return t(l,l),c.buffer}function Eo(o,e){if(!o?.isMesh&&!o?.geometry)throw new Error("Expected a Three.js mesh");if(o.isSkinnedMesh||o.isInstancedMesh||o.children?.length||o.morphTargetInfluences?.some(Boolean))throw new Error("Only a static standalone Three.js mesh is supported");let t=o.material;if(Array.isArray(t)||t?.isShaderMaterial||t?.map)throw new Error("Multiple, textured or shader materials need an application adapter");let i=o.geometry,s,r=null;if(i?.attributes?.position){let n=i.attributes.position,d=i.index,l=d?.count??n.count;if(l%3||l/3>e.max_triangles)throw new Error("Invalid or oversized mesh");s=new Float32Array(l*3),i.attributes.color&&(r=new Float32Array(l*3));for(let c=0;c<l;c++){let m=d?d.getX(c):c;for(let[_,w]of["getX","getY","getZ"].entries())s[c*3+_]=n[w](m),r&&(r[c*3+_]=i.attributes.color[w](m))}}else if(i?.vertices&&i?.faces){if(i.faces.length>e.max_triangles)throw new Error("Triangle limit exceeded");s=new Float32Array(i.faces.length*9),t?.vertexColors&&(r=new Float32Array(s.length)),i.faces.forEach((n,d)=>{for(let[l,c]of[n.a,n.b,n.c].entries()){let m=i.vertices[c];if(!m)throw new Error("Invalid mesh index");s.set([m.x,m.y,m.z],d*9+l*3),r&&r.set([n.color.r,n.color.g,n.color.b],d*9+l*3)}})}else throw new Error("Unsupported Three.js geometry");return Xe(s,r,e)}function ko(o,e){if(!e?.Mesh||!e?.BufferGeometry||!e?.BufferAttribute)throw new Error("get_model_mesh requires options.three or globalThis.THREE; use get_model_data for native geometry");let t=o.geometry,i=new e.BufferGeometry,s=t.positions.slice(),r=o.scaleVector,n=o.center;for(let m=0;m<s.length;m++)s[m]=(s[m]-n[m%3])*r[m%3];let d=(m,_)=>{let w=new e.BufferAttribute(_,3);i.setAttribute?i.setAttribute(m,w):i.addAttribute(m,w)};d("position",s),t.colors&&!o.color&&d("color",t.colors.slice()),i.computeVertexNormals();let l=new e.MeshLambertMaterial({color:o.color==="transparent"?"#909090":o.color||(t.colors?"#ffffff":"#909090"),vertexColors:t.colors&&!o.color?e.VertexColors??!0:!1,side:e.DoubleSide,transparent:o.opacity<1||o.color==="transparent",opacity:o.color==="transparent"?0:o.opacity,wireframe:o.display==="wireframe",flatShading:o.display!=="smooth"}),c=new e.Mesh(i,l);return c.position.set(o.x,o.y,o.z),c.rotation.set(o.rotationx,o.rotationy,o.rotationz),c}var Le=["x","y","z","rotationx","rotationy","rotationz","scalex","scaley","scalez"],Wo=["id","filename","remote_url","remote_format","orig_filename","local_file","mesh","data","x","y","z","display","color","scale","scalex","scaley","scalez","rotationx","rotationy","rotationz","opacity","view_edges","smooth_shading","section","units","animation"],Q=(o,e)=>{if(typeof o!="number"||!Number.isFinite(o))throw new Error(`${e} must be a finite number`);return o},me=(o,e)=>{if(Q(o,e),o<=0)throw new Error(`${e} must be positive`);return o},At=(o,e)=>[Q(o.x,`${e}.x`),Q(o.y,`${e}.y`),Q(o.z,`${e}.z`)],tt=o=>({x:o[0],y:o[1],z:o[2]}),ot=o=>String(o||"model.stl").split(/[\\/]/).pop().split(/[?#]/)[0];function Ce(o,e){let t=document.createElement("a"),i=URL.createObjectURL(o);t.href=i,t.download=ot(e),t.click(),setTimeout(()=>URL.revokeObjectURL(i),1e3)}function So(o){let e={};for(let t of Wo)Object.hasOwn(o,t)&&(e[t]=o[t]);return e}var _e=class o{static version="2.0.0-rc.4";static workerURL=null;constructor(e,t={}){if(!e?.appendChild)throw new TypeError("StlViewer requires a DOM container");this.parent_element=e,this.options={...t},this.error=null,this.ready=!1,this.killsign=!1,this.models=[],this.models_ref=Object.create(null),this.models_count=0,this.max_model_id=-1,this.load_status=Object.create(null),this.load_session=0,this._models=new Map,this._pending=new Map,this._jobs=new Map,this._derived=new Set,this._memoryReservations=new Map,this._exportBytes=0,this._loadTail=Promise.resolve(),this._sceneLoads=new Set,this._aborters=new Set,this._generation=0,this._sessionActive=!1,this._frame=0,this._lastTime=0,this.grid=null,this._gridAuto=!1,this.camera={position:[0,0,100],target:[0,0,0],up:[0,1,0],near:.01,far:1e4},this.when_ready=new Promise((i,s)=>{this._readyResolve=i,this._readyReject=s}),this.when_ready.catch(()=>{});for(let i of["ready_callback","all_loaded_callback","model_loaded_callback","load_error_callback","loading_progress_callback","on_model_drop"])this[i]=t[i]??null;this.onmousedown_callback=t.on_model_mousedown??t.on_model_mouseclick??null,queueMicrotask(()=>this._initialize())}_initialize(){if(!this.killsign)try{let e=this.options;if(this.limits=ao(e),this.bg_color=e.bg_color??e.bgcolor??"transparent",this.bg_color!=="transparent"&&Y(this.bg_color),e.worker_count!==void 0&&(!Number.isInteger(e.worker_count)||e.worker_count<1||e.worker_count>4))throw new Error("worker_count must be 1\u20134");if(e.worker_timeout!==void 0&&me(e.worker_timeout,"worker_timeout"),e.pixel_ratio!==void 0&&me(e.pixel_ratio,"pixel_ratio"),e.controls!==void 0&&e.controls!==0&&e.controls!==1)throw new Error("controls must be 0 or 1");this.center_models=e.center_models??!0,this.auto_resize=e.auto_resize??!0,this.auto_rotate=e.auto_rotate??!1,this.mouse_zoom=e.mouse_zoom??!0,this.send_no_model_click_event=e.send_no_model_click_event??!1,this.zoom=e.zoom??-1,this.canvas_width=e.width??e.canvas_width??"100%",this.canvas_height=e.height??e.canvas_height??"100%",this.canvas=document.createElement("canvas"),this.canvas.style.cssText="display:block;touch-action:none;outline-offset:3px;",this._setSizeStyle(),this.canvas.tabIndex=0,this.canvas.setAttribute("aria-label","3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."),this.parent_element.appendChild(this.canvas),this.renderer=new Ze(this.canvas,()=>this._message("Graphics interrupted. Waiting for the browser to restore rendering\u2026"),()=>{this._removeMessage(),this._invalidate()},i=>this._error(i)),this.controls=new Qe(this.canvas,this.camera,()=>this._invalidate(),(i,s)=>this._pick(i,s),e.controls??0),this.controls.noZoom=!this.mouse_zoom;let t=e.worker_url??o.workerURL;if(e.worker_url===void 0&&typeof e.load_three_files=="string"&&(t=new URL("viewstl.worker.js",new URL(e.load_three_files||"./",document.baseURI)).href),!t&&!e.worker_factory)throw new Error("Specify worker_url when using the CommonJS build");this._pool=new $e(t,{count:e.worker_count??2,timeout:e.worker_timeout??12e4,factory:e.worker_factory}),this._drop=i=>{i.preventDefault();let s=[...i.dataTransfer.files];for(let r of s)this._callback("on_model_drop",r.name),this.add_model({local_file:r})},this._drag=i=>i.preventDefault(),this.set_drag_and_drop(e.allow_drag_and_drop??!0),this.set_auto_resize(this.auto_resize),this.do_resize(),this.camera.position=[e.camerax??0,e.cameray??0,this.zoom>=0?Math.max(this.zoom,1e-6):e.cameraz??100],e.camera_state&&(this.set_camera_state(e.camera_state),this._restoreCamera=e.camera_state),e.grid&&this.set_grid(!0),this._key=i=>{if(!this.controls.enabled)return;let s={ArrowLeft:()=>this.controls.orbit(-.12,0),ArrowRight:()=>this.controls.orbit(.12,0),ArrowUp:()=>this.controls.orbit(0,-.12),ArrowDown:()=>this.controls.orbit(0,.12),"+":()=>this.controls.zoom(.9),"-":()=>this.controls.zoom(1.1),f:()=>this.set_auto_zoom()};s[i.key]&&(i.preventDefault(),s[i.key]())},this.canvas.addEventListener("keydown",this._key),this.ready=!0,this._readyResolve(this),this._callback("ready_callback"),e.models&&this.add_models(e.models),this._invalidate()}catch(e){this.ready=!1,this._readyReject(e),this._error(e),this._message(e.message),this._pool?.dispose(),this.controls?.dispose(),this.renderer?.dispose(),this._resizeObserver?.disconnect()}}_callback(e,...t){let i=this[e];if(typeof i=="function")try{i(...t)}catch(s){console.error(`ViewSTL ${e} callback failed`,s)}}_error(e){return this.error=e?.message??String(e),this._callback("load_error_callback",this.error),null}_message(e){this._removeMessage(),this._messageElement=document.createElement("p"),this._messageElement.setAttribute("role","status"),this._messageElement.textContent=e,this.parent_element.appendChild(this._messageElement)}_removeMessage(){this._messageElement?.remove(),this._messageElement=null}_setSizeStyle(){this.canvas.style.width=typeof this.canvas_width=="number"?`${this.canvas_width}px`:this.canvas_width,this.canvas.style.height=typeof this.canvas_height=="number"?`${this.canvas_height}px`:this.canvas_height}_guard(e){if(this.killsign)return null;try{return e()}catch(t){return this._error(t)}}_model(e){let t=this._models.get(Number(e));if(!t)throw new Error(`Model id not found: ${e}`);return t}_invalidate(){let e=this.renderer?.diagnostic;e&&ge(e.model)!==e.signature&&this.renderer.setDiagnostic(null),!(this.killsign||!this.renderer||this.renderer.lost||this._frame)&&(this._frame=requestAnimationFrame(t=>{this._frame=0,this._render(t)}))}_render(e){let t=this._lastTime?Math.min((e-this._lastTime)/1e3,.1):0;this._lastTime=e;let i=!1;for(let s of this._models.values())s.geometry&&s._animation&&(this._animate(s,e),i||=!!s._animation);this.auto_rotate&&this.controls.enabled&&this.controls.orbit(t*.3,0),this._clip();try{this.renderer.draw([...this._models.values()].filter(s=>!s._sceneImportPending),this.camera,this.bg_color)}catch(s){this.renderer.clear(),this._error(s);return}i||this.auto_rotate&&this.controls.enabled?this._invalidate():this._lastTime=0}_bounds(){let e=[1/0,1/0,1/0],t=[-1/0,-1/0,-1/0],i=0;for(let s of this._models.values())if(s.geometry&&!s._sceneImportPending){i++;let{bounds:r}=s.geometry,n=X(s);for(let d=0;d<8;d++){let l=le(n,[0,1,2].map(c=>r[d&1<<c?"max":"min"][c]));for(let c=0;c<3;c++)e[c]=Math.min(e[c],l[c]),t[c]=Math.max(t[c],l[c])}}return i?{min:e,max:t,center:e.map((s,r)=>(s+t[r])/2),radius:Math.max(Math.hypot(...V(t,e))/2,1e-6)}:null}_clip(){let e=this._bounds(),t=Math.hypot(...V(this.camera.position,e?.center??this.camera.target)),i=e?.radius??10;this.camera.near=Math.max(1e-6,Math.min(i*.001,Math.max(t-i,1e-6)*.1)),this.camera.far=Math.max(this.camera.near*100,t+i*4,100)}_progress(e,t){!this._models.has(e)||this.killsign||(this.load_status[e]=t,this._callback("loading_progress_callback",this.load_status,this.load_session))}add_models(e){return this._guard(()=>{if(!Array.isArray(e))throw new Error("models must be an array");for(let t of e)this.add_model(t)})}add_model(e){return this._guard(()=>{if(!e||typeof e!="object")throw new Error("Invalid model");let t=So(e),i=t.filename??t.local_file?.name??"model.stl";if(/\.vsj(?:[?#]|$)/i.test(i))return this.load_vsj(t.local_file??i);if(/\.vsb(?:[?#]|$)/i.test(i))return this.load_vsb(t.local_file??i);let s=t.id===void 0||t.id===-1?this.max_model_id+1:t.id;if(!Number.isSafeInteger(s)||s<0||this._models.has(s))throw new Error(`Invalid or duplicate model id: ${s}`);let r={...t,id:s,filename:t.filename??t.local_file?.name??"",x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,scalex:1,scaley:1,scalez:1,opacity:1,units:"mm",display:"flat",view_edges:!1,color:null,unitScale:1,center:[0,0,0],geometry:null};for(let l of Le)r[l]=t[l]??(l.startsWith("scale")?t.scale??1:0);for(let l of Le)Q(r[l],l),l.startsWith("scale")&&me(r[l],l);if(r.opacity=t.opacity??t.mesh?.material?.opacity??1,Q(r.opacity,"opacity"),r.opacity<0||r.opacity>1)throw new Error("Opacity must be between 0 and 1");if(r.display=t.display??"flat",Object.assign(r,He(r.display,t.smooth_shading??r.display==="smooth")),r.section=qe(t.section),r.units=t.units??"mm",!["mm","inch"].includes(r.units))throw new Error("Invalid units");r.color=t.color??(t.mesh?.material?.color?.getHexString?"#"+t.mesh.material.color.getHexString():null),r.color&&r.color!=="transparent"&&Y(r.color),r.view_edges=!!t.view_edges,Object.defineProperty(r,"scaleVector",{get:()=>[r.scalex*r.unitScale,r.scaley*r.unitScale,r.scalez*r.unitScale]}),this.max_model_id=Math.max(s,this.max_model_id),this._models.set(s,r),this.models_ref[s]=this.models.length,this.models.push(r),this.models_count=this._models.size,this._sessionActive=!0,this.load_status[s]={loaded:0,total:0};let n=this._generation,d=this._loadModel(r,t,n).then(()=>{n!==this._generation||this._models.get(s)!==r||(this._pending.delete(s),this._finishSession(),this._callback("model_loaded_callback",s))}).catch(l=>{n===this._generation&&this._models.get(s)===r&&(this._pending.delete(s),this._deleteModel(r),l.name!=="AbortError"&&this._error(l),this._finishSession())}).finally(()=>{this._jobs.delete(r)});this._pending.set(s,d)})}_occupiedMemory(e){let t=(this._exportBytes||0)+(this._sceneBytes||0)+(this._printBytes||0)+(this._printCache?4194304:0);for(let i of this._models.values())if(i!==e){let s=this._memoryReservations?.get(i);t+=Math.max(ro(i.geometry),[...s?.values()||[]].reduce((r,n)=>r+n.bytes,0))}return t}_reserveMemory(e,t,i="derive"){this._memoryReservations??=new Map;let s=this._memoryReservations.get(e);s||this._memoryReservations.set(e,s=new Map);let r={};return s.set(r,{bytes:t,kind:i}),()=>{s.delete(r),!s.size&&this._memoryReservations.get(e)===s&&this._memoryReservations.delete(e)}}async _loadModel(e,t,i){let s=()=>{};if(this.options.max_memory_bytes!==void 0){let r=this._loadTail;this._loadTail=new Promise(n=>{s=n}),await r}try{await this._readModel(e,t,i)}finally{s()}}async _readModel(e,t,i){if(await this.when_ready,this.killsign||i!==this._generation||this._models.get(e.id)!==e)return;let s=Math.max(1,this.limits.max_memory_bytes-this._occupiedMemory(e)),r={...this.limits,max_memory_bytes:s};K(r,67108864,"scene");let n=this.limits.max_memory_bytes<Number.MAX_SAFE_INTEGER?this._reserveMemory(e,s-67108864,"parse"):()=>{};try{let d=wt(e);if(t.mesh)e.geometry=Eo(t.mesh,r);else if(t.data)e.geometry=Xe(new Float32Array(t.data.positions),t.data.colors?new Float32Array(t.data.colors):null,r);else{let l=t.local_file??t.filename??t.remote_url;if(!l)throw new Error("A filename, local_file or mesh is required");let c;if(t.remote_url){let _=new URL(t.remote_url);if(!["https:","http:"].includes(_.protocol)||_.username||_.password)throw new Error("Use a public HTTP or HTTPS file URL without login credentials.");if(!this.options.url_import_endpoint)throw new Error("Configure url_import_endpoint to import remote URLs through a relay.");if(c=new URL(this.options.url_import_endpoint,document.baseURI).href,new URL(c).origin!==location.origin)throw new Error("The URL relay must use the same origin as this page.")}let m=this._pool.run({kind:"parse",remote:t.remote_url,format:t.remote_format,relay:c,source:typeof l=="string"?new URL(l,document.baseURI).href:l,name:e.filename,limits:r,features:d.smooth||d.edges||d.wire?d:null},_=>this._progress(e.id,_));this._jobs.set(e,m),e.geometry=await m,t.remote_url&&(e.filename=e.geometry.source_name,delete e.remote_url,delete e.remote_format)}if(this.killsign||i!==this._generation||this._models.get(e.id)!==e){e.geometry=null;return}this.center_models&&(e.center=e.geometry.bounds.min.map((l,c)=>(l+e.geometry.bounds.max[c])/2)),(t.mesh||t.data)&&(d.smooth||d.edges||d.wire)&&await this._derive(e,d),t.animation&&this.animate_model(e.id,t.animation),delete e.mesh,delete e.data,this._invalidate()}finally{n()}}_finishSession(){this.killsign||this._pending.size||this._sceneLoads.size||!this._sessionActive||(this._sessionActive=!1,this._restoreCamera?(this.set_camera_state(this._restoreCamera),this._restoreCamera=null):this.zoom===-1&&this.set_auto_zoom(),this._gridAuto&&this.set_grid(!0),this.load_session++,this._callback("all_loaded_callback"),this._invalidate())}_deleteModel(e){for(let[t,i]of this._printJobs||[])i.has(e)&&t.abort();this._printCache=null,this.renderer?.selection===e&&this.renderer.setSelection(null),this.renderer?.remove(e),this._jobs.get(e)?.cancel?.();for(let t of e._deriveJobs||[])t.cancel?.();this._models.delete(e.id),this._memoryReservations.delete(e),this.models[this.models_ref[e.id]]=null,delete this.models_ref[e.id],delete this.load_status[e.id],this.models_count=this._models.size,this._invalidate()}remove_model(e){return this._guard(()=>{let t=this._model(e);this._deleteModel(t),this._pending.delete(t.id),this._finishSession()})}clean(){if(!this.killsign){this._generation++,this._printCache=null,this._pool?.clear();for(let e of this._aborters)e.abort();this._aborters.clear(),this._models.clear(),this._pending.clear(),this._jobs.clear(),this._derived.clear(),this._memoryReservations.clear(),this._sceneLoads.clear(),this.models=[],this.models_ref=Object.create(null),this.models_count=0,this.max_model_id=-1,this.load_status=Object.create(null),this._sessionActive=!1,this.renderer?.clear(),this._restoreCamera=null,this._invalidate()}}dispose(){if(!this.killsign){this.clean(),this.killsign=!0,this.ready=!1,this._readyReject(new Error("Viewer disposed")),cancelAnimationFrame(this._frame),this._frame=0,this._pool?.dispose(),this.controls?.dispose(),this._resizeObserver?.disconnect(),this._windowResize&&window.removeEventListener("resize",this._windowResize),this.renderer?.dispose(),this.canvas&&(this.canvas.removeEventListener("drop",this._drop),this.canvas.removeEventListener("dragover",this._drag),this.canvas.removeEventListener("keydown",this._key),this.canvas.remove()),this._removeMessage();for(let e of Object.keys(this))e.endsWith("_callback")&&(this[e]=null);this.options={}}}async when_idle(){for(await this.when_ready;this._pending.size||this._sceneLoads.size||this._derived.size;)await Promise.allSettled([...this._pending.values(),...this._sceneLoads,...this._derived]);return this}_derive(e,t){let i=this._occupiedMemory(e)+[...this._memoryReservations?.get(e)?.values()||[]].filter(m=>m.kind==="derive").reduce((m,_)=>m+_.bytes,0),s=no(e.geometry.triangles,{...t,smooth:t.smooth||!!e.geometry.smoothNormals,edges:t.edges||!!e.geometry.edges,wire:t.wire||!!e.geometry.wire});K(this.limits,i+s,"view mode");let r=this._reserveMemory(e,s-67108864),n=this._generation,d=e._deriveRevision=(e._deriveRevision||0)+1,l;try{let m=e.geometry.positions.slice();l=this._pool.run({kind:"derive",positions:m,features:t,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-i}},null,[m.buffer])}catch(m){throw r(),m}(e._deriveJobs??=new Set).add(l);let c=l.then(m=>!this.killsign&&n===this._generation&&d===e._deriveRevision&&this._models.get(e.id)===e?(Object.assign(e.geometry,m),this.renderer.remove(e),this._invalidate(),!0):!1).catch(m=>{m.name!=="AbortError"&&n===this._generation&&this._error(m)}).finally(()=>{r(),e._deriveJobs.delete(l),this._derived.delete(c)});return this._derived.add(c),c}set_view(e,t){return this._guard(()=>{let i=this._model(e),s=i._requestedView||i,r=He(t.display??s.display,t.smooth_shading??s.smooth_shading),n=wt({...i,...r}),d=i.geometry,c=d&&(n.smooth&&!d.smoothNormals||n.wire&&!d.wire||n.edges&&(!d.edges||d.edgeAngle!==n.edgeAngle))?this._derive(i,n):null,m=(i._viewRevision||0)+1;i._viewRevision=m,i._requestedView=r;let _=w=>{this._models.get(e)!==i||i._viewRevision!==m||(w&&Object.assign(i,r),delete i._requestedView,this._invalidate())};if(!c)_(!0);else{let w=c.then(_).finally(()=>this._derived.delete(w));this._derived.add(w)}})}set_display(e,t){return this.set_view(e,{display:t,...t==="flat"||t==="smooth"?{smooth_shading:t==="smooth"}:{}})}set_smooth(e,t){return this._guard(()=>{let i=this._model(e),s=i._requestedView||i;return this.set_view(e,{display:s.display==="smooth"?"flat":s.display,smooth_shading:t})})}set_section(e,t){return this._guard(()=>{this._model(e).section=qe(t),this._invalidate()})}set_color(e,t){return this._guard(()=>{t!==null&&t!=="transparent"&&Y(t);let i=this._model(e);i.color=t,this._invalidate()})}set_opacity(e,t){return this._guard(()=>{if(Q(t,"opacity"),t<0||t>1)throw new Error("Opacity must be between 0 and 1");this._model(e).opacity=t,this._invalidate()})}set_position(e,t,i,s){return this._guard(()=>{let r=this._model(e);[t,i,s].forEach((n,d)=>{n!==void 0&&(r[["x","y","z"][d]]=Q(n,"position"))}),this._invalidate()})}set_scale(e,t,i=t,s=t,r=!1){return this._guard(()=>{let n=this._model(e);if([t,i,s].forEach(d=>me(d,"scale")),r){if(t/n.scalex!==i/n.scaley||t/n.scalex!==s/n.scalez)throw new Error("keep_prev_scale requires a uniform geometry factor");n.unitScale*=t/n.scalex}else[n.scalex,n.scaley,n.scalez]=[t,i,s];n._stats=null,this._invalidate()})}set_selection(e){return this._guard(()=>{this.renderer?.setSelection(e===null?null:this._model(e)),this._invalidate()})}set_rotation(e,t,i,s,r=!1){return this._guard(()=>{let n=this._model(e);[t,i,s].forEach((d,l)=>{let c=["rotationx","rotationy","rotationz"][l];d!==void 0&&(n[c]=Q(d,"rotation")+(r?n[c]:0))}),this._invalidate()})}rotate(e,t,i,s){return this.set_rotation(e,t,i,s,!0)}set_edges(e,t){return this._guard(()=>{let i=this._model(e);i.view_edges=!!t,t&&i.geometry&&!i.geometry.edges&&this._derive(i,{edges:!0}),this._invalidate()})}set_grid(e,t,i=10){return this._guard(()=>{if(this._gridAuto=!!e&&t===void 0,!e)this.grid=null,this.renderer?.setGrid(null);else{let s=this._bounds();if(t??=s&&Math.max(...V(s.max,s.min))*1.2||10,me(t,"grid size"),!Number.isInteger(i)||i<1||i>1e3)throw new Error("Grid divisions must be 1\u20131000");this.grid={size:t,divisions:i},this.renderer?.setGrid(this.grid)}this._invalidate()})}set_infinite_grid(e,t=1,i=0){return this._guard(()=>{me(t,"grid spacing"),Q(i,"grid height"),this._gridAuto=!1,this.grid=e?{infinite:!0,spacing:t,height:i}:null,this.renderer?.setGrid(this.grid),this._invalidate()})}set_model_units(e,t,i=!1){return this._guard(()=>{if(!["mm","inch"].includes(t))throw new Error("Units must be mm or inch");let s=this._model(e);i&&s.units!==t&&(s.unitScale*=t==="inch"?25.4:1/25.4),s.units=t,s._stats=null,this._invalidate()})}set_bg_color(e){return this._guard(()=>{e!=="transparent"&&Y(e),this.bg_color=e,this._invalidate()})}set_center_models(e){this.center_models=!!e}set_zoom(e=-1){return this._guard(()=>{if(Q(e,"zoom"),e===-1)this.zoom=-1,this.set_auto_zoom();else{if(e<0)throw new Error("Zoom must be non-negative or -1");this.zoom=e,this.camera.position[2]=Math.max(e,1e-6),this._invalidate()}})}set_auto_zoom(){let e=this._bounds();if(!e)return;this.zoom=-1;let t=(this.canvas?.clientWidth||1)/(this.canvas?.clientHeight||1),i=Math.min(Math.PI/8,Math.atan(Math.tan(Math.PI/8)*t)),s=H(V(this.camera.position,this.camera.target));this.camera.target=e.center,this.camera.position=re(e.center,oe(Math.hypot(...s)?s:[0,0,1],e.radius/Math.sin(i)*1.08)),this._invalidate()}set_auto_rotate(e){this.auto_rotate=!!e,this._invalidate()}set_mouse_zoom(e){this.mouse_zoom=!!e,this.controls&&(this.controls.noZoom=!e)}set_auto_resize(e){this.auto_resize=!!e,this._resizeObserver?.disconnect(),this._windowResize&&window.removeEventListener("resize",this._windowResize),!(!e||!this.canvas)&&(typeof ResizeObserver<"u"&&(this._resizeObserver=new ResizeObserver(()=>this.do_resize()),this._resizeObserver.observe(this.parent_element)),this._windowResize=()=>this.do_resize(),window.addEventListener("resize",this._windowResize))}do_resize(){if(!this.renderer||this.killsign)return;let e=this.canvas.getBoundingClientRect();this.renderer.resize(e.width||this.parent_element.clientWidth||300,e.height||this.parent_element.clientHeight||150,this.options.pixel_ratio??Math.min(globalThis.devicePixelRatio||1,2)),this._invalidate()}set_on_model_mousedown(e){this.onmousedown_callback=e}set_on_model_mouseclick(e){this.set_on_model_mousedown(e)}set_drag_and_drop(e){this.allow_drag_and_drop=!!e,this.canvas&&(this.canvas.removeEventListener("drop",this._drop),this.canvas.removeEventListener("dragover",this._drag),e&&(this.canvas.addEventListener("drop",this._drop),this.canvas.addEventListener("dragover",this._drag)))}get_camera_state(){return{position:tt(this.camera.position),up:tt(this.camera.up),target:tt(this.camera.target)}}set_camera_state(e){return this._guard(()=>{let t=e.position?At(e.position,"position"):this.camera.position,i=e.up?At(e.up,"up"):this.camera.up,s=e.target?At(e.target,"target"):this.camera.target;if(!Math.hypot(...i)||!Math.hypot(...V(t,s)))throw new Error("Degenerate camera state");this.camera.position=t,this.camera.up=H(i),this.camera.target=s,this._invalidate()})}set_camera(e,t,i){return this.set_camera_state({position:{x:e??this.camera.position[0],y:t??this.camera.position[1],z:i??this.camera.position[2]}})}pick_model(e,t){if(!this.ready||this.killsign||!Number.isFinite(e)||!Number.isFinite(t))return null;let i=this.canvas.getBoundingClientRect();if(!i.width||!i.height||e<i.left||e>i.right||t<i.top||t>i.bottom)return null;let s=fe(Ye(this.camera,i.width/i.height)),r=le(s,[(e-i.left)/i.width*2-1,1-(t-i.top)/i.height*2,1]),n=H(V(r,this.camera.position)),d=null,l=1/0;for(let c of this._models.values())if(c.geometry&&c.opacity>0&&c.color!=="transparent"){let m=fe(X(c)),_=lo(c.geometry,le(m,this.camera.position),le(m,n,0),We(c.geometry.bounds,c.section));_<l&&(l=_,d=c.id)}return d===null?null:{id:d,distance:l}}_pick(e,t){if(!this.onmousedown_callback)return;let i=this.pick_model(e.clientX,e.clientY);(i||this.send_no_model_click_event)&&this._callback("onmousedown_callback",i?.id??null,e,i?.distance??0,t)}get_model_info(e){return this._guard(()=>{let t=this._model(e),i=t.geometry;if(!i)return null;let s=t.scaleVector,r=s.join(",");(!t._stats||t._stats.key!==r)&&(t._stats={key:r,...co(i,s)});let n=t.units==="inch"?1/25.4:1;return{name:t.filename,orig_filename:t.orig_filename??null,position:{x:t.x,y:t.y,z:t.z},dims:tt(i.bounds.min.map((d,l)=>(i.bounds.max[l]-d)*s[l])),rotation:{x:t.rotationx,y:t.rotationy,z:t.rotationz},display:t.display,smooth_shading:t.smooth_shading,section:t.section?{...t.section}:null,color:t.color??(i.colors?"#ffffff":"#909090"),scale:{x:t.scalex,y:t.scaley,z:t.scalez},volume:t._stats.volume*n**3,area:t._stats.area*n**2,triangles:i.triangles,bounds:{min:[...i.bounds.min],max:[...i.bounds.max]},has_colors:!!i.colors,source_units:i.units||null,units:t.units,opacity:t.opacity,file_bytes:i.file_bytes??null}})}get_model_data(e){return this._guard(()=>{let t=this._model(e);return t.geometry?{positions:t.geometry.positions.slice(),colors:t.geometry.colors?.slice()??null}:null})}get_model_mesh(e){return this._guard(()=>{let t=this._model(e);return t.geometry?ko(t,this.options.three??globalThis.THREE):null})}get_stl_bin(e){return this._guard(()=>{let t=this._model(e);return t.geometry?ho(t.geometry,t.scaleVector,t.center):null})}check_printability(e={}){return bo.call(this,e)}is_printability_current(e){return et(this,e)}show_printability(e,t){return vo.call(this,e,t)}clear_printability(){this.renderer?.setDiagnostic(null),this._invalidate()}async export_models(e={}){let t=wo(e),i=e.signal,s=()=>{throw new DOMException("Export cancelled","AbortError")};if(i?.aborted&&s(),await this.when_ready,i?.aborted&&s(),this.killsign)throw new Error("Viewer disposed");let r=e.model_ids;if(r==="selected"){let f=this.renderer?.selection;r=f?[f.id]:[]}else r===void 0&&(r=[...this._models.keys()]);if(!Array.isArray(r)||r.some(f=>!Number.isSafeInteger(f))||new Set(r).size!==r.length)throw new Error("model_ids must be an array of unique model IDs or 'selected'");if(!r.length)throw new Error("No models to export");let n=r.map(f=>{let u=this._model(f);if(!u.geometry)throw new Error(`Model ${f} has not finished loading`);return u});if(n.reduce((f,u)=>f+u.geometry.triangles,0)>this.limits.max_triangles)throw new Error("Export triangle limit exceeded");let l=xo(n,t),c=this._occupiedMemory();K(this.limits,c+l,"export"),this._exportBytes=(this._exportBytes||0)+l;let m,_,w;try{let f=n.map(p=>{let y=t.apply_transforms?p:{...p,center:[0,0,0],x:0,y:0,z:0,rotationx:0,rotationy:0,rotationz:0,scaleVector:[p.unitScale,p.unitScale,p.unitScale]},g=X(y),A=t.units==="inch"?1/25.4:1;for(let k=0;k<4;k++)for(let E=0;E<3;E++)g[k*4+E]*=A;return{id:p.id,name:ot(p.filename),matrix:g,positions:p.geometry.positions.slice(),colors:t.colors?p.geometry.colors?.slice():null,color:t.colors&&p.color&&p.color!=="transparent"?Y(p.color):null,opacity:p.color==="transparent"?0:p.opacity}}),u=f.flatMap(p=>[p.positions.buffer,...p.colors?[p.colors.buffer]:[]]);m=this._pool.run({kind:"export",models:f,options:t,limits:{...this.limits,max_memory_bytes:this.limits.max_memory_bytes-c}},p=>{try{e.on_progress?.(p)}catch(y){w=y,m.cancel()}},u),_=()=>m.cancel(),i?.addEventListener("abort",_,{once:!0}),i?.aborted&&_();try{return await m}catch(p){throw w||p}}finally{_&&i?.removeEventListener("abort",_),this._exportBytes-=l}}async download_models(e={}){let t=await this.export_models(e);if(e.signal?.aborted)throw new DOMException("Export cancelled","AbortError");return Ce(t.blob,t.filename),t}download_model(e,t){let i=this.get_stl_bin(e);i&&Ce(new Blob([i],{type:"model/stl"}),t??this._model(e).filename??"model.stl")}async export_scene(e={}){let t=String(e.format||"").toLowerCase();if(!["vsb","vsj"].includes(t))throw new Error("Scene format must be vsb or vsj");if(e.force_basename!==void 0&&typeof e.force_basename!="boolean")throw new Error("force_basename must be a boolean");if(e.on_progress!==void 0&&typeof e.on_progress!="function")throw new Error("on_progress must be a function");let i=e.signal,s=()=>{if(i?.aborted)throw new DOMException("Export cancelled","AbortError")};if(s(),await this.when_ready,s(),this.killsign)throw new Error("Viewer disposed");if(this._pending.size||this._sceneLoads.size)throw new Error("Wait for models to finish opening before saving the scene");let r=[...this._models.values()];if(!r.length)throw new Error("No models to export");if(r.some(u=>!u.geometry))throw new Error("A model has not finished loading");if(r.length+(t==="vsb"?1:0)>this.limits.max_archive_entries)throw new Error("Scene model count limit exceeded");let n=r.reduce((u,p)=>u+p.geometry.triangles,0);if(n>this.limits.max_triangles)throw new Error("Scene triangle limit exceeded");let d=this.get_vsj(!0,!!e.force_basename,t==="vsb"),l=JSON.stringify(d),c=new Blob([l]).size;if(c>this.limits.max_file_bytes)throw new Error("Scene manifest size limit exceeded");if(t==="vsb"&&c+r.length*84+n*50>this.limits.max_uncompressed_bytes)throw new Error("Scene archive limit exceeded");let m=67108864+l.length*8+(t==="vsb"?n*512:0);K(this.limits,this._occupiedMemory()+m,"scene export"),this._exportBytes+=m;let _,w,f;try{e.on_progress?.({loaded:0,total:r.length,phase:"exporting"}),s();let u;if(t==="vsj")u=new Blob([l],{type:"application/json"}),e.on_progress?.({loaded:r.length,total:r.length,phase:"complete"});else{let p=r.map(y=>({id:y.id,triangles:y.geometry.triangles,positions:y.geometry.positions.slice(),colors:y.geometry.colors?.slice()||null,scale:y.scaleVector,center:[...y.center]}));_=this._pool.run({kind:"export-scene",scene:d,models:p,limits:this.limits},y=>{try{e.on_progress?.(y)}catch(g){f=g,_.cancel()}},p.flatMap(y=>[y.positions.buffer,...y.colors?[y.colors.buffer]:[]])),w=()=>_.cancel(),i?.addEventListener("abort",w,{once:!0}),i?.aborted&&w();try{u=await _}catch(y){throw f||y}}return s(),{blob:u,format:t,filename:St(e.filename,t),model_ids:r.map(p=>p.id),triangles:n,representation:"scene"}}finally{w&&i?.removeEventListener("abort",w),this._exportBytes-=m}}async download_scene(e={}){let t=await this.export_scene(e);if(e.signal?.aborted||this.killsign)throw new DOMException("Export cancelled","AbortError");return Ce(t.blob,t.filename),t}get_vsj(e=!1,t=!1,i=!1){let s={canvas_height:this.canvas_height,bg_color:this.bg_color,camera_state:this.get_camera_state(),auto_rotate:this.auto_rotate,mouse_zoom:this.mouse_zoom,auto_resize:this.auto_resize,center_models:this.center_models,models:[]};this.grid&&(s.grid=!0,this.grid.infinite&&(s.infinite_grid={spacing:this.grid.spacing,height:this.grid.height}));for(let r of this._models.values())if(r.geometry){let n={id:i?-1:r.id,filename:i?`${r.id}.stl`:t?ot(r.filename):r.filename,units:r.units,display:r.display,opacity:r.opacity,view_edges:r.view_edges,smooth_shading:r.smooth_shading,section:r.section?{...r.section}:null};i&&(n.orig_filename=ot(r.orig_filename||r.filename));for(let d of Le)d.startsWith("scale")||(n[d]=r[d]);i||(n.scalex=r.scalex*r.unitScale,n.scaley=r.scaley*r.unitScale,n.scalez=r.scalez*r.unitScale),r.color&&(n.color=r.color),r.animation&&(n.animation=structuredClone(r.animation)),s.models.push(n)}return e?s:JSON.stringify(s)}download_vsj(e="1.vsj"){Ce(new Blob([this.get_vsj()],{type:"application/json"}),e)}get_vsb(){return{vsj:this.get_vsj(!0,!0,!0),files:[...this._models.values()].filter(e=>e.geometry).map(e=>({id:e.id,bin:this.get_stl_bin(e.id)}))}}async get_vsb_blob(){await this.when_ready;let e=this.get_vsb(),t=this._pool.run({kind:"zip",scene:e,limits:this.limits},null,e.files.map(i=>i.bin));return new Blob([await t],{type:"application/zip"})}download_vsb(e="1.vsb"){return this.get_vsb_blob().then(t=>Ce(t,/\.vsb$/i.test(e)?e:`${e}.vsb`)).catch(t=>this._error(t))}import_scene(e,t={}){return go.call(this,e,t)}_sceneLoad(e,t){let i=this._generation;this._sessionActive=!0;let s=(async()=>{if(await this.when_ready,i!==this._generation||this.killsign)return;let r,n,d=document.baseURI;if(typeof e=="string"&&(d=new URL(e,document.baseURI).href),t){if(n=await this._pool.run({kind:"unzip",source:typeof e=="string"?d:e,limits:this.limits}),!Object.hasOwn(n,"json_data.vsj"))throw new Error("VSB manifest json_data.vsj is missing");r=JSON.parse(new TextDecoder().decode(n["json_data.vsj"]))}else{let l=new AbortController;this._aborters.add(l);try{r=JSON.parse(new TextDecoder().decode(await Mo(typeof e=="string"?d:e,this.limits.max_file_bytes,()=>{},l.signal)))}finally{this._aborters.delete(l)}}if(!(i!==this._generation||this.killsign)){if(!r||!Array.isArray(r.models)||r.models.length>this.limits.max_archive_entries)throw new Error("Invalid or oversized scene manifest");r.bg_color!==void 0&&this.set_bg_color(r.bg_color),r.center_models!==void 0&&this.set_center_models(r.center_models),r.auto_rotate!==void 0&&this.set_auto_rotate(r.auto_rotate),r.mouse_zoom!==void 0&&this.set_mouse_zoom(r.mouse_zoom),r.auto_resize!==void 0&&this.set_auto_resize(r.auto_resize),r.canvas_height!==void 0&&(this.canvas_height=r.canvas_height,this._setSizeStyle(),this.do_resize()),r.infinite_grid?this.set_infinite_grid(!0,r.infinite_grid.spacing,r.infinite_grid.height):r.grid&&this.set_grid(!0),r.camera_state&&(this._restoreCamera=r.camera_state);for(let l of r.models){if(!l||typeof l!="object")throw new Error("Invalid scene model");let c=So(l);if(delete c.mesh,delete c.data,delete c.local_file,n){if(typeof c.filename!="string"||!Object.hasOwn(n,c.filename))throw new Error("VSB model file is missing");c.local_file=new File([n[c.filename]],c.filename)}else c.filename&&(c.filename=new URL(c.filename,d).href);this.add_model(c)}}})().catch(r=>{i===this._generation&&r.name!=="AbortError"&&this._error(r)}).finally(()=>{this._sceneLoads.delete(s),this._finishSession()});this._sceneLoads.add(s)}load_vsj(e){return this._sceneLoad(e,!1)}load_vsb(e){return this._sceneLoad(e,!0)}animate_model(e,t){return this._guard(()=>{let i=this._model(e);if(!t){i.animation=null,i._animation=null;return}let s=structuredClone(t),r=[];for(let n of["delta","exact"])if(s[n]){let d=s[n];me(d.msec??300,"animation duration");let l={};for(let c of Le){let m=d[c]??(c.startsWith("scale")?d.scale:void 0);m!==void 0&&(Q(m,c),n==="exact"&&c.startsWith("scale")&&me(m,c),l[c]=m)}r.push({mode:n,values:l,duration:d.msec??300,loop:n==="delta"&&!!d.loop,from:Object.fromEntries(Le.map(c=>[c,i[c]]))})}if(!r.length)throw new Error("Animation needs delta or exact");i.animation=s,i._animation={start:null,tracks:r},this._invalidate()})}_animate(e,t){let i=e._animation;i.start??=t;let s=!1;for(let r of i.tracks){let n=(t-i.start)/r.duration,d=r.loop?n:Math.min(n,1);s||=r.loop||n<1;for(let[l,c]of Object.entries(r.values))e[l]=l.startsWith("scale")?Math.max(1e-6,r.mode==="delta"?r.from[l]+c*d:r.from[l]+(c-r.from[l])*d):r.mode==="delta"?r.from[l]+c*d:r.from[l]+(c-r.from[l])*d}e._stats=null,s||(e._animation=null,e.animation=null)}async get_image_blob(e="image/png"){await this.when_idle(),this._clip(),this.renderer.draw([...this._models.values()],this.camera,this.bg_color,!1);let t=new Promise((i,s)=>this.canvas.toBlob(r=>r?i(r):s(new Error("Screenshot failed")),e));return this._invalidate(),t}};var zt=new WeakMap,Yo=["background-color","background-value","cancel-load","clear-scene","close-colors","close-info","close-model","close-scene","close-section","close-view","color-mode-note","colors-button","colors-panel","depth-controls","depth-model-button","dismiss-notice","drop-message","drop-overlay","empty-state","file-heading","file-input","filename","finish-move","fit-button","grid-button","image-button","info-button","info-dimensions","info-panel","info-size","info-triangles","live-status","loading-label","loading-state","memory-budget","model-color","model-color-value","model-controls","model-list","model-tool","move-controls","move-gesture-message","move-mode","move-mode-name","move-model-button","notice","notice-message","open-label","position-details","position-error","position-units","position-x","position-y","position-z","reset-position","reset-rotation","restore-color","rotate-controls","rotate-model-button","rotation-error","rotation-x","rotation-y","rotation-z","sample-button","scene-arrangement","scene-button","scene-count","scene-panel","section-axis","section-button","section-enabled","section-flip","section-panel","section-position","section-value","smooth-shading","unit-note","view-button","view-description","view-label","view-panel","view-status","viewer","workspace"];function Ao(o,e={}){if(!o?.querySelectorAll)throw new TypeError("A Document or HTMLElement root is required");if(zt.has(o))throw new Error("ViewSTL app is already mounted on this root");let t=o.ownerDocument||o,i=t.defaultView,s=i.URL,r=new Map([...o.querySelectorAll("[id]")].map(a=>[a.id,a])),n=a=>r.get((e.id_prefix||"")+a),d=Yo.filter(a=>!n(a));if(Et.some(a=>n(a))&&d.push(...Et.filter(a=>!n(a))),kt.some(a=>n(a))&&d.push(...kt.filter(a=>!n(a))),d.length)throw new Error("Missing ViewSTL controls: "+d.join(", "));let l=new i.AbortController,c=o===t?i:o,m=!1,_=null,w=null,f=()=>{if(m)throw new Error("ViewSTL app is disposed")},u=(a,h,b,S={})=>a.addEventListener(h,b,{...S,signal:l.signal}),p=Vt(i.navigator.deviceMemory),y=["view","section","info","scene","colors"],g=new Map,A=[],k=["#64a5b2","#dd766b","#edb14c","#7ca984","#927fbc","#5a8ec7","#89949d"],E,x,C=0,I=0,v=null,F=0,z=null,P,L,D,M=!1,R=!1,j=!1,O=0,we=1,J=!1,q="move",W,ee,ue="transparent",zo={flat:"Solid",edges:"Solid + edges",wireframe:"Wireframe",xray:"X-ray",mesh:"Mesh overlay",studio:"Studio",orientation:"Face orientation"},ye=[...o.querySelectorAll('[name="view-mode"]')];e.id_prefix&&ye.forEach(a=>a.name=e.id_prefix+"view-mode");let it=e.sample_url,Ie=new Intl.NumberFormat(void 0,{maximumFractionDigits:2});function B(a){m||(n("live-status").textContent=a)}function ne(a){m||(n("notice-message").textContent=a,n("notice").hidden=!1)}function xe(){x?.set_selection(!R&&g.has(v)&&(J||!n("scene-panel").hidden)?v:null)}function Rt(a){n(a+"-panel").hidden=!0,n(a+"-button").setAttribute("aria-expanded","false"),xe()}function ce(){y.forEach(Rt)}function Pe(a){let h=n(a+"-panel").hidden;ce(),n(a+"-panel").hidden=!h,n(a+"-button").setAttribute("aria-expanded",String(h)),xe()}function Me(a){return a==null?"Sample model":a<1024**2?`${Ie.format(a/1024)} KiB`:a<1024**3?`${Ie.format(a/1024**2)} MiB`:`${Ie.format(a/1024**3)} GiB`}function rt(){let a=g.get(v),h=/^#[a-f0-9]{6}$/i.test(a?.color)?a.color:"#ffffff";n("model-color").value=h,n("model-color-value").textContent=a?.color===null?"File colors":h.toUpperCase(),n("restore-color").textContent=a?.embedded?"Restore file colors":"Restore original color",o.querySelectorAll("[data-model-color]").forEach(b=>b.setAttribute("aria-pressed",String(b.dataset.modelColor===a?.color))),o.querySelectorAll("[data-background]").forEach(b=>b.setAttribute("aria-pressed",String(b.dataset.background===ue))),n("background-color").value=ue==="transparent"?"#fafbfb":ue,n("background-value").textContent=ue==="transparent"?"Default":ue.toUpperCase()}function Lt(){let a=v===null?null:x.get_model_info(v),h=a?.display==="smooth"?"flat":a?.display||"flat";for(let T of ye)T.checked=T.value===h,T.disabled=R;n("view-label").textContent=zo[h],n("smooth-shading").checked=a?.smooth_shading||!1,n("smooth-shading").disabled=R||h==="wireframe",n("view-status").textContent=h==="wireframe"?"Smooth shading is available in surface views.":"Shading changes the appearance, not the geometry.";let b=h==="orientation"?"Blue: front faces \xB7 Red: back faces":"";n("view-description").textContent=b,n("view-description").hidden=!b,n("color-mode-note").hidden=h!=="orientation";let S=a?.section;n("section-enabled").checked=!!S,n("section-axis").value=S?.axis||"z",n("section-position").value=(S?.position??.5)*100,n("section-value").textContent=n("section-position").value+"%",n("section-flip").setAttribute("aria-pressed",String(S?.flip||!1)),n("section-button").setAttribute("aria-pressed",String(!!S)),o.querySelectorAll("[data-selected-name]").forEach(T=>T.textContent=g.get(v)?.name||""),rt(),st(),P?.sync(),L?.sync()}function Ct(){if(v===null)return;let a=x.get_model_info(v),h=g.get(v),b=h.units==="mm"?" mm":"";n("info-triangles").textContent=a.triangles.toLocaleString(),n("info-dimensions").textContent=[a.dims.x,a.dims.y,a.dims.z].map(S=>Ie.format(S)).join(" \xD7 ")+b,n("info-size").textContent=Me(h.size),n("unit-note").textContent=b?"Dimensions are shown in millimetres.":"Dimensions are in model units. STL and OBJ files do not specify physical units."}function It(){n("model-list").replaceChildren(...[...g.values()].map(a=>{let h=t.createElement("li"),b=t.createElement("button"),S=t.createElement("button"),T=t.createElement("button");return h.className="model-row",h.classList.toggle("selected",a.id===v),b.className="model-swatch",b.dataset.colorModel=a.id,b.style.backgroundColor=a.color||"#b7c5c9",b.setAttribute("aria-label","Color for "+a.name),b.title="Change color",S.className="model-pick",S.dataset.selectModel=a.id,S.textContent=a.name,S.title=a.name,S.setAttribute("aria-pressed",String(a.id===v)),T.className="icon-button",T.dataset.removeModel=a.id,T.textContent="\xD7",T.setAttribute("aria-label","Remove "+a.name),h.append(b,S,T),h})),n("scene-count").textContent=g.size,n("scene-arrangement").disabled=g.size===0}function be(){let a=g.size>0;n("workspace").classList.toggle("has-models",a),n("viewer").hidden=!a&&!R,n("empty-state").hidden=a||R,n("file-heading").hidden=!a,n("model-controls").hidden=!a,n("filename").textContent=g.get(v)?.name||"",n("filename").title=n("filename").textContent,n("open-label").textContent=a?"Add models":"Open files",n("drop-message").textContent=a?"Add to your scene.":"Drop it here.",It(),Lt(),Ct(),a||ce(),xe()}function Te(a){a&&P?.close(),!m&&(a&&G(!1),R=a,n("loading-state").hidden=!a,n("workspace").setAttribute("aria-busy",String(a)),be())}function Fe(a){P?.close(),g.has(a)&&(G(!1),I++,v=a,be())}function Ro(){return Qt([...g.values()],n("scene-arrangement").value)}function Oe(){x?.set_infinite_grid(j,we,O),n("grid-button").setAttribute("aria-pressed",String(j))}function nt(){if(G(!1),n("scene-arrangement").value==="original")for(let h of g.values())h.rotation={...h.originalRotation||{x:0,y:0,z:0}};let a=Ro();for(let h of a)g.get(h.id).position={x:h.x,y:h.y,z:h.z},at(g.get(h.id));(n("scene-arrangement").value!=="custom"||!g.size)&&(O=$t([...g.values()],a)),st(),Oe(),x?.set_auto_zoom()}function st(){let a=g.get(v);for(let h of["x","y","z"]){let b=n("position-"+h);b.value=Number((a?.position?.[h]||0).toPrecision(12)),b.disabled=!a,b.removeAttribute("aria-invalid")}n("position-error").hidden=!0,n("position-units").textContent=a?.units==="mm"?"Offset from file coordinates, in mm.":"Offset from file coordinates, in model units.",n("move-model-button").disabled=!a||R,n("reset-position").disabled=!a,Tt();for(let h of["rotate-model-button","depth-model-button","depth-nearer","depth-farther"])n(h).disabled=!a||R}function Ee(a,h,b=!0){return!g.has(a)||!Object.values(h).every(S=>Number.isFinite(S)&&Math.abs(S)<=1e12)?!1:(g.get(a).position={...h},at(g.get(a)),n("scene-arrangement").value="custom",a===v&&b&&st(),!0)}function at(a){let h=gt(a),b=a.rotation;x.set_rotation(a.id,...["x","y","z"].map(S=>b[S]%360*Math.PI/180)),x.set_position(a.id,h.x,h.y,h.z)}function Pt(){let a=g.get(v);return a?{...a,center:Xt(a),radius:Kt(a)}:null}function Tt(){let a=g.get(v);for(let h of["x","y","z"]){let b=n("rotation-"+h);b.value=Number((a?.rotation?.[h]||0).toPrecision(10)),b.disabled=!a,b.removeAttribute("aria-invalid")}n("rotation-error").hidden=!0,n("reset-rotation").disabled=!a}function Ne(a,h,b=!0){return!g.has(a)||!Object.values(h).every(S=>Number.isFinite(S)&&Math.abs(S)<=36e4)?!1:(g.get(a).rotation={...h},at(g.get(a)),n("scene-arrangement").value="custom",b&&a===v&&Tt(),!0)}let Lo={move:"Move",rotate:"Rotate",depth:"Depth"},Ft={move:"Drag to move the selected model",rotate:"Drag to rotate the selected model",depth:"Drag up for nearer, down for farther"};function Co(){x?.canvas&&(x.canvas.dataset.modelTool=q),o.querySelectorAll("[data-transform-tab]").forEach(a=>a.setAttribute("aria-pressed",String(a.dataset.transformTab===q)));for(let a of["move","rotate","depth"])n(a+"-controls").hidden=a!==q,n(a+"-model-button").setAttribute("aria-pressed",String(J&&a===q));n("model-tool").value=q,n("move-gesture-message").textContent=Ft[q]+" \xB7 Scroll to adjust depth"}function G(a,h=q){W?.setEnabled(!1),q=h,J=!!a&&v!==null&&!R,W?.setEnabled(J),Co(),n("move-mode").hidden=!J,n("move-mode-name").textContent=g.get(v)?.name||"",n("workspace").classList.toggle("moving-model-mode",J),x?.canvas&&x.canvas.setAttribute("aria-label",J?Ft[q]+". Scroll changes only its depth. Escape exits.":"3D model viewer. Drag to rotate, shift-drag to pan, scroll to zoom."),J&&(ce(),x.canvas.focus({preventScroll:!0}),B(Lo[q]+" tool for "+g.get(v).name+". Done or Escape returns to viewing.")),xe()}for(let a of["x","y","z"]){let h=n("rotation-"+a);u(h,"input",()=>{v===null||!h.value.trim()||Ne(v,{...g.get(v).rotation,[a]:h.valueAsNumber},!1)&&(h.removeAttribute("aria-invalid"),n("rotation-error").hidden=!0)}),u(h,"change",()=>{v!==null&&(!h.value.trim()||!Ne(v,{...g.get(v).rotation,[a]:h.valueAsNumber})?(h.setAttribute("aria-invalid","true"),n("rotation-error").hidden=!1):B("Model rotation updated."))})}u(n("reset-rotation"),"click",()=>{G(!1),v!==null&&(Ne(v,{...g.get(v).originalRotation||{x:0,y:0,z:0}}),B("Selected model rotation reset."))});for(let a of["move","rotate","depth"])u(n(a+"-model-button"),"click",()=>G(!J||q!==a,a));for(let[a,h]of[["depth-nearer",-.12],["depth-farther",.12]])u(n(a),"click",()=>{let b=Pt();b&&(Ee(b.id,Ae(b,x.get_camera_state(),h)),B(h<0?"Selected model moved nearer.":"Selected model moved farther."))});o.querySelectorAll("[data-transform-tab]").forEach(a=>u(a,"click",()=>G(!1,a.dataset.transformTab))),u(n("model-tool"),"change",()=>G(!0,n("model-tool").value));for(let a of["x","y","z"]){let h=n("position-"+a);u(h,"input",()=>{v===null||!h.value.trim()||Ee(v,{...g.get(v).position,[a]:h.valueAsNumber},!1)&&(h.removeAttribute("aria-invalid"),n("position-error").hidden=!0)}),u(h,"change",()=>{v!==null&&(!h.value.trim()||!Ee(v,{...g.get(v).position,[a]:h.valueAsNumber})?(h.setAttribute("aria-invalid","true"),n("position-error").hidden=!1):B("Model position updated."))})}u(n("finish-move"),"click",()=>{G(!1),x?.canvas.focus({preventScroll:!0})}),u(n("reset-position"),"click",()=>{G(!1),v!==null&&(Ee(v,{...g.get(v).originalPosition||{x:0,y:0,z:0}}),B("Selected model restored to its original position."))}),u(i,"blur",()=>{J&&G(!1)});function De(){C++,A.length=0,D?.abort(),z!==null&&(x?.remove_model(z),z=null),Te(!1),B("Opening cancelled.")}function Ot(){P?.close(),G(!1),De(),I++,x?.clean(),g.clear(),v=null,j=!1,O=0,we=1,n("scene-arrangement").value="original",Oe(),n("notice").hidden=!0,be(),B("Scene cleared.")}function lt(a){if(!g.has(a))return;P?.close();let h=g.get(a).name;G(!1),x.remove_model(a),g.delete(a),I++,v===a&&(v=[...g.keys()].at(-1)??null),nt(),be(),B(h+" removed.")}async function Nt(){return E||(E=Promise.resolve().then(async()=>{if(m)throw new Error("ViewSTL app is disposed");if(x=new _e(n("viewer"),{...p.limits,worker_count:1,worker_timeout:e.worker_timeout??6e5,worker_url:e.worker_url,worker_factory:e.worker_factory,url_import_endpoint:e.url_import_endpoint,center_models:!1,bg_color:ue,allow_drag_and_drop:!1,camera_state:{position:{x:90,y:-130,z:90},target:{x:0,y:0,z:0},up:{x:0,y:0,z:1}},loading_progress_callback:a=>{if(m||!R||D)return;let h=a[z];n("loading-label").textContent=h?.phase==="loading-cad"?"Loading the CAD reader\u2026":h?.phase==="parsing-cad"?"Preparing CAD surfaces\u2026":h?.loaded&&!h.total?`Downloading your file\u2026 ${Me(h.loaded)}`:h?.total&&h.loaded<h.total?`Reading your file\u2026 ${Math.min(99,Math.floor(100*h.loaded/h.total))}%`:"Preparing your model\u2026"},model_loaded_callback:a=>{w=a},load_error_callback:a=>{ne(/limit exceeded|safety budget/i.test(a)?`This scene or view mode exceeds the estimated memory budget for this device (${Me(p.budget)}). Remove a model, use Solid view without smoothing, or try a device with more memory.`:`Couldn\u2019t display this model. ${a}`)}}),await x.when_ready,m)throw new Error("ViewSTL app is disposed");return W=to(x.canvas,{getCamera:()=>x.get_camera_state(),getModel:Pt,getTool:()=>q,onRotate:(a,h)=>Ne(a,h),onMove:(a,h)=>Ee(a,h),onStart:()=>{ee=n("scene-arrangement").value},onEnd:(a,h)=>{h&&(n("scene-arrangement").value=ee),B(h?"Move cancelled.":"Model updated.")}}),u(x.canvas,"dblclick",a=>{if(R||a.button!==0)return;let h=x.pick_model(a.clientX,a.clientY);!h||!g.has(h.id)||(a.preventDefault(),Fe(h.id),ce(),n("scene-panel").hidden=!1,n("scene-button").setAttribute("aria-expanded","true"),n("position-details").open=!0,xe(),B(g.get(h.id).name+" selected. Choose Move, Rotate or Depth."))}),x}).catch(a=>{throw W?.dispose(),W=null,x?.dispose(),x=null,E=null,a})),E}function je(a){f();let h=[];for(let S of a){if(!/\.(stl|obj|3mf|step|stp|iges|igs|vsb|vsj)$/i.test(S.name)){ne("Choose STL, OBJ, 3MF, STEP/STP, IGES/IGS, VSB or VSJ files.");continue}if(S.size===0){ne("This file is empty. Choose a file containing a 3D model.");continue}h.push(S)}let b=h.filter(S=>pe(S.name));if(b.length){let S={files:h.filter(T=>!pe(T.name)),used:new Set,failed:!1};for(let T of b)A.push({name:T.name,sceneFile:T,batch:S});A.push({remaining:S})}else A.push(...h);return A.length&&!M&&(_=Dt()),_||Promise.resolve()}function ct(a,h=""){if(f(),!e.url_import_endpoint)throw new Error("URL imports are not configured.");let b=new s(a.trim());if(!["https:","http:"].includes(b.protocol)||b.username||b.password)throw new Error("Use a public HTTP or HTTPS file URL without login credentials.");if(h&&!/^(stl|obj|3mf|step|stp|iges|igs|vsb|vsj)$/.test(h))throw new Error("Choose a supported 3D format.");b.hash="";let S=decodeURIComponent(b.pathname.split("/").at(-1)||"Remote model").slice(0,200);return A.push({name:S,remote:b.href,format:h,size:0,scene:["vsb","vsj"].includes(h)?h:pe(S)}),M||(_=Dt()),_||Promise.resolve()}n("url-form")&&n("url-dialog")&&u(n("url-form"),"submit",a=>{a.preventDefault();try{let h=ct(n("url-input").value,n("url-format").value);n("url-input").value="",n("url-error").hidden=!0,n("url-dialog").close(),h.catch(b=>ne(b.message))}catch(h){n("url-error").textContent=h.message,n("url-error").hidden=!1}});async function Dt(){if(!M){M=!0;try{for(;A.length;){let a=A.shift(),h=C;if(a.remaining){a.remaining.failed||A.unshift(...a.remaining.files.filter(b=>!a.remaining.used.has(b)));continue}Te(!0),n("notice").hidden=!0,n("loading-label").textContent="Opening "+a.name+"\u2026",B("Opening "+a.name);try{if(a.sceneFile||a.scene){await Io(a,h);continue}let b=await Gt(a);if(h!==C)continue;let S=[...g.values()].reduce((U,he)=>U+he.estimated,0);if(b+S>p.budget||a.size>p.limits.max_file_bytes)throw Error(`Adding this file would exceed the estimated ${Me(p.budget)} scene memory budget. Remove a model or try a device with more memory.`);let T=await Nt();if(h!==C)continue;T.do_resize(),F=Math.max(F,T.max_model_id+1);let $=F++;if(z=$,w=null,T.add_model({id:$,display:"flat",...a.remote?{remote_url:a.remote,remote_format:a.format}:a.url?{filename:a.url}:{local_file:a}}),await T.when_idle(),h!==C)continue;z=null;let se=w===$?T.get_model_info($):null;if(!se){B(a.name+" could not be opened.");continue}a.remote&&(a.name=se.name,a.size=se.file_bytes,delete a.remote);let de=se.has_colors,N=de?null:k[g.size%k.length];N&&T.set_color($,N),g.set($,{id:$,name:a.name,size:a.size,units:se.source_units||(/\.3mf$/i.test(a.name)?"mm":null),bounds:se.bounds,position:{x:0,y:0,z:0},rotation:{x:0,y:0,z:0},embedded:de,originalColor:N,color:N,estimated:se.triangles*224,sourceFile:a instanceof i.File?a:null}),v=$,I++,nt(),be(),B(a.name+" is ready."),T.canvas.focus({preventScroll:!0})}catch(b){a.batch&&(a.batch.failed=!0),h===C&&(z!==null&&(x?.remove_model(z),z=null),ne(/WebGL/i.test(b.message)?"3D viewing needs WebGL 2. Try enabling hardware acceleration in your browser, or use another device.":b.message))}}}finally{M=!1,Te(!1)}}}async function Io(a,h){let b=await Nt();if(h!==C)return;let S=new i.AbortController;D=S;try{let T=a.batch?.files||[],$=[...new Set([...g.values()].map(N=>N.sourceFile).filter(Boolean))],se=[...T,...a.sceneFile?$.filter(N=>!T.some(U=>U.name===N.name)&&$.filter(U=>U.name===N.name).length===1):[]],de=await b.import_scene(a.sceneFile||a.remote,{format:a.scene||pe(a.name),signal:S.signal,files:se,on_progress(N){h!==C||m||(n("loading-label").textContent=N.phase==="opening-model"?`Opening scene model ${N.loaded+1} of ${N.total}\u2026`:N.phase==="complete"?"Restoring scene\u2026":N.total?`Reading scene\u2026 ${Math.min(99,Math.floor(N.loaded/N.total*100))}%`:"Reading scene\u2026")}});if(h!==C)return;for(let N of de.used_files)a.batch?.used.add(N);for(let N of de.model_ids){let U=b._models.get(N),he=U.geometry,Po=Object.fromEntries(["min","max"].map(ve=>[ve,he.bounds[ve].map((Fo,Ut)=>(Fo-U.center[Ut])*U.scaleVector[Ut])])),ae={id:N,name:String(U.orig_filename||U.filename).split(/[\\/]/).pop().split(/[?#]/)[0],size:he.file_bytes??U.local_file?.size??null,units:he.units||U.units,bounds:Po,position:{x:0,y:0,z:0},rotation:{x:U.rotationx*180/Math.PI,y:U.rotationy*180/Math.PI,z:U.rotationz*180/Math.PI},embedded:!!he.colors,color:U.color||(he.colors?null:"#909090"),estimated:he.triangles*224,sourceFile:de.used_files.includes(U.local_file)?U.local_file:null},To=gt(ae);for(let ve of["x","y","z"])ae.position[ve]=U[ve]-To[ve];ae.originalPosition={...ae.position},ae.originalRotation={...ae.rotation},ae.originalColor=ae.color,g.set(N,ae)}F=Math.max(F,b.max_model_id+1),v=de.model_ids.at(-1),I++,n("scene-arrangement").value="custom",j=!!b.grid,O=b.grid?.height||0,we=b.grid?.spacing||1,n("grid-button").setAttribute("aria-pressed",String(j)),ht(b.bg_color),be(),B(a.name+" opened. "+de.model_ids.length+" models added."),b.canvas.focus({preventScroll:!0})}finally{D===S&&(D=null)}}o.querySelectorAll("[data-open-file]").forEach(a=>u(a,"click",()=>n("file-input").click())),u(n("file-input"),"change",()=>{let a=[...n("file-input").files];n("file-input").value="",je(a)}),n("sample-button").hidden=!it,u(n("sample-button"),"click",()=>{it&&je([{name:e.sample_name||"Sample.stl",url:it}])}),u(n("close-model"),"click",()=>lt(v)),u(n("cancel-load"),"click",De),u(n("clear-scene"),"click",Ot),u(n("dismiss-notice"),"click",()=>{n("notice").hidden=!0}),u(n("fit-button"),"click",()=>x?.set_auto_zoom()),u(n("scene-arrangement"),"change",nt),u(n("model-list"),"click",a=>{let h=a.target.closest("button");h&&(h.dataset.removeModel!==void 0?lt(Number(h.dataset.removeModel)):h.dataset.selectModel!==void 0?Fe(Number(h.dataset.selectModel)):h.dataset.colorModel!==void 0&&(Fe(Number(h.dataset.colorModel)),Pe("colors")))});async function jt(){if(v===null||R)return;let a=++I,h=ye.find(b=>b.checked).value;ye.forEach(b=>{b.disabled=!0}),n("smooth-shading").disabled=!0,n("view-status").textContent="Preparing view\u2026",n("notice").hidden=!0;try{x.set_view(v,{display:h,smooth_shading:n("smooth-shading").checked}),await x.when_idle()}finally{a===I&&(Lt(),B(n("view-label").textContent+" view ready."))}}ye.forEach(a=>u(a,"change",jt)),u(n("smooth-shading"),"change",jt);for(let a of y)u(n(a+"-button"),"click",()=>{a==="info"&&Ct(),Pe(a),a==="section"&&!n("section-panel").hidden&&!n("section-enabled").checked&&(n("section-enabled").checked=!0,ke())}),u(n("close-"+a),"click",()=>{Rt(a),a==="colors"&&Pe("view"),n(a+"-button").focus()});function ke(){if(v===null)return;let a=n("section-enabled").checked;x.set_section(v,a?{axis:n("section-axis").value,position:Number(n("section-position").value)/100,flip:n("section-flip").getAttribute("aria-pressed")==="true"}:null),n("section-value").textContent=n("section-position").value+"%",n("section-button").setAttribute("aria-pressed",String(a))}u(n("section-enabled"),"change",ke),u(n("section-axis"),"change",ke),u(n("section-position"),"input",ke),u(n("section-flip"),"click",()=>{n("section-flip").setAttribute("aria-pressed",String(n("section-flip").getAttribute("aria-pressed")!=="true")),ke()}),u(n("grid-button"),"click",()=>{j=!j,Oe()});function dt(a){v!==null&&(x.set_color(v,a),g.get(v).color=a,rt(),It())}u(n("model-color"),"input",()=>dt(n("model-color").value)),u(n("restore-color"),"click",()=>dt(g.get(v)?.originalColor)),o.querySelectorAll("[data-model-color]").forEach(a=>u(a,"click",()=>dt(a.dataset.modelColor)));function ht(a){ue=a;let h=a==="transparent"?[255,255,255]:[1,3,5].map(b=>parseInt(a.slice(b,b+2),16));n("workspace").classList.toggle("dark-background",h[0]*.2126+h[1]*.7152+h[2]*.0722<115),x?.set_bg_color(a),rt()}u(n("background-color"),"input",()=>ht(n("background-color").value)),o.querySelectorAll("[data-background]").forEach(a=>u(a,"click",()=>ht(a.dataset.background))),u(n("image-button"),"click",async()=>{if(v===null)return;let a=g.size>1?"viewstl-scene.png":g.get(v).name.replace(/\.[^.]+$/,"")+".png";try{let h=await x.get_image_blob("image/png");if(m)return;let b=s.createObjectURL(h),S=t.createElement("a");S.href=b,S.download=a,S.click(),setTimeout(()=>s.revokeObjectURL(b),1e3),B("Image ready to download.")}catch{ne("The image could not be saved. Please try again.")}});let Be=0,mt=a=>{let h=vt(a.dataTransfer);return h==="files"?h:!e.url_import_endpoint||a.target?.isContentEditable||a.target?.closest?.("input, textarea")?null:h},Ue=()=>{Be=0,n("drop-overlay").hidden=!0};u(c,"dragenter",a=>{mt(a)&&(a.preventDefault(),Be++,n("drop-overlay").hidden=!1)}),u(c,"dragover",a=>{mt(a)&&(a.preventDefault(),a.dataTransfer.dropEffect="copy")}),u(c,"dragleave",a=>{Be>0&&--Be===0&&Ue()}),u(c,"drop",a=>{let h=mt(a);if(Ue(),!!h){a.preventDefault();try{if(h==="files")je([...a.dataTransfer.files]).catch(b=>ne(b.message));else for(let b of oo(a.dataTransfer))ct(b).catch(S=>ne(S.message))}catch(b){ne(b.message)}}}),u(c,"dragend",Ue),u(i,"blur",Ue),o.querySelectorAll("[data-dialog]").forEach(a=>u(a,"click",()=>n(a.dataset.dialog).showModal())),o.querySelectorAll("dialog").forEach(a=>u(a,"click",h=>{if(h.target===a){let b=a.getBoundingClientRect();(h.clientX<b.left||h.clientX>b.right||h.clientY<b.top||h.clientY>b.bottom)&&(a===n("save-dialog")?P.close():a.close())}})),u(o,"keydown",a=>{if(a.key!=="Escape"||n("save-dialog")?.open)return;if(J){G(!1),B("Move tool closed.");return}let h=y.find(b=>!n(b+"-panel").hidden);h&&(ce(),h==="colors"&&Pe("view"),n(h+"-button").focus())}),u(o,"pointerdown",a=>{a.target.closest(".view-panel,.info-panel,.toolbar,.scene-heading-button,.move-mode")||ce()}),n("memory-budget").textContent=`Scene memory budget: ${Me(p.budget)}. Half of ${p.reported?"the memory estimate reported by your browser":"a conservative 2 GiB estimate because this browser does not report device memory"} is kept in reserve. This is an estimate, not a guarantee of available memory.`,n("scene-arrangement").value="original",n("notice").hidden=!0,n("drop-overlay").hidden=!0,n("save-button")&&(P=uo({$:n,listen:u,window:i,getState:()=>({busy:R,selectedId:v,models:[...g.values()]}),getViewer:()=>x,onOpen:()=>{G(!1),ce()},announce:B})),n("print-button")&&(L=fo({$:n,listen:u,window:i,getState:()=>({busy:R,selectedId:v,models:[...g.values()]}),getViewer:()=>x,onOpen:()=>{G(!1),ce()},announce:B})),Oe(),Te(!1),G(!1);let Bt={openFiles:je,openURL:ct,cancelLoading(){f(),De()},clear(){f(),Ot()},select(a){f(),Fe(a)},remove(a){f(),lt(a)},getState(){return f(),{selectedId:v,busy:R,arrangement:n("scene-arrangement").value,memoryBudget:p.budget,models:[...g.values()].map(a=>({id:a.id,name:a.name,size:a.size,units:a.units,position:{...a.position},rotation:{...a.rotation},color:a.color}))}},dispose(){m||(P?.dispose(),L?.dispose(),De(),G(!1),I++,m=!0,l.abort(),W?.dispose(),x?.dispose(),g.clear(),e.id_prefix&&ye.forEach(a=>a.name="view-mode"),zt.delete(o))}};return zt.set(o,Bt),Bt}var Jo=globalThis.document?.currentScript;_e.workerURL=new URL("viewstl.worker.js",Jo?.src||document.baseURI).href;globalThis.StlViewer=_e;globalThis.mountViewstlApp=Ao;})();
@@ -1,6 +1,6 @@
1
1
  # Native WebGL2 plugin: compatibility requirements
2
2
 
3
- Status: v2 implemented; release candidate `2.0.0-rc.3`. This document records the original requirements;
3
+ Status: v2 implemented; release candidate `2.0.0-rc.4`. This document records the original requirements;
4
4
  see [usage and intentional differences](v2-usage.md) for the implemented
5
5
  contract and [verification](v2-verification.md) for actual test coverage.
6
6
 
package/docs/v2-usage.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # ViewSTL 2 — native WebGL2 plugin
2
2
 
3
- Version: `2.0.0-rc.3` (ViewSTL v2.00 release candidate). The npm package is
4
- `@omrips/viewstl`; install the candidate with `npm install @omrips/viewstl@2.0.0-rc.3`.
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
5
  The legacy plugin remains at its original repository paths and on the production
6
6
  website. 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.3`, then copy the complete
10
+ Install with `npm install @omrips/viewstl@2.0.0-rc.4`, then copy the complete
11
11
  `node_modules/@omrips/viewstl/build/v2/` folder to `/viewstl-v2/` on your website.
12
12
  Paste these three lines into your page's `<body>`:
13
13
 
@@ -96,7 +96,7 @@ The CommonJS entry point also requires
96
96
 
97
97
  ## Node.js model analysis
98
98
 
99
- Install `npm install @omrips/viewstl@2.0.0-rc.3`. Node.js 20+ resolves the analysis
99
+ Install `npm install @omrips/viewstl@2.0.0-rc.4`. Node.js 20+ resolves the analysis
100
100
  worker and optional CAD WASM directly inside the package; there is no asset-copy
101
101
  step for server use.
102
102
 
@@ -3,6 +3,23 @@
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
+ ## Runnable server example 2.0.0-rc.4 — 2026-10-20
7
+
8
+ The npm README now contains a clearly labeled server-only example, including
9
+ installation, the `analyze.mjs` filename, a three-line script that prints JSON,
10
+ and the command to run it. This is a documentation update.
11
+
12
+ The exact example was extracted from the packed README and run with a binary
13
+ STL using the installed package on Node.js 24.19.0 on Windows. The resulting
14
+ JSON contains the expected dimensions, surface area, validated volume and mesh
15
+ checks. All 40 packaged files match the local release sources by SHA-256.
16
+ All 31 existing browser/Node runtime, declaration and CAD assets match the
17
+ public `2.0.0-rc.3` package apart from embedded version strings. The legacy
18
+ guard passes for tracked files and all 14 archived files. No runtime logic was
19
+ changed; the broader API verification below still describes the implementation.
20
+
21
+ Evidence is retained locally in `test-results/npm-2.0.0-rc.4/` (ignored by Git).
22
+
6
23
  ## Node.js model analysis 2.0.0-rc.3 — 2026-10-20
7
24
 
8
25
  The new `@omrips/viewstl/node` export provides `analyzeFile`, `analyzeBuffer`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omrips/viewstl",
3
- "version": "2.0.0-rc.3",
3
+ "version": "2.0.0-rc.4",
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",