@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.
@@ -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)`,
@@ -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.6",
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.6.md",
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.