@omrips/viewstl 2.0.0-rc.6 → 2.0.0-rc.7
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 +3 -3
- package/build/chunks/scene-export-7P6ZJOG3.js +1 -0
- package/build/stl_viewer.min.js +6 -5
- package/build/viewstl.cjs +118 -17
- package/build/viewstl.js +119 -17
- package/build/viewstl.min.js +6 -5
- package/build/viewstl.worker.js +1 -1
- package/docs/releases/2.0.0-rc.7.md +16 -0
- package/docs/v2-usage.md +15 -0
- package/docs/v2-verification.md +43 -0
- package/package.json +2 -2
- package/build/chunks/scene-export-FPPL466I.js +0 -1
- package/docs/releases/2.0.0-rc.6.md +0 -7
package/build/viewstl.worker.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as E,c as A,d as R}from"./chunks/chunk-FUXP2W57.js";import{a as b,b as I}from"./chunks/chunk-W5KVLGVR.js";import"./chunks/chunk-6AW5U52H.js";function S(t,e,o,r=0){if(t>o.max_triangles)throw new Error("Triangle limit exceeded");b(o,67108864+r+t*224);let l=new Float32Array(t*9),n=null,d=new TextDecoder().decode(e),s=d.indexOf("COLOR="),a=s>=0&&s+10<=80,f=/^Binary colored STL by viewstl\.com/i.test(d),c=a?[...e.subarray(s+6,s+9)].map(i=>i/255):[144/255,144/255,144/255];return{write(i,w,x){for(let m=0;m<9;m++)l[x*9+m]=i.getFloat32(w+12+m*4,!0);let y=i.getUint16(w+48,!0),L=a||f||!!(y&32768);if(L&&!n){n=new Float32Array(t*9);for(let m=0;m<n.length;m++)n[m]=c[m%3]}if(L){let m=a&&y&32768?c:(a||f?[y&31,y>>5&31,y>>10&31]:[y>>10&31,y>>5&31,y&31]).map(g=>g/31);for(let g=0;g<9;g++)n[x*9+g]=m[g%3]}},finish(){return{...A(l,n,o),format:"stl-binary"}}}}async function D(t,e=E,o=()=>{},r=65536){if(t.size>e.max_file_bytes)throw new Error("File size limit exceeded");if(!Number.isSafeInteger(r)||r<1)throw new Error("Invalid chunk size");if(t.size<84)return null;let l=await t.slice(0,84).arrayBuffer(),n=new DataView(l).getUint32(80,!0);if(!n||84+n*50!==t.size)return null;let d=S(n,new Uint8Array(l,0,80),e);for(let s=0;s<n;s+=r){let a=Math.min(n,s+r),f=await t.slice(84+s*50,84+a*50).arrayBuffer();if(f.byteLength!==(a-s)*50)throw new Error("Truncated binary STL");let c=new DataView(f);for(let i=s;i<a;i++)d.write(c,(i-s)*50,i);o(84+a*50,t.size)}return d.finish()}function V(t,e=E){if(!(t instanceof ArrayBuffer)||t.byteLength>e.max_file_bytes)throw new Error("Invalid or oversized STL");let o=new DataView(t),r=t.byteLength>=84?o.getUint32(80,!0):0,l=r>0&&84+r*50===t.byteLength,n,d=null;if(l){let s=S(r,new Uint8Array(t,0,80),e,t.byteLength);for(let a=0;a<r;a++)s.write(o,84+a*50,a);return s.finish()}else{b(e,67108864+t.byteLength*3);let s=new TextDecoder().decode(t);if(!/^\s*solid(?:\s|$)/i.test(s)||!/\bendsolid\b/i.test(s))throw new Error("Invalid or truncated STL");let a=/\bvertex\s+(\S+)\s+(\S+)\s+(\S+)/gi,f=0,c;for(;a.exec(s);){if(f++,f>e.max_triangles*3)throw new Error("Triangle limit exceeded");b(e,67108864+t.byteLength*3+f/3*224)}if(!f||f%3||(s.match(/\bendfacet\b/gi)||[]).length!==f/3)throw new Error("Incomplete ASCII STL triangles");n=new Float32Array(f*3),a.lastIndex=0;let i=0;for(;c=a.exec(s);)for(let w=1;w<=3;w++)n[i++]=Number(c[w])}return{...A(n,d,e),format:l?"stl-binary":"stl-ascii"}}function C(t,e=E){if(t.byteLength>e.max_file_bytes)throw new Error("File size limit exceeded");b(e,67108864+t.byteLength*3);let o=new TextDecoder().decode(t),r=[],l=[];for(let[d]of o.matchAll(/[^\r\n]+/g)){let s=d.trim().split(/\s+/);if(!(!s[0]||s[0].startsWith("#"))){if(s[0]==="v"){if(s.length<4)throw new Error("Invalid OBJ vertex");let a=s.slice(1,4).map(Number);if(a.some(f=>!Number.isFinite(f)))throw new Error("Invalid OBJ coordinate");if(r.push(a),b(e,67108864+t.byteLength*3+r.length*128+l.length/3*320),r.length>e.max_triangles*3)throw new Error("Vertex limit exceeded")}else if(s[0]==="f"){let a=s.findIndex(c=>c.startsWith("#"));if(a>=0&&(s.length=a),s.length<4)throw new Error("Invalid OBJ face");let f=s.slice(1).map(c=>{let i=Number(c.split("/")[0]),w=i<0?r.length+i:i-1;if(!Number.isInteger(i)||i===0||w<0||w>=r.length)throw new Error("Invalid OBJ index");return w});for(let c=1;c<f.length-1;c++)if(l.push(f[0],f[c],f[c+1]),b(e,67108864+t.byteLength*3+r.length*128+l.length/3*320),l.length/3>e.max_triangles)throw new Error("Triangle limit exceeded")}}}let n=new Float32Array(l.length*3);return l.forEach((d,s)=>n.set(r[d],s*3)),{...A(n,null,e),format:"obj"}}function v(t,e="",o=E){return/\.obj(?:[?#]|$)/i.test(e)?C(t,o):V(t,o)}async function j(t,e,o=()=>{},r){let l,n=0;if(t instanceof ArrayBuffer){if(t.byteLength>e)throw new Error("File size limit exceeded");return o(t.byteLength,t.byteLength),t}if(t instanceof Blob){if(n=t.size,n>e)throw new Error("File size limit exceeded");if(r?.aborted)throw new DOMException("Loading cancelled","AbortError");o(0,n);let i=await t.arrayBuffer();if(r?.aborted)throw new DOMException("Loading cancelled","AbortError");return o(n,n),i}else{let i=new URL(String(t),globalThis.location?.href);if(!["http:","https:","blob:"].includes(i.protocol))throw new Error("Unsupported file URL protocol");let w=await fetch(i,{signal:r,credentials:"same-origin"});if(!w.ok)throw new Error(`HTTP ${w.status}: ${i.pathname}`);n=Number(w.headers.get("content-length"))||0,l=w.body}if(n>e)throw new Error("File size limit exceeded");if(!l)throw new Error("Empty response body");let d=l.getReader(),s=[],a=0;try{for(;;){if(r?.aborted)throw new DOMException("Loading cancelled","AbortError");let{done:i,value:w}=await d.read();if(i)break;if(a+=w.length,a>e)throw new Error("File size limit exceeded");s.push(w),o(a,Math.max(n,a))}}catch(i){throw await d.cancel().catch(()=>{}),i}finally{d.releaseLock()}let f=new Uint8Array(a),c=0;for(let i of s)f.set(i,c),c+=i.length;return o(a,a),f.buffer}var G=/^(stl|obj|3mf|step|stp|iges|igs|vsb|vsj)$/;async function N(t,e=()=>{}){let o=await fetch(t.relay,{method:"POST",credentials:"same-origin",cache:"no-store",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:t.remote,format:t.format||"",max_bytes:t.limits.max_file_bytes})});if(!o.ok){let i=await o.json().catch(()=>null);throw new Error(typeof i?.error=="string"?i.error:`URL import failed (HTTP ${o.status}).`)}let r=o.headers.get("x-viewstl-format")||"";if(!G.test(r)||!o.body)throw await o.body?.cancel(),new Error("The URL relay returned an invalid response.");let l;try{l=decodeURIComponent(o.headers.get("x-viewstl-filename")||"")}catch{}l=l?.replace(/[\x00-\x1f\x7f/\\]/g,"").slice(0,200)||`download.${r}`;let n=Number(o.headers.get("content-length"))||0,d=t.limits,a=/^(step|stp|iges|igs)$/.test(r)?Math.min(d.max_file_bytes,Math.max(0,Math.floor(((d.max_memory_bytes??Number.MAX_SAFE_INTEGER)-128*1024**2)/40))):d.max_file_bytes;if(n>a)throw await o.body.cancel(),new Error("File size limit exceeded for this device.");let f;try{f=await J(o.body,{...d,max_file_bytes:a},r,n,e)}catch(i){throw i instanceof TypeError?new Error("The download stopped before it completed. Try again or open a local copy."):i}let c;try{c=decodeURIComponent(o.headers.get("x-viewstl-base-url")||"")}catch{}return{...f,name:l,format:r,base:c}}async function J(t,e,o,r,l=()=>{}){let n=t.getReader(),d=0,s=o!=="stl",a=null,f=0,c=0,i=[],w=0,x=new Uint8Array(0),y=84,L=[],m=null,g=0,O=h=>{if(b(e,67108864+Math.max(d,r)*3,"remote file"),r){if(m??=new Uint8Array(r),g+h.length>r)throw new Error("The download length changed.");m.set(h,g),g+=h.length}else L.push(h)},U=h=>{if(y){let p=Math.min(y,h.length);h=h.subarray(p),y-=p}if(x.length){let p=new Uint8Array(x.length+h.length);p.set(x),p.set(h,x.length),h=p}let _=h.length-h.length%50,T=new DataView(h.buffer,h.byteOffset,_);for(let p=0;p<_;p+=50){if(84+(f+1)*50>c)throw new Error("The STL download contains extra data.");a.write(T,p,f++)}x=h.slice(_)};try{for(;;){let{value:h,done:_}=await n.read();if(_)break;if(d+=h.length,d>e.max_file_bytes)throw new Error("File size limit exceeded.");if(s)a?U(h):O(h);else if(i.push(h),w+=h.length,w>=84){let T=new Uint8Array(84),p=0;for(let M of i){let B=M.subarray(0,84-p);if(T.set(B,p),p+=B.length,p===84)break}let z=new DataView(T.buffer).getUint32(80,!0);c=84+z*50,z&&(r?c===r:c<=e.max_file_bytes)&&(a=S(z,T.subarray(0,80),e)),s=!0;for(let M of i)a?U(M):O(M);i=[]}l(d,r)}if(!d||r&&d!==r)throw new Error("The download is empty or incomplete.");if(a){if(d!==c||x.length||f*50+84!==c)throw new Error("The STL download is incomplete.");return{geometry:a.finish(),size:d}}for(let h of i)O(h);if(!m){m=new Uint8Array(d);for(let h of L)m.set(h,g),g+=h.length}return{buffer:m.buffer,size:d}}catch(h){throw await n.cancel().catch(()=>{}),h}finally{n.releaseLock()}}function $(t,e=new Set){if(t instanceof ArrayBuffer)e.add(t);else if(ArrayBuffer.isView(t))e.add(t.buffer);else if(t&&typeof t=="object")for(let o of Object.values(t))$(o,e);return[...e]}self.onmessage=async({data:{id:t,task:e}})=>{try{let o=e.limits||E,r;if(e.kind==="derive")b(o,I(e.positions.length/9,e.features),"view mode"),r=R(e.positions,e.features);else if(e.kind==="measure")r=(await import("./chunks/measure-3LDALPCN.js")).measure(e,l=>self.postMessage({id:t,progress:l}));else if(e.kind==="print-check")r=(await import("./chunks/print-check-WWHTT5PW.js")).checkMesh(e.model,e.options,o,l=>self.postMessage({id:t,progress:l}));else if(e.kind==="export")r=await(await import("./chunks/export-2CFMTPJ3.js")).exportGeometry(e.models,e.options,o,l=>self.postMessage({id:t,progress:l}));else if(e.kind==="export-scene")r=(await import("./chunks/scene-export-FPPL466I.js")).exportSceneArchive(e.scene,e.models,o,l=>self.postMessage({id:t,progress:l}));else if(e.kind==="zip")r=(await import("./chunks/archive-QK7XZSVT.js")).zipScene(e.scene,o);else{let l=(c,i)=>self.postMessage({id:t,progress:{loaded:c,total:i}}),n=e.remote?await N(e,l):null,d=n?`download.${n.format}`:e.name;n?.geometry&&(r=n.geometry);let s=/\.(step|stp|iges|igs)(?:[?#]|$)/i.test(d),a=s?Math.min(o.max_file_bytes,Math.max(0,Math.floor(((o.max_memory_bytes??Number.MAX_SAFE_INTEGER)-128*1024**2)/40))):o.max_file_bytes;e.kind==="parse"&&e.source instanceof Blob&&/\.stl(?:[?#]|$)/i.test(e.name)&&(r=await D(e.source,o,l));let f=r?null:n?.buffer??await j(e.source,a,l);if(e.kind==="read-scene"){let c=n?.format||e.format;if(!["vsb","vsj"].includes(c))throw new Error("The URL does not contain a VSB or VSJ scene");r={...(await import("./chunks/scene-reader-WXTRELY3.js")).readSceneDocument(f,c,o),format:c,base:n?.base||e.remote||null}}else if(e.kind==="unzip"){let c=(await import("./chunks/archive-QK7XZSVT.js")).unzipChecked(f,o);r=Object.fromEntries([...c].map(([i,w])=>[i,w.buffer]))}else r??=s?await(await import("./chunks/cad-QGVO6UGR.js")).parseCAD(f,d,o,c=>self.postMessage({id:t,progress:{loaded:f.byteLength,total:f.byteLength,phase:c}})):/\.3mf(?:[?#]|$)/i.test(d)?(await import("./chunks/archive-QK7XZSVT.js")).parse3MF(f,o):v(f,d,o),n&&Object.assign(r,{source_name:n.name,file_bytes:n.size}),e.features&&(b(o,I(r.triangles,e.features),"view mode"),Object.assign(r,R(r.positions,e.features)))}self.postMessage({id:t,result:r},$(r))}catch(o){self.postMessage({id:t,error:o instanceof RangeError?"The browser could not allocate enough memory for this model. Try closing other tabs or using a device with more memory.":o?.message||String(o)})}};
|
|
1
|
+
import{a as E,c as A,d as R}from"./chunks/chunk-FUXP2W57.js";import{a as b,b as I}from"./chunks/chunk-W5KVLGVR.js";import"./chunks/chunk-6AW5U52H.js";function S(t,e,o,r=0){if(t>o.max_triangles)throw new Error("Triangle limit exceeded");b(o,67108864+r+t*224);let l=new Float32Array(t*9),n=null,d=new TextDecoder().decode(e),s=d.indexOf("COLOR="),a=s>=0&&s+10<=80,f=/^Binary colored STL by viewstl\.com/i.test(d),c=a?[...e.subarray(s+6,s+9)].map(i=>i/255):[144/255,144/255,144/255];return{write(i,w,x){for(let m=0;m<9;m++)l[x*9+m]=i.getFloat32(w+12+m*4,!0);let y=i.getUint16(w+48,!0),L=a||f||!!(y&32768);if(L&&!n){n=new Float32Array(t*9);for(let m=0;m<n.length;m++)n[m]=c[m%3]}if(L){let m=a&&y&32768?c:(a||f?[y&31,y>>5&31,y>>10&31]:[y>>10&31,y>>5&31,y&31]).map(g=>g/31);for(let g=0;g<9;g++)n[x*9+g]=m[g%3]}},finish(){return{...A(l,n,o),format:"stl-binary"}}}}async function D(t,e=E,o=()=>{},r=65536){if(t.size>e.max_file_bytes)throw new Error("File size limit exceeded");if(!Number.isSafeInteger(r)||r<1)throw new Error("Invalid chunk size");if(t.size<84)return null;let l=await t.slice(0,84).arrayBuffer(),n=new DataView(l).getUint32(80,!0);if(!n||84+n*50!==t.size)return null;let d=S(n,new Uint8Array(l,0,80),e);for(let s=0;s<n;s+=r){let a=Math.min(n,s+r),f=await t.slice(84+s*50,84+a*50).arrayBuffer();if(f.byteLength!==(a-s)*50)throw new Error("Truncated binary STL");let c=new DataView(f);for(let i=s;i<a;i++)d.write(c,(i-s)*50,i);o(84+a*50,t.size)}return d.finish()}function V(t,e=E){if(!(t instanceof ArrayBuffer)||t.byteLength>e.max_file_bytes)throw new Error("Invalid or oversized STL");let o=new DataView(t),r=t.byteLength>=84?o.getUint32(80,!0):0,l=r>0&&84+r*50===t.byteLength,n,d=null;if(l){let s=S(r,new Uint8Array(t,0,80),e,t.byteLength);for(let a=0;a<r;a++)s.write(o,84+a*50,a);return s.finish()}else{b(e,67108864+t.byteLength*3);let s=new TextDecoder().decode(t);if(!/^\s*solid(?:\s|$)/i.test(s)||!/\bendsolid\b/i.test(s))throw new Error("Invalid or truncated STL");let a=/\bvertex\s+(\S+)\s+(\S+)\s+(\S+)/gi,f=0,c;for(;a.exec(s);){if(f++,f>e.max_triangles*3)throw new Error("Triangle limit exceeded");b(e,67108864+t.byteLength*3+f/3*224)}if(!f||f%3||(s.match(/\bendfacet\b/gi)||[]).length!==f/3)throw new Error("Incomplete ASCII STL triangles");n=new Float32Array(f*3),a.lastIndex=0;let i=0;for(;c=a.exec(s);)for(let w=1;w<=3;w++)n[i++]=Number(c[w])}return{...A(n,d,e),format:l?"stl-binary":"stl-ascii"}}function C(t,e=E){if(t.byteLength>e.max_file_bytes)throw new Error("File size limit exceeded");b(e,67108864+t.byteLength*3);let o=new TextDecoder().decode(t),r=[],l=[];for(let[d]of o.matchAll(/[^\r\n]+/g)){let s=d.trim().split(/\s+/);if(!(!s[0]||s[0].startsWith("#"))){if(s[0]==="v"){if(s.length<4)throw new Error("Invalid OBJ vertex");let a=s.slice(1,4).map(Number);if(a.some(f=>!Number.isFinite(f)))throw new Error("Invalid OBJ coordinate");if(r.push(a),b(e,67108864+t.byteLength*3+r.length*128+l.length/3*320),r.length>e.max_triangles*3)throw new Error("Vertex limit exceeded")}else if(s[0]==="f"){let a=s.findIndex(c=>c.startsWith("#"));if(a>=0&&(s.length=a),s.length<4)throw new Error("Invalid OBJ face");let f=s.slice(1).map(c=>{let i=Number(c.split("/")[0]),w=i<0?r.length+i:i-1;if(!Number.isInteger(i)||i===0||w<0||w>=r.length)throw new Error("Invalid OBJ index");return w});for(let c=1;c<f.length-1;c++)if(l.push(f[0],f[c],f[c+1]),b(e,67108864+t.byteLength*3+r.length*128+l.length/3*320),l.length/3>e.max_triangles)throw new Error("Triangle limit exceeded")}}}let n=new Float32Array(l.length*3);return l.forEach((d,s)=>n.set(r[d],s*3)),{...A(n,null,e),format:"obj"}}function v(t,e="",o=E){return/\.obj(?:[?#]|$)/i.test(e)?C(t,o):V(t,o)}async function j(t,e,o=()=>{},r){let l,n=0;if(t instanceof ArrayBuffer){if(t.byteLength>e)throw new Error("File size limit exceeded");return o(t.byteLength,t.byteLength),t}if(t instanceof Blob){if(n=t.size,n>e)throw new Error("File size limit exceeded");if(r?.aborted)throw new DOMException("Loading cancelled","AbortError");o(0,n);let i=await t.arrayBuffer();if(r?.aborted)throw new DOMException("Loading cancelled","AbortError");return o(n,n),i}else{let i=new URL(String(t),globalThis.location?.href);if(!["http:","https:","blob:"].includes(i.protocol))throw new Error("Unsupported file URL protocol");let w=await fetch(i,{signal:r,credentials:"same-origin"});if(!w.ok)throw new Error(`HTTP ${w.status}: ${i.pathname}`);n=Number(w.headers.get("content-length"))||0,l=w.body}if(n>e)throw new Error("File size limit exceeded");if(!l)throw new Error("Empty response body");let d=l.getReader(),s=[],a=0;try{for(;;){if(r?.aborted)throw new DOMException("Loading cancelled","AbortError");let{done:i,value:w}=await d.read();if(i)break;if(a+=w.length,a>e)throw new Error("File size limit exceeded");s.push(w),o(a,Math.max(n,a))}}catch(i){throw await d.cancel().catch(()=>{}),i}finally{d.releaseLock()}let f=new Uint8Array(a),c=0;for(let i of s)f.set(i,c),c+=i.length;return o(a,a),f.buffer}var G=/^(stl|obj|3mf|step|stp|iges|igs|vsb|vsj)$/;async function N(t,e=()=>{}){let o=await fetch(t.relay,{method:"POST",credentials:"same-origin",cache:"no-store",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:t.remote,format:t.format||"",max_bytes:t.limits.max_file_bytes})});if(!o.ok){let i=await o.json().catch(()=>null);throw new Error(typeof i?.error=="string"?i.error:`URL import failed (HTTP ${o.status}).`)}let r=o.headers.get("x-viewstl-format")||"";if(!G.test(r)||!o.body)throw await o.body?.cancel(),new Error("The URL relay returned an invalid response.");let l;try{l=decodeURIComponent(o.headers.get("x-viewstl-filename")||"")}catch{}l=l?.replace(/[\x00-\x1f\x7f/\\]/g,"").slice(0,200)||`download.${r}`;let n=Number(o.headers.get("content-length"))||0,d=t.limits,a=/^(step|stp|iges|igs)$/.test(r)?Math.min(d.max_file_bytes,Math.max(0,Math.floor(((d.max_memory_bytes??Number.MAX_SAFE_INTEGER)-128*1024**2)/40))):d.max_file_bytes;if(n>a)throw await o.body.cancel(),new Error("File size limit exceeded for this device.");let f;try{f=await J(o.body,{...d,max_file_bytes:a},r,n,e)}catch(i){throw i instanceof TypeError?new Error("The download stopped before it completed. Try again or open a local copy."):i}let c;try{c=decodeURIComponent(o.headers.get("x-viewstl-base-url")||"")}catch{}return{...f,name:l,format:r,base:c}}async function J(t,e,o,r,l=()=>{}){let n=t.getReader(),d=0,s=o!=="stl",a=null,f=0,c=0,i=[],w=0,x=new Uint8Array(0),y=84,L=[],m=null,g=0,O=h=>{if(b(e,67108864+Math.max(d,r)*3,"remote file"),r){if(m??=new Uint8Array(r),g+h.length>r)throw new Error("The download length changed.");m.set(h,g),g+=h.length}else L.push(h)},U=h=>{if(y){let p=Math.min(y,h.length);h=h.subarray(p),y-=p}if(x.length){let p=new Uint8Array(x.length+h.length);p.set(x),p.set(h,x.length),h=p}let _=h.length-h.length%50,T=new DataView(h.buffer,h.byteOffset,_);for(let p=0;p<_;p+=50){if(84+(f+1)*50>c)throw new Error("The STL download contains extra data.");a.write(T,p,f++)}x=h.slice(_)};try{for(;;){let{value:h,done:_}=await n.read();if(_)break;if(d+=h.length,d>e.max_file_bytes)throw new Error("File size limit exceeded.");if(s)a?U(h):O(h);else if(i.push(h),w+=h.length,w>=84){let T=new Uint8Array(84),p=0;for(let M of i){let B=M.subarray(0,84-p);if(T.set(B,p),p+=B.length,p===84)break}let z=new DataView(T.buffer).getUint32(80,!0);c=84+z*50,z&&(r?c===r:c<=e.max_file_bytes)&&(a=S(z,T.subarray(0,80),e)),s=!0;for(let M of i)a?U(M):O(M);i=[]}l(d,r)}if(!d||r&&d!==r)throw new Error("The download is empty or incomplete.");if(a){if(d!==c||x.length||f*50+84!==c)throw new Error("The STL download is incomplete.");return{geometry:a.finish(),size:d}}for(let h of i)O(h);if(!m){m=new Uint8Array(d);for(let h of L)m.set(h,g),g+=h.length}return{buffer:m.buffer,size:d}}catch(h){throw await n.cancel().catch(()=>{}),h}finally{n.releaseLock()}}function $(t,e=new Set){if(t instanceof ArrayBuffer)e.add(t);else if(ArrayBuffer.isView(t))e.add(t.buffer);else if(t&&typeof t=="object")for(let o of Object.values(t))$(o,e);return[...e]}self.onmessage=async({data:{id:t,task:e}})=>{try{let o=e.limits||E,r;if(e.kind==="derive")b(o,I(e.positions.length/9,e.features),"view mode"),r=R(e.positions,e.features);else if(e.kind==="measure")r=(await import("./chunks/measure-3LDALPCN.js")).measure(e,l=>self.postMessage({id:t,progress:l}));else if(e.kind==="print-check")r=(await import("./chunks/print-check-WWHTT5PW.js")).checkMesh(e.model,e.options,o,l=>self.postMessage({id:t,progress:l}));else if(e.kind==="export")r=await(await import("./chunks/export-2CFMTPJ3.js")).exportGeometry(e.models,e.options,o,l=>self.postMessage({id:t,progress:l}));else if(e.kind==="export-scene")r=(await import("./chunks/scene-export-7P6ZJOG3.js")).exportSceneArchive(e.scene,e.models,o,l=>self.postMessage({id:t,progress:l}));else if(e.kind==="zip")r=(await import("./chunks/archive-QK7XZSVT.js")).zipScene(e.scene,o);else{let l=(c,i)=>self.postMessage({id:t,progress:{loaded:c,total:i}}),n=e.remote?await N(e,l):null,d=n?`download.${n.format}`:e.name;n?.geometry&&(r=n.geometry);let s=/\.(step|stp|iges|igs)(?:[?#]|$)/i.test(d),a=s?Math.min(o.max_file_bytes,Math.max(0,Math.floor(((o.max_memory_bytes??Number.MAX_SAFE_INTEGER)-128*1024**2)/40))):o.max_file_bytes;e.kind==="parse"&&e.source instanceof Blob&&/\.stl(?:[?#]|$)/i.test(e.name)&&(r=await D(e.source,o,l));let f=r?null:n?.buffer??await j(e.source,a,l);if(e.kind==="read-scene"){let c=n?.format||e.format;if(!["vsb","vsj"].includes(c))throw new Error("The URL does not contain a VSB or VSJ scene");r={...(await import("./chunks/scene-reader-WXTRELY3.js")).readSceneDocument(f,c,o),format:c,base:n?.base||e.remote||null}}else if(e.kind==="unzip"){let c=(await import("./chunks/archive-QK7XZSVT.js")).unzipChecked(f,o);r=Object.fromEntries([...c].map(([i,w])=>[i,w.buffer]))}else r??=s?await(await import("./chunks/cad-QGVO6UGR.js")).parseCAD(f,d,o,c=>self.postMessage({id:t,progress:{loaded:f.byteLength,total:f.byteLength,phase:c}})):/\.3mf(?:[?#]|$)/i.test(d)?(await import("./chunks/archive-QK7XZSVT.js")).parse3MF(f,o):v(f,d,o),n&&Object.assign(r,{source_name:n.name,file_bytes:n.size}),e.features&&(b(o,I(r.triangles,e.features),"view mode"),Object.assign(r,R(r.positions,e.features)))}self.postMessage({id:t,result:r},$(r))}catch(o){self.postMessage({id:t,error:o instanceof RangeError?"The browser could not allocate enough memory for this model. Try closing other tabs or using a device with more memory.":o?.message||String(o)})}};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# ViewSTL 2.0.0-rc.7
|
|
2
|
+
|
|
3
|
+
Adds optional application shortcuts for Save, model selection mode, and seven
|
|
4
|
+
camera orientations. Hosts provide the buttons and responsive styling; the
|
|
5
|
+
plugin owns the handlers. Selection mode lets users click a model without
|
|
6
|
+
rotating the scene. Escape, camera shortcuts, model manipulation and hiding
|
|
7
|
+
the selection controls on resize restore normal viewing.
|
|
8
|
+
|
|
9
|
+
VSB downloads now use `application/octet-stream`, preventing the ZIP MIME label
|
|
10
|
+
from encouraging Android to append `.zip` to `.vsb`. Both modern and compatibility
|
|
11
|
+
VSB export APIs retain the established archive contents and scene compatibility.
|
|
12
|
+
Physical Android download behavior still requires a device retest.
|
|
13
|
+
|
|
14
|
+
The normal and minified plugin files now retain the previously requested
|
|
15
|
+
first-line author/version banner. Existing APIs, local-file privacy, native
|
|
16
|
+
WebGL 2 rendering and archived v1 distributions remain unchanged.
|
package/docs/v2-usage.md
CHANGED
|
@@ -234,6 +234,21 @@ each layout's IDs and label/ARIA references and pass the matching `id_prefix`;
|
|
|
234
234
|
the controller separates radio groups as well. `dispose()` permits remounting
|
|
235
235
|
and prevents late reads from updating the new app.
|
|
236
236
|
|
|
237
|
+
Optional shortcut buttons can reuse the app actions without extra host-side
|
|
238
|
+
JavaScript. Use `data-open-file` for Open, and `data-viewstl-shortcut` with `save`,
|
|
239
|
+
`select`, `front`, `right`, `top`, `back`, `left`, `bottom` or `isometric` for the
|
|
240
|
+
other actions. Save requires the normal save-dialog control group. These
|
|
241
|
+
buttons do not add required control IDs, so existing app layouts remain valid.
|
|
242
|
+
Use real buttons with descriptive labels/tooltips. The host supplies responsive
|
|
243
|
+
CSS; the ViewSTL website shows its shortcut row only on wide desktop screens.
|
|
244
|
+
|
|
245
|
+
`select` toggles click-to-select mode and reflects it through `aria-pressed`.
|
|
246
|
+
Camera gestures pause in this mode; clicking a model opens its existing tools.
|
|
247
|
+
Escape, a camera shortcut or entering a model transform tool exits selection
|
|
248
|
+
mode. A viewport resize also exits when all selection shortcuts become hidden.
|
|
249
|
+
This is temporary interaction state, not a saved scene property. The existing
|
|
250
|
+
double-click and Models-list selection mechanisms remain available.
|
|
251
|
+
|
|
237
252
|
`openFiles()` accepts Files or configured `{name, url, size?}` sources and
|
|
238
253
|
resolves when the queue settles. File errors appear in the notice; success is
|
|
239
254
|
reflected by `getState().models`. Other methods are `select(id)`, `remove(id)`,
|
package/docs/v2-verification.md
CHANGED
|
@@ -3,6 +3,49 @@
|
|
|
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
|
+
## rc.7 release candidate verification — 2026-11-14
|
|
7
|
+
|
|
8
|
+
The desktop app shortcuts and VSB binary MIME fix pass 147 unit tests, strict
|
|
9
|
+
browser/Node type checks and the 37-file legacy integrity guard. Both staged
|
|
10
|
+
package names pass browser and Node ESM/CommonJS import checks, package-content
|
|
11
|
+
checks and the required first-line version/banner checks.
|
|
12
|
+
|
|
13
|
+
The website candidate passed 43 shortcut checks in headless Windows Edge
|
|
14
|
+
157.0.4341.46: Open/Save, all seven camera presets, unchanged model transforms,
|
|
15
|
+
single-click selection without camera movement, drag rejection, keyboard
|
|
16
|
+
toggle/Escape, resize exit, persistent print results, disabled empty-scene
|
|
17
|
+
actions, and desktop/narrow/touch-only layouts. Another 26 scene-save and 25
|
|
18
|
+
scene-open checks passed. Scene URL fixtures use mocked relay responses.
|
|
19
|
+
Screens/touch are emulation; no physical Android, Safari or Firefox validation
|
|
20
|
+
is claimed. The previously requested banner is included in rc.7.
|
|
21
|
+
|
|
22
|
+
Evidence: website `results/desktop-shortcuts-20261114/` and plugin
|
|
23
|
+
`test-results/desktop-shortcuts-20261114/` / `test-results/vsb-mime-20261114/`.
|
|
24
|
+
The VSB section below records the earlier local checkpoint, before rc.7 staging.
|
|
25
|
+
|
|
26
|
+
## VSB download MIME fix — 2026-11-14 (local, unpublished)
|
|
27
|
+
|
|
28
|
+
The owner reported Android saving scenes as `name.vsb.zip`. Both the scene
|
|
29
|
+
worker used by `export_scene` / `download_scene` and the compatibility
|
|
30
|
+
`get_vsb_blob` / `download_vsb` path now return `application/octet-stream`
|
|
31
|
+
instead of `application/zip`. The `.vsb` filename and ZIP container bytes are
|
|
32
|
+
unchanged; existing VSB files remain compatible. Actual OBJ ZIP exports still
|
|
33
|
+
use their ZIP MIME type.
|
|
34
|
+
|
|
35
|
+
All 147 unit tests pass and the 37-file legacy integrity guard passes. A real
|
|
36
|
+
worker/download check in headless Windows Edge 157.0.4341.46 verified binary
|
|
37
|
+
MIME for both VSB APIs, reopened both two-model scenes with positions preserved,
|
|
38
|
+
and downloaded `android-scene.vsb` without console errors. Evidence is in
|
|
39
|
+
`test-results/vsb-mime-20261114/` and `test-results/vsb-mime-tests-20261114.log`.
|
|
40
|
+
Android download behavior still requires a physical-device retest. A file
|
|
41
|
+
manager that inspects the bytes may continue recognizing the ZIP container.
|
|
42
|
+
|
|
43
|
+
The build is updated locally, including the previously approved author banner.
|
|
44
|
+
The package version remains rc.6 for this unpublished working tree; do not
|
|
45
|
+
overwrite the existing registry artifact. Production/preview still use the
|
|
46
|
+
published rc.6 package and have not received this fix. A new plugin version
|
|
47
|
+
and website deployment are needed to ship it.
|
|
48
|
+
|
|
6
49
|
## npm names and installation — 2026-11-07
|
|
7
50
|
|
|
8
51
|
Published `viewstl@2.0.0-rc.5` and `@omrips/viewstl@2.0.0-rc.5` with `latest`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omrips/viewstl",
|
|
3
|
-
"version": "2.0.0-rc.
|
|
3
|
+
"version": "2.0.0-rc.7",
|
|
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",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"docs/v2-usage.md",
|
|
63
63
|
"docs/v2-compatibility.md",
|
|
64
64
|
"docs/v2-verification.md",
|
|
65
|
-
"docs/releases/2.0.0-rc.
|
|
65
|
+
"docs/releases/2.0.0-rc.7.md",
|
|
66
66
|
"legacy/README.md",
|
|
67
67
|
"THIRD-PARTY-NOTICES.txt"
|
|
68
68
|
],
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{c as a}from"./chunk-CVGQTUJ5.js";import"./chunk-ZSV4PTIA.js";import{f as p}from"./chunk-FUXP2W57.js";import"./chunk-W5KVLGVR.js";import"./chunk-6AW5U52H.js";function g(i,t,l,n){let o=[];for(let e of t)o.push({id:e.id,bin:p(e,e.scale,e.center)}),n({loaded:o.length,total:t.length,phase:"exporting"});n({loaded:t.length,total:t.length,phase:"packaging"});let c=a({vsj:i,files:o},l);return n({loaded:t.length,total:t.length,phase:"complete"}),new Blob([c],{type:"application/zip"})}export{g as exportSceneArchive};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# ViewSTL 2.0.0-rc.6
|
|
2
|
-
|
|
3
|
-
Adds center-preserving resizing, explicit file-unit correction, independent display units, model duplication, seven Z-up camera presets, named scene camera bookmarks, and on-demand surface area / validated volume measurements. Optional application controls are implemented in the plugin; the website remains static markup and styles plus its initializer.
|
|
4
|
-
|
|
5
|
-
VSB and VSJ retain size baselines, corrected units, display units, duplicate labels and bookmarks alongside existing animation state. Older scene manifests remain supported. Local models and measurements stay on the device. Legacy v1 files and releases remain unchanged.
|
|
6
|
-
|
|
7
|
-
New APIs: `get_model_size`, `resize_model`, `reset_model_size`, `set_file_units`, `duplicate_model`, `set_display_units`, `set_camera_view`, `get_camera_bookmarks`, `set_camera_bookmarks`, and `measure_model`. See the usage documentation for units and validation limits.
|