scad-gltf 0.3.3 → 0.4.0

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
- *{box-sizing:border-box}body{color:#ddd;background-color:#222;margin:0;font-family:system-ui,sans-serif;overflow:hidden}#app-container{flex-direction:row;width:100vw;height:100vh;display:flex}@media (width<=800px),(orientation:portrait){#app-container{flex-direction:column}}#left-panel{background:#1e1e1e;border-right:1px solid #444;flex-direction:column;flex:1;gap:15px;min-width:0;min-height:50vh;padding:15px;display:flex;overflow-y:auto}@media (width<=800px),(orientation:portrait){#left-panel{border-bottom:1px solid #444;border-right:none}}#right-panel{background:#111;flex:1;min-width:0;min-height:50vh;position:relative}.panel-section{flex-direction:column;gap:8px;display:flex}.flex-grow{flex-grow:1}h2{color:#fff;text-transform:uppercase;letter-spacing:.5px;margin:0;font-size:14px}.help-text{color:#aaa;margin:0 0 5px;font-size:11px}textarea{color:#d4d4d4;resize:vertical;background:#2a2a2a;border:1px solid #444;border-radius:4px;outline:none;width:100%;padding:10px;font-family:monospace;font-size:12px}textarea:focus{border-color:#666}input[type=text],input[type=number],select{color:#d4d4d4;background:#2a2a2a;border:1px solid #444;border-radius:4px;outline:none;align-items:center;height:34px;padding:0 8px;font-size:13px;display:flex}input[type=text]:focus,input[type=number]:focus,select:focus{border-color:#666}#editor{white-space:pre;flex-grow:1;min-height:150px;overflow:auto}button{color:#fff;cursor:pointer;background:#444;border:1px solid #555;border-radius:4px;justify-content:center;align-items:center;height:34px;padding:0 12px;font-size:13px;font-weight:500;transition:background .2s;display:flex}button:hover{background:#555}button.primary{background:#06c;border-color:#05a}button.primary:hover{background:#07e}button.danger{background:#c00;border-color:#a00}button.danger:hover{background:#e00}.controls-row{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.toggles{-webkit-user-select:none;user-select:none;font-size:13px}.scad-prompt-toggles{-webkit-user-select:none;user-select:none;flex-wrap:wrap;gap:8px 12px;margin-bottom:8px;font-size:13px;display:flex}.scad-prompt-toggles label{cursor:pointer;align-items:center;gap:4px;display:flex}.scad-prompt-group{background:#2a2a2a;border:1px solid #444;border-radius:4px;flex-direction:column;gap:8px;margin-bottom:8px;padding:8px;display:flex}.scad-prompt-group-header{color:#eee;border-bottom:1px solid #444;justify-content:space-between;align-items:center;padding-bottom:6px;font-size:13px;font-weight:700;display:flex}.scad-prompt-group-header label{cursor:pointer;align-items:center;gap:4px;font-size:12px;font-weight:400;display:flex}.scad-prompt-toggles.nested{margin-bottom:0}.toggles label{cursor:pointer;align-items:center;gap:4px;display:flex}.toggles label input[type=number]{color:#d4d4d4;background:#2a2a2a;border:1px solid #444;border-radius:4px;outline:none;width:45px;height:24px;padding:2px 4px;font-family:monospace;font-size:12px}input[type=range]{-webkit-appearance:none;background:0 0;flex:1;width:100%;margin:8px 0}input[type=range]:focus{outline:none}input[type=range]::-webkit-slider-runnable-track{cursor:pointer;background:#444;border-radius:3px;width:100%;height:6px}input[type=range]::-webkit-slider-thumb{cursor:pointer;-webkit-appearance:none;background:#06c;border-radius:50%;width:16px;height:16px;margin-top:-5px}#backend-url{flex:1;min-width:0}#backend-ui{flex-direction:column;gap:10px;margin-top:10px;display:none}#backend-ui.active{display:flex}#backend-select{flex:1;min-width:0}#backend-single-save-btn,#backend-delete-btn{flex-shrink:0}#viewer{width:100%;height:100%;overflow:hidden}#viewer canvas{display:block}#viewer-controls{z-index:10;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#0009;border-radius:6px;align-items:center;gap:12px;padding:6px 12px;display:flex;position:absolute;top:15px;right:15px}#viewer-controls label{color:#fff;cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:4px;margin:0;font-size:13px;display:flex}#fullscreen-btn,#screenshot-btn{color:#fff;cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;height:auto;font-size:16px;display:flex}#fullscreen-btn{padding:0 4px}#screenshot-btn{padding:0 4px 5px}#fullscreen-btn:hover,#screenshot-btn:hover{color:#0af;background:0 0}#drag-overlay{color:#fff;z-index:9999;letter-spacing:1px;background:#0066ccd9;justify-content:center;align-items:center;width:100vw;height:100vh;font-size:2rem;font-weight:700;display:none;position:fixed;top:0;left:0}#drag-overlay.active{display:flex}.footer-section{align-items:center;margin-top:auto;padding-top:5px}.source-link{color:#888;font-size:12px;text-decoration:none;transition:color .2s}.source-link:hover{color:#fff}
1
+ *{box-sizing:border-box}body{color:#ddd;background-color:#222;margin:0;font-family:system-ui,sans-serif;overflow:hidden}#app-container{flex-direction:row;width:100vw;height:100vh;display:flex}@media (width<=800px),(orientation:portrait){#app-container{flex-direction:column}}#left-panel{background:#1e1e1e;border-right:1px solid #444;flex-direction:column;flex:1;gap:15px;min-width:0;min-height:50vh;padding:15px;display:flex;overflow-y:auto}@media (width<=800px),(orientation:portrait){#left-panel{border-bottom:1px solid #444;border-right:none}}#right-panel{background:#111;flex:1;min-width:0;min-height:50vh;position:relative}.panel-section{flex-direction:column;gap:8px;display:flex}.flex-grow{flex-grow:1}h2{color:#fff;text-transform:uppercase;letter-spacing:.5px;margin:0;font-size:14px}.help-text{color:#aaa;margin:0 0 5px;font-size:11px}textarea{color:#d4d4d4;resize:vertical;background:#2a2a2a;border:1px solid #444;border-radius:4px;outline:none;width:100%;padding:10px;font-family:monospace;font-size:12px}textarea:focus{border-color:#666}input[type=text],input[type=number],select{color:#d4d4d4;background:#2a2a2a;border:1px solid #444;border-radius:4px;outline:none;align-items:center;height:34px;padding:0 8px;font-size:13px;display:flex}input[type=text]:focus,input[type=number]:focus,select:focus{border-color:#666}#editor{white-space:pre;flex-grow:1;min-height:150px;overflow:auto}button{color:#fff;cursor:pointer;background:#444;border:1px solid #555;border-radius:4px;justify-content:center;align-items:center;height:34px;padding:0 12px;font-size:13px;font-weight:500;transition:background .2s;display:flex}button:hover{background:#555}button.primary{background:#06c;border-color:#05a}button.primary:hover{background:#07e}button.danger{background:#c00;border-color:#a00}button.danger:hover{background:#e00}.controls-row{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.toggles{-webkit-user-select:none;user-select:none;font-size:13px}.scad-prompt-toggles{-webkit-user-select:none;user-select:none;flex-wrap:wrap;gap:8px 12px;margin-bottom:8px;font-size:13px;display:flex}.scad-prompt-toggles label{cursor:pointer;align-items:center;gap:4px;display:flex}.scad-prompt-group{background:#2a2a2a;border:1px solid #444;border-radius:4px;flex-direction:column;gap:8px;margin-bottom:8px;padding:8px;display:flex}.scad-prompt-group-header{color:#eee;border-bottom:1px solid #444;justify-content:space-between;align-items:center;padding-bottom:6px;font-size:13px;font-weight:700;display:flex}.scad-prompt-group-header label{cursor:pointer;align-items:center;gap:4px;font-size:12px;font-weight:400;display:flex}.scad-prompt-toggles.nested{margin-bottom:0}.toggles label{cursor:pointer;align-items:center;gap:4px;display:flex}.toggles label input[type=number]{color:#d4d4d4;background:#2a2a2a;border:1px solid #444;border-radius:4px;outline:none;width:45px;height:24px;padding:2px 4px;font-family:monospace;font-size:12px}input[type=range]{-webkit-appearance:none;background:0 0;flex:1;width:100%;margin:8px 0}input[type=range]:focus{outline:none}input[type=range]::-webkit-slider-runnable-track{cursor:pointer;background:#444;border-radius:3px;width:100%;height:6px}input[type=range]::-webkit-slider-thumb{cursor:pointer;-webkit-appearance:none;background:#06c;border-radius:50%;width:16px;height:16px;margin-top:-5px}#backend-url{flex:1;min-width:0}#backend-ui{flex-direction:column;gap:10px;margin-top:10px;display:none}#backend-ui.active{display:flex}#backend-select{flex:1;min-width:0}#backend-single-save-btn,#backend-delete-btn{flex-shrink:0}#viewer{width:100%;height:100%;overflow:hidden}#viewer canvas{display:block}#viewer-controls{z-index:10;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#0009;border-radius:6px;flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:12px;max-width:calc(100% - 30px);padding:6px 12px;display:flex;position:absolute;top:15px;right:15px}#viewer-controls select{color:#fff;cursor:pointer;background:#333;border:1px solid #555;border-radius:4px;outline:none;padding:2px 6px;font-size:13px}#viewer-controls label{color:#fff;cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:4px;margin:0;font-size:13px;display:flex}#fullscreen-btn,#screenshot-btn{color:#fff;cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;height:auto;font-size:16px;display:flex}#fullscreen-btn{padding:0 4px}#screenshot-btn{padding:0 4px 5px}#fullscreen-btn:hover,#screenshot-btn:hover{color:#0af;background:0 0}#drag-overlay{color:#fff;z-index:9999;letter-spacing:1px;background:#0066ccd9;justify-content:center;align-items:center;width:100vw;height:100vh;font-size:2rem;font-weight:700;display:none;position:fixed;top:0;left:0}#drag-overlay.active{display:flex}.footer-section{align-items:center;margin-top:auto;padding-top:5px}.source-link{color:#888;font-size:12px;text-decoration:none;transition:color .2s}.source-link:hover{color:#fff}
@@ -0,0 +1 @@
1
+ import{A as e,B as t,D as n,E as r,H as i,O as a,R as o,W as s,a as ee,d as c,et as te,f as ne,ht as re,i as ie,l as ae,lt as oe,mt as se,n as ce,o as le,p as l,r as ue,s as de,t as fe,u as pe,ut as u,w as me,y as he}from"./OutputPass-C3DZe46t.js";var d=document.getElementById(`viewer-container`),f=document.getElementById(`viewer`),p=document.getElementById(`filename-input`),m=document.getElementById(`save-btn`),h=document.getElementById(`open-editor-btn`),g=document.getElementById(`show-grid-cb`),_=document.getElementById(`wireframe-cb`),v=document.getElementById(`fullscreen-btn`),y=document.getElementById(`screenshot-btn`),b=document.getElementById(`camera-select`),x=document.getElementById(`anim-controls`),S=document.getElementById(`anim-play-btn`),C=document.getElementById(`anim-select`),w=document.getElementById(`anim-slider`),T=null,E=null,D=!0,O=!1,k=[],A=``,j=!1,M=null,N=null,P=!1,F=[],I=null;function L(e,t,n){if(e){if(e.isPerspectiveCamera)e.aspect=t/n,e.updateProjectionMatrix();else if(e.isOrthographicCamera){let r=t/n;e.userData.ymag||(e.userData.ymag=e.top),e.left=-e.userData.ymag*r,e.right=e.userData.ymag*r,e.top=e.userData.ymag,e.bottom=-e.userData.ymag,e.updateProjectionMatrix()}}}var R=new te;R.background=new he(2236962);var z=new i(60,window.innerWidth/window.innerHeight,.1,2e3);z.position.set(50,50,-50),I=z;var B=new pe({antialias:!0});B.setSize(window.innerWidth,window.innerHeight),B.setPixelRatio(Math.min(window.devicePixelRatio,2)),B.shadowMap.enabled=!0,B.shadowMap.type=2,B.toneMapping=4,B.toneMappingExposure=1,f.appendChild(B.domElement);var V=new ie(B),H=new ue(R,I);V.addPass(H);var ge=new ce(new oe(window.innerWidth,window.innerHeight),.1,.1,2);V.addPass(ge);var _e=new fe;V.addPass(_e);var U=new de(z,B.domElement);U.enableDamping=!0,U.dampingFactor=.1,R.environment=new ae(B).fromScene(new ee,.04).texture,R.environmentIntensity=.8,b&&b.addEventListener(`change`,e=>{let t=e.target.value;t===``?(I=z,U.enabled=!0):(I=F[parseInt(t)],U.enabled=!1);let n=d?d.clientWidth:window.innerWidth,r=d?d.clientHeight:window.innerHeight;L(I,n,r),H.camera=I});var W=new a;R.add(W);var ve=new s(2e3,2e3),ye=new t({color:2236962,roughness:.8,metalness:.1}),G=new o(ve,ye);G.rotation.x=-Math.PI/2,G.receiveShadow=!0,R.add(G);var K=new n(2e3,100,5592405,3355443);K.material.transparent=!0,K.material.opacity=.5,R.add(K);var q=new ne(100);R.add(q);var J=new me(16777215,1.5);J.castShadow=!0,J.shadow.mapSize.width=2048,J.shadow.mapSize.height=2048,J.shadow.bias=-5e-4,W.add(J),W.add(J.target);var Y=new e(16777215,4473924,.6);Y.position.set(0,20,0),W.add(Y),_&&_.addEventListener(`change`,()=>{let e=_.checked;T&&T.traverse(t=>{t.isMesh&&t.material&&(Array.isArray(t.material)?t.material.forEach(t=>{t.wireframe=e}):t.material.wireframe=e)})}),v&&d&&(v.addEventListener(`click`,()=>{document.fullscreenElement?document.exitFullscreen().catch(e=>{console.warn(`Error attempting to exit fullscreen: ${e.message}`)}):d.requestFullscreen().catch(e=>{console.warn(`Error attempting to enable fullscreen: ${e.message}`)})}),document.addEventListener(`fullscreenchange`,()=>{setTimeout(()=>window.dispatchEvent(new Event(`resize`)),100)})),y&&y.addEventListener(`click`,()=>{P=!0;let e=y.innerText;y.innerText=`✅`,setTimeout(()=>{y.innerText=e},1e3)});function X(){E&&(D=!1,E.paused=!0,S&&(S.innerText=`▶ Play`))}function Z(e){if(!N||!k[e])return;E&&E.stop();let t=k[e];E=N.clipAction(t),E.play(),E.time=0,N.update(0),D=!0,E.paused=!1,S&&(S.innerText=`⏸ Pause`),w&&(w.value=0)}C&&C.addEventListener(`change`,e=>{Z(parseInt(e.target.value))}),S&&S.addEventListener(`click`,()=>{E&&(D=!D,E.paused=!D,S.innerText=D?`⏸ Pause`:`▶ Play`)}),w&&(w.addEventListener(`mousedown`,()=>{O=!0,X()}),w.addEventListener(`mouseup`,()=>{O=!1}),w.addEventListener(`touchstart`,()=>{O=!0,X()},{passive:!0}),w.addEventListener(`touchend`,()=>{O=!1},{passive:!0}),w.addEventListener(`input`,e=>{if(X(),E){let t=E.getClip().duration;E.time=parseFloat(e.target.value)*t,N&&N.update(0)}}));var Q=performance.now();function $(){requestAnimationFrame($);let e=performance.now(),t=(e-Q)/1e3;if(Q=e,N&&(N.update(t),E&&D&&!O)){let e=E.getClip().duration;if(e>0){let t=E.time%e;t<0&&(t+=e),w&&(w.value=t/e)}}U.update();let n=!g||g.checked;if(K.visible=n,q.visible=n,typeof pathTracer<`u`&&E&&D&&pathTracer.updateCamera(),V.render(),P){P=!1;let e=B.domElement.toDataURL(`image/png`);window.parent.postMessage({type:`ADD_IMAGE_TO_CHAT`,dataUrl:e},`*`)}}$(),window.addEventListener(`resize`,()=>{let e=d?d.clientWidth:window.innerWidth,t=d?d.clientHeight:window.innerHeight;L(I,e,t),B.setSize(e,t),V.setSize(e,t)});function be(e,t){if(!e)return new l;e.updateMatrixWorld(!0);let n=new l().setFromObject(e);if(!t||t.length===0)return n;let r=new Map;e.traverse(e=>{r.set(e,{position:e.position.clone(),quaternion:e.quaternion.clone(),scale:e.scale.clone()})});let i=new c(e);for(let r of t){let t=i.clipAction(r);t.play();let a=new Set([0,r.duration]);if(r.tracks){for(let e of r.tracks)if(e.times)for(let t=0;t<e.times.length;t++)a.add(e.times[t])}if(r.duration>0)for(let e=0;e<=20;e++)a.add(e/20*r.duration);let o=Array.from(a).sort((e,t)=>e-t);if(o.length>60){let e=[],t=(o.length-1)/59;for(let n=0;n<60;n++)e.push(o[Math.round(n*t)]);o=e}for(let r of o)t.time=r,i.update(0),e.updateMatrixWorld(!0),n.expandByObject(e);t.stop()}return i.stopAllAction(),i.uncacheRoot(e),r.forEach((e,t)=>{t.position.copy(e.position),t.quaternion.copy(e.quaternion),t.scale.copy(e.scale)}),e.updateMatrixWorld(!0),n}function xe(e){return new Promise((t,n)=>{T&&=(N&&=(N.stopAllAction(),N.uncacheRoot(N.getRoot()),null),E=null,k=[],R.remove(T),T.traverse(e=>{e.isMesh&&(e.geometry.dispose(),Array.isArray(e.material)?e.material.forEach(e=>e.dispose()):e.material.dispose())}),null);let r=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength);new le().parse(r,``,e=>{T=e.scene,k=e.animations||[];let n=_?_.checked:!1;F=[],T.traverse(e=>{e.isCamera&&F.push(e)});let r=!1;b?F.length>0?(b.style.display=`inline-block`,b.innerHTML=`<option value="">Free Camera</option>`,F.forEach((e,t)=>{let n=document.createElement(`option`);n.value=t,n.innerText=e.name||`Camera ${t+1}`,b.appendChild(n)}),b.value=`0`,I=F[0],U.enabled=!1,r=!0):(b.style.display=`none`,b.value=``,I=z,U.enabled=!0):(I=z,U.enabled=!0);let i=f?f.clientWidth:window.innerWidth,a=f?f.clientHeight:window.innerHeight;L(I,i,a),H.camera=I,k.length>0?(N=new c(T),r=!0,x?(S&&(S.style.display=``),C&&(C.style.display=``),w&&(w.style.display=``),C.innerHTML=``,k.forEach((e,t)=>{let n=document.createElement(`option`);n.value=t,n.innerText=e.name||`Animation ${t+1}`,C.appendChild(n)}),Z(0)):(k.forEach(e=>{let t=N.clipAction(e);t.play(),t.time=0}),N.update(0))):(x&&(S&&(S.style.display=`none`),C&&(C.style.display=`none`),w&&(w.style.display=`none`)),E=null),x&&(x.style.display=r?`flex`:`none`),T.traverse(e=>{e.isMesh&&(e.castShadow=!0,e.receiveShadow=!0,e.frustumCulled=!1,e.material&&(Array.isArray(e.material)?e.material.forEach(e=>{e.wireframe=n}):e.material.wireframe=n))}),R.add(T),Se(),t()},e=>{console.error(e),n(e)})})}function Se(){if(!T)return;let e=be(T,k);if(e.isEmpty())return;let t=e.getCenter(new u),n=e.getSize(new u),i=Math.max(n.x,n.y,n.z)||10;G.position.y=e.min.y-.01,K.position.y=G.position.y+.001,q.position.y=G.position.y+.002;let a=z.fov*(Math.PI/180),o=i/(2*Math.tan(a/2));z.aspect<1&&(o/=z.aspect),o*=1.5,z.near=Math.max(.01,i*.01),z.far=Math.max(2e3,o*10),z.updateProjectionMatrix(),U.maxDistance=z.far,z.position.set(t.x+o*.8,t.y+o*.8,t.z-o*.8),z.lookAt(t),U.target.copy(t),U.update(),J.position.set(t.x+i,t.y+i*1.5,t.z-i),J.target.position.copy(t),J.target.updateMatrixWorld();let s=i*1.5;J.shadow.camera.left=-s,J.shadow.camera.right=s,J.shadow.camera.top=s,J.shadow.camera.bottom=-s,J.shadow.camera.near=.1,J.shadow.camera.far=i*5,J.shadow.camera.updateProjectionMatrix(),R.fog=new r(2236962,o*1.5,o*5)}async function Ce(e){let t={},n=`http://localhost:3000`,r=[];try{let e=await fetch(`${n}/api/scads`);e.ok&&(r=(await e.json()).files||[])}catch{return t}let i=new Set;async function a(e){let o=/(?:include|use)\s*([<"])([^>"]+)([>"])/g,s;for(;(s=o.exec(e))!==null;){let e=s[2],o=e.split(/[/\\]/).pop();if(!i.has(e)&&r.includes(o)){i.add(e);try{let r=await fetch(`${n}/api/scads/${encodeURIComponent(o)}`);if(r.ok){let n=await r.json();t[e]=n.content,await a(n.content)}}catch{console.warn(`Failed to load dependency: ${e}`)}}}}return await a(e),t}async function we(e){if(j){M=e;return}j=!0;try{let t=await Ce(e);await xe(await se(e,{wasmUrl:re,additionalFiles:t}))}catch(e){console.error(e)}finally{if(j=!1,M!==null){let e=M;M=null,we(e)}}}window.addEventListener(`message`,async e=>{if(e.data.type===`RENDER_SCAD`){A=e.data.code;let t=A.match(/\/\*\s*Model Name:\s*(.*?)\s*\*\//i);if(t&&t[1]){let e=t[1].trim().replace(/\s+/g,`_`).replace(/[^a-zA-Z0-9_-]/g,``);e&&(p.value=e)}await we(e.data.code)}}),m.addEventListener(`click`,async()=>{let e=p.value.trim();if(!e){alert(`Please enter a filename.`),p.focus();return}let t=`http://localhost:3000`;try{m.innerText=`Checking...`,m.disabled=!0;let n=await fetch(`${t}/api/scads`);if(!n.ok)throw Error("Could not reach `scad-gltf serve`.");let r=await n.json(),i=e.toLowerCase().endsWith(`.scad`)?e:`${e}.scad`;if(r.files&&r.files.includes(i)&&!confirm(`File "${i}" already exists. Overwrite?`)){m.innerText=`Save`,m.disabled=!1;return}m.innerText=`Saving...`;let a=await fetch(`${t}/api/scads`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({filename:e,content:A||``})});if(!a.ok){let e=await a.json();throw Error(e.error||`Save failed.`)}m.innerText=`✅ Saved!`,setTimeout(()=>{m.innerText=`Save`,m.disabled=!1},2e3)}catch(t){console.warn(`Backend not connected or save failed, falling back to download`,t);let n=new Blob([A||``],{type:`text/plain`}),r=URL.createObjectURL(n),i=document.createElement(`a`);i.href=r,i.download=e.toLowerCase().endsWith(`.scad`)?e:`${e}.scad`,document.body.appendChild(i),i.click(),document.body.removeChild(i),URL.revokeObjectURL(r),m.innerText=`✅ Downloaded!`,setTimeout(()=>{m.innerText=`Save`,m.disabled=!1},2e3)}});async function Te(e){try{if(typeof CompressionStream<`u`){let t=new Blob([e]).stream().pipeThrough(new CompressionStream(`deflate-raw`)),n=await new Response(t).arrayBuffer(),r=new Uint8Array(n),i=``;for(let e=0;e<r.length;e++)i+=String.fromCharCode(r[e]);return`c`+btoa(i).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}}catch(e){console.warn(`CompressionStream failed, falling back`,e)}return`u`+btoa(unescape(encodeURIComponent(e))).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}h.addEventListener(`click`,async()=>{if(!A){alert(`No model loaded to open.`);return}try{h.disabled=!0,h.innerText=`Preparing...`;let e=`https://iliagrigorevdev.github.io/scad-gltf/#${await Te(A)}`;window.open(e,`_blank`),window.parent.postMessage({type:`CLOSE_PREVIEW`},`*`),h.innerText=`✅ Opened!`,setTimeout(()=>{h.innerText=`Edit`,h.disabled=!1},2e3)}catch(e){console.error(`Error creating editor link`,e),alert(`Error creating editor link: `+e.message),h.innerText=`Edit`,h.disabled=!1}});
@@ -0,0 +1 @@
1
+ body{background-color:#333;width:100vw;height:100vh;margin:0;font-family:sans-serif;display:flex;overflow:hidden}#viewer-container{background:#1e1e1e;flex-grow:1;width:100%;height:100vh;position:relative;overflow:hidden}#viewer{width:100%;height:100%}#top-ui{z-index:10;pointer-events:none;flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:10px;display:flex;position:absolute;top:15px;left:15px;right:40px}#top-ui>*{pointer-events:auto}#save-ui,#viewer-controls{-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#0009;border-radius:6px;flex-wrap:wrap;align-items:center;gap:8px;padding:6px;display:flex}#viewer-controls select{color:#fff;cursor:pointer;background:#333;border:1px solid #555;border-radius:4px;outline:none;padding:2px 6px;font-size:13px}#viewer-controls label{color:#fff;cursor:pointer;-webkit-user-select:none;user-select:none;align-items:center;gap:4px;margin:0;font-size:13px;display:flex}#fullscreen-btn,#screenshot-btn{color:#fff;cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;height:auto;font-size:16px;display:flex}#fullscreen-btn{padding:0 4px}#screenshot-btn{padding:0 4px 6px}#fullscreen-btn:hover,#screenshot-btn:hover{color:#0af;background:0 0}#filename-input{color:#fff;background:#333;border:1px solid #555;border-radius:4px;outline:none;width:160px;padding:4px 8px;font-family:monospace}#filename-input:focus{border-color:#0af}#save-btn{color:#fff;cursor:pointer;background:#0af;border:none;border-radius:4px;padding:4px 12px;font-weight:700}#save-btn:hover:not(:disabled){background:#08c}#save-btn:disabled{opacity:.7;cursor:not-allowed}#open-editor-btn{color:#fff;cursor:pointer;background:#6f42c1;border:none;border-radius:4px;padding:4px 12px;font-weight:700}#open-editor-btn:hover:not(:disabled){background:#5a32a3}#open-editor-btn:disabled{opacity:.7;cursor:not-allowed}#anim-controls{z-index:10;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#0009;border-radius:8px;flex-wrap:wrap;justify-content:center;align-items:center;gap:10px;width:90%;max-width:500px;padding:8px 16px;display:flex;position:absolute;bottom:20px;left:50%;transform:translate(-50%)}#anim-controls select{color:#fff;text-overflow:ellipsis;background:#333;border:1px solid #555;border-radius:4px;outline:none;max-width:150px;padding:4px;font-size:13px;overflow:hidden}#anim-controls button{color:#fff;cursor:pointer;white-space:nowrap;background:#444;border:none;border-radius:4px;padding:4px 12px;font-size:13px}#anim-controls button:hover{background:#555}#anim-slider{cursor:pointer;-webkit-appearance:none;background:0 0;flex-grow:1;min-width:80px;margin:0}#anim-slider:focus{outline:none}#anim-slider::-webkit-slider-runnable-track{cursor:pointer;background:#444;border-radius:3px;width:100%;height:6px}#anim-slider::-webkit-slider-thumb{cursor:pointer;-webkit-appearance:none;background:#0af;border-radius:50%;width:14px;height:14px;margin-top:-4px}
@@ -27,6 +27,7 @@ Important Geometry rules:
27
27
  Important Animation rules:
28
28
  - Wrapping: Use the 'armature(animations=...)' module at the root to wrap all animated components.
29
29
  - Hierarchies: Use the 'bone(name="BoneName", t=[x,y,z], r=[x,y,z])' module to define hierarchical animated parts.
30
+ - Camera Animation: You can create an animated glTF camera by naming a bone "Camera" or "camera". You can append numbers or separators to the name (e.g., "Camera1", "Camera_fov60" for Perspective 60 deg FOV, or "Camera_ortho_mag10" for Orthographic, scale 10). Physical parts like "CameraMount" are safely ignored. It will animate just like any other bone! Note: The camera looks forward along the +Y axis, with +Z as its UP direction.
30
31
  - Auto-Unioning: Any child meshes (e.g., cube, cylinder, imported objects) placed directly inside an 'armature()' or 'bone()' node are automatically unioned together by the engine and rigidly attached to that bone's transform node. Child bones remain separate nodes in the hierarchy.
31
32
  - Animation Data: The 'animations' property is an array of named animation sequences. Each sequence contains an array of tracks defining keyframes for each bone. Format:
32
33
  animations = [
@@ -1,4 +1,4 @@
1
- import{i as e,n as t,r as n,t as r}from"./assets/prompt-ui-B9QCNLfJ.js";console.log(`🚀 SCAD Preview Extension loaded!`),window.addEventListener(`message`,e=>{if(e.data){if(e.data.type===`CLOSE_PREVIEW`){let e=document.getElementById(`scad-preview-iframe`);e&&e.remove()}else e.data.type===`ADD_IMAGE_TO_CHAT`&&i(e.data.dataUrl)}});async function i(e){try{let t=await(await fetch(e)).blob(),n=new File([t],`preview.png`,{type:`image/png`}),r=document.querySelector(`ms-prompt-box textarea[formcontrolname="promptText"]`)||document.querySelector(`ms-prompt-box textarea`)||document.querySelector(`textarea`);if(r){r.focus();let e=new DataTransfer;e.items.add(n);let t=new ClipboardEvent(`paste`,{clipboardData:e,bubbles:!0,cancelable:!0});r.dispatchEvent(t)}else console.warn(`Could not find chat input to paste the image.`)}catch(e){console.error(`Error adding image to chat:`,e)}}new MutationObserver(()=>{a(),o(),document.querySelectorAll(`ms-code-block[data-test-language="openscad" i], ms-code-block[data-test-language="scad" i]`).forEach(e=>{if(e.querySelector(`.scad-preview-btn`))return;let t=document.createElement(`button`);t.className=`scad-preview-btn`,t.title=`Preview 3D`,t.setAttribute(`aria-label`,`Preview 3D`),t.innerHTML=`
1
+ import{i as e,n as t,r as n,t as r}from"./assets/prompt-ui-BjZlMzjn.js";console.log(`🚀 SCAD Preview Extension loaded!`),window.addEventListener(`message`,e=>{if(e.data){if(e.data.type===`CLOSE_PREVIEW`){let e=document.getElementById(`scad-preview-iframe`);e&&e.remove()}else e.data.type===`ADD_IMAGE_TO_CHAT`&&i(e.data.dataUrl)}});async function i(e){try{let t=await(await fetch(e)).blob(),n=new File([t],`preview.png`,{type:`image/png`}),r=document.querySelector(`ms-prompt-box textarea[formcontrolname="promptText"]`)||document.querySelector(`ms-prompt-box textarea`)||document.querySelector(`textarea`);if(r){r.focus();let e=new DataTransfer;e.items.add(n);let t=new ClipboardEvent(`paste`,{clipboardData:e,bubbles:!0,cancelable:!0});r.dispatchEvent(t)}else console.warn(`Could not find chat input to paste the image.`)}catch(e){console.error(`Error adding image to chat:`,e)}}new MutationObserver(()=>{a(),o(),document.querySelectorAll(`ms-code-block[data-test-language="openscad" i], ms-code-block[data-test-language="scad" i]`).forEach(e=>{if(e.querySelector(`.scad-preview-btn`))return;let t=document.createElement(`button`);t.className=`scad-preview-btn`,t.title=`Preview 3D`,t.setAttribute(`aria-label`,`Preview 3D`),t.innerHTML=`
2
2
  <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
3
3
  <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
4
4
  <polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
@@ -44,10 +44,10 @@
44
44
  content="https://iliagrigorevdev.github.io/scad-gltf/icon.png"
45
45
  />
46
46
 
47
- <script type="module" crossorigin src="./assets/index-CgVih1C4.js"></script>
48
- <link rel="modulepreload" crossorigin href="./assets/OutputPass-B6MrFnGE.js">
49
- <link rel="modulepreload" crossorigin href="./assets/prompt-ui-B9QCNLfJ.js">
50
- <link rel="stylesheet" crossorigin href="./assets/index-B1KAb97r.css">
47
+ <script type="module" crossorigin src="./assets/index-D-FSzMhd.js"></script>
48
+ <link rel="modulepreload" crossorigin href="./assets/OutputPass-C3DZe46t.js">
49
+ <link rel="modulepreload" crossorigin href="./assets/prompt-ui-BjZlMzjn.js">
50
+ <link rel="stylesheet" crossorigin href="./assets/index-D8In1wnN.css">
51
51
  <link rel="manifest" href="./manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="./registerSW.js"></script></head>
52
52
  <body>
53
53
  <div id="app-container">
@@ -103,14 +103,15 @@
103
103
  id="anim-controls-section"
104
104
  style="display: none"
105
105
  >
106
- <h2>3. Animation Controls</h2>
106
+ <h2>3. Animation & Camera</h2>
107
107
  <div class="controls-row">
108
108
  <button id="anim-play-btn" class="primary" style="width: 90px">
109
109
  ⏸ Pause
110
110
  </button>
111
111
  <select id="anim-select"></select>
112
+ <select id="camera-select" style="display: none"></select>
112
113
  </div>
113
- <div class="controls-row">
114
+ <div class="controls-row" id="anim-slider-row">
114
115
  <input
115
116
  type="range"
116
117
  id="anim-slider"
@@ -2,28 +2,33 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <script type="module" crossorigin src="./assets/preview-BbUpKLF4.js"></script>
6
- <link rel="modulepreload" crossorigin href="./assets/OutputPass-B6MrFnGE.js">
7
- <link rel="stylesheet" crossorigin href="./assets/preview-A6M04CFK.css">
5
+ <script type="module" crossorigin src="./assets/preview-Br3_yFrj.js"></script>
6
+ <link rel="modulepreload" crossorigin href="./assets/OutputPass-C3DZe46t.js">
7
+ <link rel="stylesheet" crossorigin href="./assets/preview-Cx3FHqKG.css">
8
8
  <link rel="manifest" href="./manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="./registerSW.js"></script></head>
9
9
  <body>
10
10
  <div id="viewer-container">
11
- <div id="viewer-controls">
12
- <label><input type="checkbox" id="show-grid-cb" checked /> Grid</label>
13
- <label><input type="checkbox" id="wireframe-cb" /> Wireframe</label>
14
- <button id="screenshot-btn" title="Take Screenshot & Add to Chat">
15
- 📷
16
- </button>
17
- <button id="fullscreen-btn" title="Toggle Full Screen">⛶</button>
18
- </div>
19
- <div id="save-ui">
20
- <input type="text" id="filename-input" placeholder="Name" />
21
- <button id="save-btn">Save</button>
22
- <button id="open-editor-btn">Edit</button>
11
+ <div id="top-ui">
12
+ <div id="save-ui">
13
+ <input type="text" id="filename-input" placeholder="Name" />
14
+ <button id="save-btn">Save</button>
15
+ <button id="open-editor-btn">Edit</button>
16
+ </div>
17
+ <div id="viewer-controls">
18
+ <label
19
+ ><input type="checkbox" id="show-grid-cb" checked /> Grid</label
20
+ >
21
+ <label><input type="checkbox" id="wireframe-cb" /> Wireframe</label>
22
+ <button id="screenshot-btn" title="Take Screenshot & Add to Chat">
23
+ 📷
24
+ </button>
25
+ <button id="fullscreen-btn" title="Toggle Full Screen">⛶</button>
26
+ </div>
23
27
  </div>
24
28
  <div id="anim-controls" style="display: none">
25
29
  <button id="anim-play-btn">⏸ Pause</button>
26
30
  <select id="anim-select"></select>
31
+ <select id="camera-select" style="display: none"></select>
27
32
  <input
28
33
  type="range"
29
34
  id="anim-slider"
package/editor/dist/sw.js CHANGED
@@ -1 +1 @@
1
- if(!self.define){let e,s={};const i=(i,r)=>(i=new URL(i+".js",r).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(r,n)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let l={};const o=e=>i(e,t),c={module:{uri:t},exports:l,require:o};s[t]=Promise.all(r.map(e=>c[e]||o(e))).then(e=>(n(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"402b66900e731ca748771b6fc5e7a068"},{url:"preview.html",revision:"0cb1d1cf5e8358e608927e2f3efc3b14"},{url:"index.html",revision:"e09c648877bb53e1fc65cfebb5a020af"},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"content.js",revision:"a0ee0962c4c041063d42d1a33eb0b294"},{url:"content.css",revision:"4ea93a2d03164b49c80a48138305e564"},{url:"content-loader.js",revision:"d94f1755552d49c1fe3a6da123e8a346"},{url:"aristea_wreck_puresky_2k.hdr",revision:"e764c66f871ab0987f3fac422edc841d"},{url:"assets/prompt-ui-B9QCNLfJ.js",revision:null},{url:"assets/preview-BbUpKLF4.js",revision:null},{url:"assets/preview-A6M04CFK.css",revision:null},{url:"assets/openscad-CdBCY4mx.wasm",revision:null},{url:"assets/index-CgVih1C4.js",revision:null},{url:"assets/index-B1KAb97r.css",revision:null},{url:"assets/OutputPass-B6MrFnGE.js",revision:null},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"manifest.webmanifest",revision:"705049c691faa532cc20c736d56d54df"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
1
+ if(!self.define){let e,s={};const i=(i,r)=>(i=new URL(i+".js",r).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(r,n)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let l={};const o=e=>i(e,t),c={module:{uri:t},exports:l,require:o};s[t]=Promise.all(r.map(e=>c[e]||o(e))).then(e=>(n(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"402b66900e731ca748771b6fc5e7a068"},{url:"preview.html",revision:"b427de9da7f99ca8b90ef8a999fe1c50"},{url:"index.html",revision:"6a6a36f54aef626681e37ecfa3d55535"},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"content.js",revision:"50c6b501e23043613af896c59d6ced31"},{url:"content.css",revision:"4ea93a2d03164b49c80a48138305e564"},{url:"content-loader.js",revision:"d94f1755552d49c1fe3a6da123e8a346"},{url:"aristea_wreck_puresky_2k.hdr",revision:"e764c66f871ab0987f3fac422edc841d"},{url:"assets/prompt-ui-BjZlMzjn.js",revision:null},{url:"assets/preview-Cx3FHqKG.css",revision:null},{url:"assets/preview-Br3_yFrj.js",revision:null},{url:"assets/openscad-DiJCzDqJ.wasm",revision:null},{url:"assets/index-D8In1wnN.css",revision:null},{url:"assets/index-D-FSzMhd.js",revision:null},{url:"assets/OutputPass-C3DZe46t.js",revision:null},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"manifest.webmanifest",revision:"705049c691faa532cc20c736d56d54df"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scad-gltf",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "WASM build of a custom OpenSCAD fork that compiles .scad scripts directly to glTF/GLB with PBR and animation support",
5
5
  "type": "module",
6
6
  "main": "./src/ext/openscad.js",
package/src/cli-utils.js CHANGED
@@ -670,11 +670,11 @@ export async function runCliApp({
670
670
  console.error("Examples with JSON options (Automated AI flow):");
671
671
  if (isRawScad) {
672
672
  console.error(
673
- ` scad-gltf gen ${projectType} "A modular sci-fi corridor piece" '{"openaiApiKey": "sk-...", "openaiModel": "gpt-4o"}'`,
673
+ ` scad-gltf gen ${projectType} "A modular sci-fi corridor piece" '{"openaiApiKey": "sk-...", "openaiModel": "gpt-4o", "scadFiles": ["./base.scad"]}'`,
674
674
  );
675
675
  } else {
676
676
  console.error(
677
- ` scad-gltf gen ${projectType} "A simple 3D simulation" '{"openaiApiKey": "sk-...", "openaiModel": "gpt-4o"}'`,
677
+ ` scad-gltf gen ${projectType} "A simple 3D simulation" '{"openaiApiKey": "sk-...", "openaiModel": "gpt-4o", "scadFiles": ["./character.scad"]}'`,
678
678
  );
679
679
  }
680
680
  process.exit(1);