scad-gltf 0.3.2 → 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.
- package/README.md +31 -4
- package/bin/gen.js +2 -2
- package/bin/mcp.js +2 -2
- package/bin/play.js +8 -8
- package/bin/scad-gltf.js +1 -1
- package/editor/dist/assets/{OutputPass-B6MrFnGE.js → OutputPass-C3DZe46t.js} +2 -2
- package/editor/dist/assets/{index-CgVih1C4.js → index-D-FSzMhd.js} +76 -76
- package/editor/dist/assets/{index-B1KAb97r.css → index-D8In1wnN.css} +1 -1
- package/editor/dist/assets/{openscad-CdBCY4mx.wasm → openscad-DiJCzDqJ.wasm} +0 -0
- package/editor/dist/assets/preview-Br3_yFrj.js +1 -0
- package/editor/dist/assets/preview-Cx3FHqKG.css +1 -0
- package/editor/dist/assets/{prompt-ui-B9QCNLfJ.js → prompt-ui-BjZlMzjn.js} +1 -0
- package/editor/dist/content.js +1 -1
- package/editor/dist/index.html +7 -6
- package/editor/dist/preview.html +20 -15
- package/editor/dist/sw.js +1 -1
- package/package.json +1 -1
- package/src/cli-utils.js +11 -7
- package/src/ext/openscad.js +1 -1
- package/src/ext/openscad.wasm +0 -0
- package/src/project-prompts.js +94 -53
- package/src/prompt.js +1 -0
- package/editor/dist/assets/preview-A6M04CFK.css +0 -1
- package/editor/dist/assets/preview-BbUpKLF4.js +0 -1
|
@@ -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}
|
|
Binary file
|
|
@@ -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 = [
|
package/editor/dist/content.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{i as e,n as t,r as n,t as r}from"./assets/prompt-ui-
|
|
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>
|
package/editor/dist/index.html
CHANGED
|
@@ -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-
|
|
48
|
-
<link rel="modulepreload" crossorigin href="./assets/OutputPass-
|
|
49
|
-
<link rel="modulepreload" crossorigin href="./assets/prompt-ui-
|
|
50
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
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
|
|
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"
|
package/editor/dist/preview.html
CHANGED
|
@@ -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-
|
|
6
|
-
<link rel="modulepreload" crossorigin href="./assets/OutputPass-
|
|
7
|
-
<link rel="stylesheet" crossorigin href="./assets/preview-
|
|
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="
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
</
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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:"
|
|
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
package/src/cli-utils.js
CHANGED
|
@@ -495,8 +495,11 @@ export async function runAutomatedAIFlow(
|
|
|
495
495
|
const isBevy =
|
|
496
496
|
projectType === "bevy" &&
|
|
497
497
|
fs.existsSync(path.join(itemPath, "Cargo.toml"));
|
|
498
|
+
const isWgpu =
|
|
499
|
+
projectType === "wgpu" &&
|
|
500
|
+
fs.existsSync(path.join(itemPath, "Cargo.toml"));
|
|
498
501
|
|
|
499
|
-
if (isGodot || isWeb || isBevy) {
|
|
502
|
+
if (isGodot || isWeb || isBevy || isWgpu) {
|
|
500
503
|
const mtime = fs.statSync(itemPath).mtimeMs;
|
|
501
504
|
if (mtime > latestTime) {
|
|
502
505
|
latestTime = mtime;
|
|
@@ -511,7 +514,7 @@ export async function runAutomatedAIFlow(
|
|
|
511
514
|
const expectedFile =
|
|
512
515
|
projectType === "godot"
|
|
513
516
|
? "project.godot"
|
|
514
|
-
: projectType === "bevy"
|
|
517
|
+
: projectType === "bevy" || projectType === "wgpu"
|
|
515
518
|
? "Cargo.toml"
|
|
516
519
|
: "package.json";
|
|
517
520
|
messages.push({
|
|
@@ -554,9 +557,10 @@ export async function runAutomatedAIFlow(
|
|
|
554
557
|
});
|
|
555
558
|
|
|
556
559
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
557
|
-
} else if (projectType === "bevy") {
|
|
560
|
+
} else if (projectType === "bevy" || projectType === "wgpu") {
|
|
561
|
+
const fwName = projectType === "bevy" ? "Bevy" : "wgpu";
|
|
558
562
|
console.log(
|
|
559
|
-
`\n🦀 Building and running
|
|
563
|
+
`\n🦀 Building and running ${fwName} project: ${path.basename(projectDir)}...`,
|
|
560
564
|
);
|
|
561
565
|
const cargoCmd =
|
|
562
566
|
process.platform === "win32" ? "cargo.exe" : "cargo";
|
|
@@ -666,11 +670,11 @@ export async function runCliApp({
|
|
|
666
670
|
console.error("Examples with JSON options (Automated AI flow):");
|
|
667
671
|
if (isRawScad) {
|
|
668
672
|
console.error(
|
|
669
|
-
` 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"]}'`,
|
|
670
674
|
);
|
|
671
675
|
} else {
|
|
672
676
|
console.error(
|
|
673
|
-
` scad-gltf gen ${projectType} "A simple 3D
|
|
677
|
+
` scad-gltf gen ${projectType} "A simple 3D simulation" '{"openaiApiKey": "sk-...", "openaiModel": "gpt-4o", "scadFiles": ["./character.scad"]}'`,
|
|
674
678
|
);
|
|
675
679
|
}
|
|
676
680
|
process.exit(1);
|
|
@@ -704,7 +708,7 @@ export async function runCliApp({
|
|
|
704
708
|
try {
|
|
705
709
|
const { generatePrompt } = await import("./prompt.js");
|
|
706
710
|
promptRules = generatePrompt(
|
|
707
|
-
isRawScad ? task : "the 3D assets for the
|
|
711
|
+
isRawScad ? task : "the 3D assets for the project",
|
|
708
712
|
options,
|
|
709
713
|
);
|
|
710
714
|
} catch (e) {
|