scad-gltf 0.1.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.
Files changed (37) hide show
  1. package/README.md +489 -0
  2. package/bin/scad-convert.js +221 -0
  3. package/bin/scad-godot.js +247 -0
  4. package/bin/scad-mcp.js +508 -0
  5. package/bin/scad-serve.js +225 -0
  6. package/bin/scad-web.js +206 -0
  7. package/editor/dist/aristea_wreck_puresky_2k.hdr +0 -0
  8. package/editor/dist/assets/OutputPass-Bvl6NigM.js +4317 -0
  9. package/editor/dist/assets/index-V9cEH4KX.js +4318 -0
  10. package/editor/dist/assets/index-t9MYrExo.css +1 -0
  11. package/editor/dist/assets/openscad-CdBCY4mx.wasm +0 -0
  12. package/editor/dist/assets/preview-C1zc24MJ.js +1 -0
  13. package/editor/dist/assets/preview-fQfL_FJ-.css +1 -0
  14. package/editor/dist/assets/prompt-ui-8EFRz0ju.js +126 -0
  15. package/editor/dist/content-loader.js +4 -0
  16. package/editor/dist/content.css +255 -0
  17. package/editor/dist/content.js +24 -0
  18. package/editor/dist/icon.png +0 -0
  19. package/editor/dist/index.html +187 -0
  20. package/editor/dist/manifest.json +23 -0
  21. package/editor/dist/manifest.webmanifest +1 -0
  22. package/editor/dist/preview.html +27 -0
  23. package/editor/dist/registerSW.js +1 -0
  24. package/editor/dist/sw.js +1 -0
  25. package/editor/dist/workbox-9c191d2f.js +1 -0
  26. package/godot/README.md +64 -0
  27. package/godot/addons/scad_importer/plugin.cfg +7 -0
  28. package/godot/addons/scad_importer/scad_importer.gd +197 -0
  29. package/godot/addons/scad_importer/scad_plugin.gd +12 -0
  30. package/godot/examples/README.md +82 -0
  31. package/godot/examples/fruit_fusion_3d.js +1574 -0
  32. package/godot/examples/package.json +5 -0
  33. package/package.json +63 -0
  34. package/src/convert.js +94 -0
  35. package/src/ext/openscad.js +14 -0
  36. package/src/ext/openscad.wasm +0 -0
  37. package/src/prompt.js +229 -0
@@ -0,0 +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{color:#fff;cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;height:auto;padding:0 4px;font-size:16px;display:flex}#fullscreen-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 l,f as u,ht as d,i as te,l as ne,lt as re,mt as ie,n as ae,o as f,p,r as m,s as h,t as g,u as _,ut as v,w as oe,y as se}from"./OutputPass-Bvl6NigM.js";var y=document.getElementById(`viewer-container`),b=document.getElementById(`viewer`),x=document.getElementById(`filename-input`),S=document.getElementById(`save-btn`),C=document.getElementById(`open-editor-btn`),w=document.getElementById(`show-grid-cb`),T=document.getElementById(`wireframe-cb`),E=document.getElementById(`fullscreen-btn`),D=document.getElementById(`screenshot-btn`),O=null,k=[],A=``,j=!1,M=null,N=null,P=!1,F=new l;F.background=new se(2236962);var I=new i(60,window.innerWidth/window.innerHeight,.1,2e3);I.position.set(50,50,-50);var L=new _({antialias:!0});L.setSize(window.innerWidth,window.innerHeight),L.setPixelRatio(Math.min(window.devicePixelRatio,2)),L.shadowMap.enabled=!0,L.shadowMap.type=2,L.toneMapping=4,L.toneMappingExposure=1,b.appendChild(L.domElement);var R=new te(L),z=new m(F,I);R.addPass(z);var B=new ae(new re(window.innerWidth,window.innerHeight),.1,.1,2);R.addPass(B);var V=new g;R.addPass(V);var H=new h(I,L.domElement);H.enableDamping=!0,H.dampingFactor=.1,F.environment=new ne(L).fromScene(new ee,.04).texture,F.environmentIntensity=.8;var U=new a;F.add(U);var W=new s(2e3,2e3),G=new t({color:2236962,roughness:.8,metalness:.1}),K=new o(W,G);K.rotation.x=-Math.PI/2,K.receiveShadow=!0,F.add(K);var q=new n(2e3,100,5592405,3355443);q.material.transparent=!0,q.material.opacity=.5,F.add(q);var J=new u(100);F.add(J);var Y=new oe(16777215,1.5);Y.castShadow=!0,Y.shadow.mapSize.width=2048,Y.shadow.mapSize.height=2048,Y.shadow.bias=-5e-4,U.add(Y),U.add(Y.target);var X=new e(16777215,4473924,.6);X.position.set(0,20,0),U.add(X),T&&T.addEventListener(`change`,()=>{let e=T.checked;O&&O.traverse(t=>{t.isMesh&&t.material&&(Array.isArray(t.material)?t.material.forEach(t=>{t.wireframe=e}):t.material.wireframe=e)})}),E&&y&&(E.addEventListener(`click`,()=>{document.fullscreenElement?document.exitFullscreen().catch(e=>{console.warn(`Error attempting to exit fullscreen: ${e.message}`)}):y.requestFullscreen().catch(e=>{console.warn(`Error attempting to enable fullscreen: ${e.message}`)})}),document.addEventListener(`fullscreenchange`,()=>{setTimeout(()=>window.dispatchEvent(new Event(`resize`)),100)})),D&&D.addEventListener(`click`,()=>{P=!0;let e=D.innerText;D.innerText=`✅`,setTimeout(()=>{D.innerText=e},1e3)});var Z=performance.now();function Q(){requestAnimationFrame(Q);let e=performance.now(),t=(e-Z)/1e3;Z=e,N&&N.update(t),H.update();let n=!w||w.checked;if(q.visible=n,J.visible=n,R.render(),P){P=!1;let e=L.domElement.toDataURL(`image/png`);window.parent.postMessage({type:`ADD_IMAGE_TO_CHAT`,dataUrl:e},`*`)}}Q(),window.addEventListener(`resize`,()=>{let e=y?y.clientWidth:window.innerWidth,t=y?y.clientHeight:window.innerHeight;I.aspect=e/t,I.updateProjectionMatrix(),L.setSize(e,t),R.setSize(e,t)});function ce(e,t){if(!e)return new p;e.updateMatrixWorld(!0);let n=new p().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 le(e){return new Promise((t,n)=>{O&&=(N&&=(N.stopAllAction(),N.uncacheRoot(N.getRoot()),null),k=[],F.remove(O),O.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 f().parse(r,``,e=>{O=e.scene,k=e.animations||[];let n=T?T.checked:!1;k.length>0&&(N=new c(O),k.forEach(e=>{let t=N.clipAction(e);t.play(),t.time=0}),N.update(0)),O.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))}),F.add(O),ue(),t()},e=>{console.error(e),n(e)})})}function ue(){if(!O)return;let e=ce(O,k);if(e.isEmpty())return;let t=e.getCenter(new v),n=e.getSize(new v),i=Math.max(n.x,n.y,n.z)||10;K.position.y=e.min.y-.01,q.position.y=K.position.y+.001,J.position.y=K.position.y+.002;let a=I.fov*(Math.PI/180),o=i/(2*Math.tan(a/2));I.aspect<1&&(o/=I.aspect),o*=1.5,I.near=Math.max(.01,i*.01),I.far=Math.max(2e3,o*10),I.updateProjectionMatrix(),H.maxDistance=I.far,I.position.set(t.x+o*.8,t.y+o*.8,t.z-o*.8),I.lookAt(t),H.target.copy(t),H.update(),Y.position.set(t.x+i,t.y+i*1.5,t.z-i),Y.target.position.copy(t),Y.target.updateMatrixWorld();let s=i*1.5;Y.shadow.camera.left=-s,Y.shadow.camera.right=s,Y.shadow.camera.top=s,Y.shadow.camera.bottom=-s,Y.shadow.camera.near=.1,Y.shadow.camera.far=i*5,Y.shadow.camera.updateProjectionMatrix(),F.fog=new r(2236962,o*1.5,o*5)}async function de(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 $(e){if(j){M=e;return}j=!0;try{let t=await de(e);await le(await ie(e,{wasmUrl:d,additionalFiles:t}))}catch(e){console.error(e)}finally{if(j=!1,M!==null){let e=M;M=null,$(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&&(x.value=e)}await $(e.data.code)}}),S.addEventListener(`click`,async()=>{let e=x.value.trim();if(!e){alert(`Please enter a filename.`),x.focus();return}let t=`http://localhost:3000`;try{S.innerText=`Checking...`,S.disabled=!0;let n=await fetch(`${t}/api/scads`);if(!n.ok)throw Error(`Could not reach scad-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?`)){S.innerText=`Save`,S.disabled=!1;return}S.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.`)}S.innerText=`✅ Saved!`,setTimeout(()=>{S.innerText=`Save`,S.disabled=!1},2e3)}catch(e){console.error(e),alert(`Error saving file: `+e.message),S.innerText=`Save`,S.disabled=!1}});async function fe(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(/=+$/,``)}C.addEventListener(`click`,async()=>{if(!A){alert(`No model loaded to open.`);return}try{C.disabled=!0,C.innerText=`Preparing...`;let e=`https://iliagrigorevdev.github.io/scad-gltf/#${await fe(A)}`;window.open(e,`_blank`),window.parent.postMessage({type:`CLOSE_PREVIEW`},`*`),C.innerText=`✅ Opened!`,setTimeout(()=>{C.innerText=`Edit`,C.disabled=!1},2e3)}catch(e){console.error(`Error creating editor link`,e),alert(`Error creating editor link: `+e.message),C.innerText=`Edit`,C.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%}#viewer-controls{z-index:10;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#0009;border-radius:6px;align-items:center;gap:8px;padding:6px;display:flex;position:absolute;top:15px;right:40px}#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}#save-ui{z-index:10;background:#0009;border-radius:6px;gap:8px;padding:6px;display:flex;position:absolute;top:15px;left:15px}#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}
@@ -0,0 +1,126 @@
1
+ function e(e,t={}){if(!e)throw Error(`A description is required to generate a prompt.`);let n={basic:t.basic??!0,transmission:t.transmission??!0,clearcoat:t.clearcoat??!0,sheen:t.sheen??!0,emissive:t.emissive??!0,specular:t.specular??!0,iridescence:t.iridescence??!0,bakeColors:t.bakeColors??!1,bakeNormals:t.bakeNormals??!1,bakeOrm:t.bakeOrm??!1,bakeUvs:t.bakeUvs??!1,autoSmoothAngle:t.autoSmoothAngle??!0,animation:t.animation??!0,lazyUnion:t.lazyUnion??!1,modelName:t.modelName??!0},r=`Generate an OpenSCAD script to design the following: ${e}.`;n.modelName&&(r+=`
2
+
3
+ Important Output Rules:
4
+ - You MUST wrap your code in a standard Markdown code block using the \`\`\`openscad language tag.
5
+ - Inside the code block, on the first line, include a block comment with a concise filename in snake_case (lowercase and underscores).
6
+ - Use this exact format: /* Model Name: your_model_name_here */`);let i=[];if(n.basic&&i.push(`'roughness'`,`'metalness'`),n.clearcoat&&i.push(`'clearcoat'`,`'clearcoatRoughness'`),n.sheen&&i.push(`'sheen'`,`'sheenColor'`,`'sheenRoughness'`),n.transmission&&i.push(`'transmission'`,`'thickness'`,`'attenuationColor'`,`'attenuationDistance'`,`'ior'`),n.emissive&&i.push(`'emissive'`,`'emissiveIntensity'`),n.specular&&i.push(`'specularColor'`,`'specularIntensity'`),n.iridescence&&i.push(`'iridescence'`,`'iridescenceIOR'`),i.length>0||n.autoSmoothAngle){i.length>0&&(r+=`\n\nPlease utilize extended color attributes, specifically including ${i.join(`, `)} parameters.`),r+=`
7
+
8
+ Important PBR & Shading rules:`,n.basic&&(r+=`
9
+ - Metalness: For solid metallic materials (e.g., gold, steel), use metalness near 1.0. High metalness blocks light transmission. (Default: 0.0)`,r+=`
10
+ - Roughness: Controls surface finish. 0.0 is perfectly smooth/glossy, while 1.0 is completely matte. (Default: 1.0)`),n.transmission&&(r+=`
11
+ - Transmission: Degree of optical transparency (0.0 to 1.0) for materials like glass or water. Note: When transmission is non-zero, alpha (opacity) should be set to 1.0. (Default: 0.0)`,r+=`
12
+ - Thickness: The thickness of the volume beneath the surface. If 0.0, the material is thin-walled (like a bubble). If > 0, it acts as a solid volume boundary (like a block of glass). (Default: 0.0)`,r+=`
13
+ - Attenuation Color & Distance: Used with transmission and thickness to simulate volume absorption (colored glass or liquids). Distance is how far light travels to reach the attenuationColor. (Defaults: [1.0, 1.0, 1.0] and 0.0)`,r+=`
14
+ - IOR (Index of Refraction): Controls how much light bends when entering a transmissive or clearcoat material. Water is ~1.33, Window Glass ~1.5, Diamond ~2.4. (Default: 1.5)`),n.clearcoat&&(r+=`
15
+ - Clearcoat: Adds a clear, reflective layer on top of the base material (car paint, varnished wood, or wet surfaces). 1.0 is fully coated. (Default: 0.0)`,r+=`
16
+ - Clearcoat Roughness: Controls the smoothness of the clearcoat layer. (Default: 0.0)`),n.sheen&&(r+=`
17
+ - Sheen: Simulates backscattering from microfibers, creating a soft velvet-like rim light useful for cloth and fabrics. 1.0 is full intensity. (Default: 0.0)`,r+=`
18
+ - Sheen Color: Sets the RGB tint of the sheen layer (e.g., sheenColor = [1.0, 0.5, 0.5]). (Default: [0.0, 0.0, 0.0])`,r+=`
19
+ - Sheen Roughness: Controls the roughness of the sheen layer. (Default: 0.0)`),n.emissive&&(r+=`
20
+ - Emissive & Emissive Intensity: Makes the material glow. Emissive is an RGB color vector, intensity is a float multiplier. (Defaults: [0.0, 0.0, 0.0] and 1.0)`),n.specular&&(r+=`
21
+ - Specular Color & Intensity: Overrides the default specular reflection. (Defaults: [1.0, 1.0, 1.0] and 1.0)`),n.iridescence&&(r+=`
22
+ - Iridescence & Iridescence IOR: Simulates thin-film interference like soap bubbles, oil spills, or pearlescent surfaces. (Defaults: 0.0 and 1.3)`),n.autoSmoothAngle&&(r+=`
23
+ - Auto Smooth Angle: Generates smooth vertex normals for adjoining faces with an angle difference less than this value (in degrees). Use > 0 (e.g., 30 or 45) for curved/smooth surfaces, 0.0 for flat shading. Can be set globally using the special variable $asa (e.g., $asa=30;), or overridden per-material via the $asa parameter INSIDE the color() module. IMPORTANT: $asa ONLY affects surface shading (normals). It DOES NOT alter the actual geometry or polygon count. You must still use standard variables like $fn to increase geometric resolution. DO NOT pass $asa directly to geometry modules like sphere() or cylinder(). (Default: 0.0)`);let e=[];n.basic&&e.push(`metalness=1.0`,`roughness=0.3`),n.transmission&&e.push(`transmission=0.8`,`ior=1.5`),n.clearcoat&&e.push(`clearcoat=1.0`),n.sheen&&e.push(`sheen=1.0`),n.iridescence&&e.push(`iridescence=1.0`),n.emissive&&e.push(`emissive=[0.0, 0.5, 1.0]`,`emissiveIntensity=2.0`),n.specular&&e.push(`specularIntensity=1.0`),n.autoSmoothAngle&&e.push(`$asa=45.0`);let t=e.length>0?`, `+e.join(`, `):``;r+=`\n\nExample Material Usage:\n// Syntax: color(c=color_value, alpha=1.0, [named PBR parameters...])\ncolor([0.2, 0.2, 0.2], alpha=1.0${t})\n cube([10, 10, 10]);`}if(n.lazyUnion&&(r+=`
24
+
25
+ Important Geometry rules:
26
+ - The compiler runs with "lazy-union" enabled. This means top-level objects, module children, and items inside loops ('for') or conditionals ('if') are NOT implicitly boolean-unioned together. They are evaluated and exported as separate discrete meshes.`),n.animation&&(r+=`
27
+
28
+ Important Animation rules:
29
+ - Wrapping: Use the 'armature(animations=...)' module at the root to wrap all animated components.
30
+ - Hierarchies: Use the 'bone(name="BoneName", t=[x,y,z], r=[x,y,z])' module to define hierarchical animated parts.
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. Child bones remain separate nodes in the hierarchy.
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:
33
+ animations = [
34
+ ["AnimationName", [
35
+ ["BoneName", [
36
+ [time_in_seconds, [rot_x, rot_y, rot_z], [trans_x, trans_y, trans_z]], // Translation is optional
37
+ [1.0, [0, 90, 0], [0, 5, 0]],
38
+ ...
39
+ ]]
40
+ ]]
41
+ ];
42
+ - Rotation Keyframes: Due to glTF Quaternion shortest-path interpolation, NEVER rotate more than 90 degrees between consecutive keyframes. To perform a full 360-degree rotation, you MUST manually subdivide it into 90-degree increments (e.g., 0, 90, 180, 270, 360).
43
+ - Translational & Rotational Keyframes: Keyframe translations and rotations are ABSOLUTE in local space. They completely replace the bone's resting 't' and 'r' attributes during the animation. If a bone's resting translation is [0, 0, 2] and it needs to move 10 units up, the keyframe translation must be [0, 0, 12]. If translation is omitted, it defaults to the resting position.
44
+
45
+ Example Animation Usage:
46
+ anim_data = [
47
+ ["Action 1", [
48
+ ["BaseSpinner", [
49
+ [0.0, [0, 0, 0]],
50
+ [1.0, [0, 0, 90]],
51
+ [2.0, [0, 0, 180]],
52
+ [3.0, [0, 0, 270]],
53
+ [4.0, [0, 0, 360]]
54
+ ]],
55
+ ["ChildSlider", [
56
+ [0.0, [0, 0, 0], [0, 0, 2]],
57
+ [2.0, [0, 0, 0], [0, 0, 12]],
58
+ [4.0, [0, 0, 0], [0, 0, 2]]
59
+ ]]
60
+ ]]
61
+ ];
62
+
63
+ armature(animations=anim_data) {
64
+ // Root bone
65
+ bone(name="BaseSpinner", t=[0, 0, 0], r=[0, 0, 0]) {
66
+ // Mesh attached to BaseSpinner
67
+ color([0.2, 0.5, 0.8]) cube([10, 10, 2], center=true);
68
+
69
+ // Nested child bone (inherits parent's transform)
70
+ bone(name="ChildSlider", t=[0, 0, 2], r=[0, 0, 0]) {
71
+ // Mesh attached to ChildSlider
72
+ color([0.8, 0.2, 0.2]) cylinder(h=5, r=2);
73
+ }
74
+ }
75
+ }`),n.bakeColors||n.bakeNormals||n.bakeOrm||n.bakeUvs){let e=[],t=[];n.bakeColors&&(e.push(`colors=true`),t.push(`- Set 'colors=true' (default false) to project and bake the high-poly's solid colors onto the low-poly mesh.`)),n.bakeNormals&&(e.push(`normals=true`),t.push(`- Set 'normals=true' (default false) to project and bake the high-poly's physical geometric details as a tangent-space normal map onto the low-poly mesh.`)),n.bakeOrm&&(e.push(`orm=true`),t.push(`- Set 'orm=true' (default false) to project and bake the high-poly's Roughness, and Metallic values onto the low-poly mesh.`)),n.bakeUvs&&(e.push(`uvs=true`),t.push(`- Set 'uvs=true' (default false) when you only want to generate UV coordinates and Tangent vectors without baking any image textures. Note that UVs and Tangents are automatically generated whenever 'colors', 'normals', or 'orm' are enabled, so 'uvs=true' is only explicitly needed for textureless UV-only exports.`)),t.push(`- You can customize the baking process using 'distance' (max ray length, default: 2.0), 'bias' (ray origin offset, default: 1e-4), 'dilation' (pixel padding around UV islands, default: 2), 'resolution' (texture dimensions, default: 512), 'msaa' (super-sampling anti-aliasing level, default: 2), 'index' (atlas group identifier, default: 0), and 'rotate_uvs' (allow UV islands to be rotated for better packing, default: true).`),t.push(`- The 'index' parameter enables multi-atlas texture baking. Low-poly meshes configured with the same 'index' will be packed together into a shared texture atlas, while meshes with distinct indices will be split into separate output image maps.`),e.push(`resolution=1024`);let i=e.length>0?`bake(${e.join(`, `)})`:`bake()`,a=t.length>0?t.join(`
76
+ `):`- You can toggle what gets baked using the 'colors', 'normals', 'orm', and 'uvs' boolean parameters (all default to false).`;r+=`\n\nImportant Texture Baking rules:
77
+ - Baking: Use the 'bake()' module to project details from a high-resolution mesh onto a low-resolution mesh.
78
+ - UV Unwrapping: The engine automatically generates UV coordinates and bakes the textures for the low-poly child mesh; you do not need to manually map textures.
79
+ - Usage: The 'bake()' module strictly requires exactly TWO children. The FIRST child is the high-poly geometry, and the SECOND child is the low-poly geometry.
80
+ ${a}
81
+
82
+ Example Baking Usage:
83
+ // Bake the selected details of a high-resolution sphere onto a low-resolution one
84
+ ${i} {
85
+ color("white") sphere(r=10, $fn=100); // Child 1: High Poly
86
+ color("white", roughness=0.5, $asa=45) sphere(r=10, $fn=20); // Child 2: Low Poly
87
+ }
88
+
89
+ // Alternatively, generate UVs/Tangents for a mesh WITHOUT a high-poly source by providing only 1 child
90
+ bake(uvs=true) {
91
+ color("white") cube([10, 10, 10]);
92
+ }`}return r}var t=`
93
+ <div class="scad-prompt-toggles">
94
+ <label><input type="checkbox" id="opt-pbr-basic" checked /> Basic PBR</label>
95
+ <label><input type="checkbox" id="opt-pbr-autosmooth" checked /> Auto Smooth</label>
96
+ <label><input type="checkbox" id="opt-anim" checked /> Animations</label>
97
+ </div>
98
+
99
+ <div class="scad-prompt-group">
100
+ <div class="scad-prompt-group-header">
101
+ <span>Extended PBR</span>
102
+ <label><input type="checkbox" id="opt-pbr-all" checked /> Enable All</label>
103
+ </div>
104
+ <div class="scad-prompt-toggles nested" id="pbr-children">
105
+ <label><input type="checkbox" class="pbr-child" id="opt-pbr-transmission" checked /> Transmission</label>
106
+ <label><input type="checkbox" class="pbr-child" id="opt-pbr-clearcoat" checked /> Clearcoat</label>
107
+ <label><input type="checkbox" class="pbr-child" id="opt-pbr-sheen" checked /> Sheen</label>
108
+ <label><input type="checkbox" class="pbr-child" id="opt-pbr-emissive" checked /> Emissive</label>
109
+ <label><input type="checkbox" class="pbr-child" id="opt-pbr-specular" checked /> Specular</label>
110
+ <label><input type="checkbox" class="pbr-child" id="opt-pbr-iridescence" checked /> Iridescence</label>
111
+ </div>
112
+ </div>
113
+
114
+ <div class="scad-prompt-group">
115
+ <div class="scad-prompt-group-header">
116
+ <span>Baking</span>
117
+ <label><input type="checkbox" id="opt-bake-all" /> Enable All</label>
118
+ </div>
119
+ <div class="scad-prompt-toggles nested" id="bake-children">
120
+ <label><input type="checkbox" class="bake-child" id="opt-bake-colors" /> Bake Colors</label>
121
+ <label><input type="checkbox" class="bake-child" id="opt-bake-normals" /> Bake Normals</label>
122
+ <label><input type="checkbox" class="bake-child" id="opt-bake-orm" /> Bake ORM</label>
123
+ <label><input type="checkbox" class="bake-child" id="opt-bake-uvs" /> Bake UVs/Tangents</label>
124
+ </div>
125
+ </div>
126
+ `;function n(e,t=`scad_prompt_settings`){let n=()=>{let n={};e.querySelectorAll(`input[type="checkbox"]`).forEach(e=>{e.id&&(n[e.id]=e.checked)}),localStorage.setItem(t,JSON.stringify(n))},r=(t,r)=>{let i=e.querySelector(t),a=e.querySelectorAll(r),o=()=>{if(!i||a.length===0)return;let e=Array.from(a).every(e=>e.checked),t=Array.from(a).some(e=>e.checked);i.checked=e,i.indeterminate=t&&!e};return i&&a.length>0&&(i.addEventListener(`change`,e=>{let t=e.target.checked;a.forEach(e=>e.checked=t),n()}),a.forEach(e=>{e.addEventListener(`change`,()=>{o(),n()})})),o},i=r(`#opt-pbr-all`,`.pbr-child`),a=r(`#opt-bake-all`,`.bake-child`);try{let n=localStorage.getItem(t);if(n){let t=JSON.parse(n);e.querySelectorAll(`input[type="checkbox"]`).forEach(e=>{e.id&&t[e.id]!==void 0&&(e.checked=t[e.id])})}}catch(e){console.warn(`Could not load SCAD prompt settings`,e)}e.addEventListener(`change`,e=>{e.target.type===`checkbox`&&n()}),i(),a()}function r(e){return{basic:e.querySelector(`#opt-pbr-basic`)?.checked??!0,transmission:e.querySelector(`#opt-pbr-transmission`)?.checked??!0,clearcoat:e.querySelector(`#opt-pbr-clearcoat`)?.checked??!0,sheen:e.querySelector(`#opt-pbr-sheen`)?.checked??!0,emissive:e.querySelector(`#opt-pbr-emissive`)?.checked??!0,specular:e.querySelector(`#opt-pbr-specular`)?.checked??!0,iridescence:e.querySelector(`#opt-pbr-iridescence`)?.checked??!0,autoSmoothAngle:e.querySelector(`#opt-pbr-autosmooth`)?.checked??!0,bakeColors:e.querySelector(`#opt-bake-colors`)?.checked??!1,bakeNormals:e.querySelector(`#opt-bake-normals`)?.checked??!1,bakeOrm:e.querySelector(`#opt-bake-orm`)?.checked??!1,bakeUvs:e.querySelector(`#opt-bake-uvs`)?.checked??!1,animation:e.querySelector(`#opt-anim`)?.checked??!0}}export{e as i,r as n,n as r,t};
@@ -0,0 +1,4 @@
1
+ // Workaround for Chrome Extension ES modules
2
+ (async () => {
3
+ await import(chrome.runtime.getURL("content.js"));
4
+ })();
@@ -0,0 +1,255 @@
1
+ .scad-preview-btn {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ gap: 5px;
6
+ background: rgba(0, 170, 255, 0.15);
7
+ color: #00aaff;
8
+ border: 1px solid rgba(0, 170, 255, 0.4);
9
+ padding: 4px 8px;
10
+ border-radius: 4px;
11
+ cursor: pointer;
12
+ font-weight: 600;
13
+ font-size: 12px;
14
+ line-height: 1;
15
+ transition: all 0.2s ease;
16
+ height: 28px;
17
+ box-sizing: border-box;
18
+ margin: 0 4px;
19
+ vertical-align: middle;
20
+ }
21
+
22
+ .scad-preview-btn:hover {
23
+ background: #00aaff;
24
+ color: #ffffff;
25
+ border-color: #00aaff;
26
+ }
27
+
28
+ .scad-preview-btn svg {
29
+ width: 14px;
30
+ height: 14px;
31
+ flex-shrink: 0;
32
+ }
33
+
34
+ #scad-preview-iframe {
35
+ position: fixed;
36
+ bottom: 20px;
37
+ right: 20px;
38
+ width: 600px;
39
+ height: 550px;
40
+ max-width: calc(100vw - 40px);
41
+ max-height: calc(100vh - 40px);
42
+ z-index: 999999;
43
+ background: #222;
44
+ border-radius: 8px;
45
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
46
+ overflow: hidden;
47
+ border: 1px solid #555;
48
+ }
49
+
50
+ #scad-preview-iframe iframe {
51
+ width: 100%;
52
+ height: 100%;
53
+ border: none;
54
+ display: block;
55
+ }
56
+
57
+ .scad-close-btn {
58
+ position: absolute;
59
+ top: 5px;
60
+ right: 5px;
61
+ background: red;
62
+ color: white;
63
+ border: none;
64
+ border-radius: 50%;
65
+ width: 25px;
66
+ height: 25px;
67
+ cursor: pointer;
68
+ font-weight: bold;
69
+ z-index: 10;
70
+ }
71
+
72
+ .scad-prompt-btn {
73
+ position: fixed;
74
+ bottom: 20px;
75
+ left: 20px;
76
+ background: #8a2be2;
77
+ color: white;
78
+ border: none;
79
+ padding: 12px 18px;
80
+ border-radius: 24px;
81
+ cursor: pointer;
82
+ font-weight: bold;
83
+ font-size: 14px;
84
+ z-index: 999999;
85
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
86
+ font-family: sans-serif;
87
+ transition:
88
+ transform 0.1s,
89
+ background 0.2s;
90
+ }
91
+
92
+ .scad-prompt-btn:hover {
93
+ background: #7a1fd1;
94
+ transform: scale(1.05);
95
+ }
96
+
97
+ .scad-prompt-btn:active {
98
+ transform: scale(0.95);
99
+ }
100
+
101
+ .scad-modal-overlay {
102
+ position: fixed;
103
+ top: 0;
104
+ left: 0;
105
+ width: 100vw;
106
+ height: 100vh;
107
+ background: rgba(0, 0, 0, 0.6);
108
+ z-index: 9999999; /* Ensure it stays on top of AI studio UI */
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ backdrop-filter: blur(3px);
113
+ }
114
+
115
+ .scad-modal-content {
116
+ background: #1e1e1e;
117
+ color: #ddd;
118
+ padding: 20px;
119
+ border-radius: 12px;
120
+ width: 500px;
121
+ max-width: 90vw;
122
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.7);
123
+ border: 1px solid #444;
124
+ font-family: system-ui, sans-serif;
125
+ display: flex;
126
+ flex-direction: column;
127
+ gap: 15px;
128
+ }
129
+
130
+ .scad-modal-header {
131
+ display: flex;
132
+ justify-content: space-between;
133
+ align-items: center;
134
+ }
135
+
136
+ .scad-modal-header h3 {
137
+ margin: 0;
138
+ color: #fff;
139
+ font-size: 16px;
140
+ text-transform: uppercase;
141
+ letter-spacing: 0.5px;
142
+ }
143
+
144
+ .scad-modal-close {
145
+ background: transparent;
146
+ color: #aaa;
147
+ border: none;
148
+ font-size: 16px;
149
+ cursor: pointer;
150
+ font-weight: bold;
151
+ }
152
+ .scad-modal-close:hover {
153
+ color: #fff;
154
+ }
155
+
156
+ .scad-help-text {
157
+ font-size: 12px;
158
+ color: #aaa;
159
+ margin: 0;
160
+ }
161
+
162
+ .scad-prompt-toggles {
163
+ display: flex;
164
+ flex-wrap: wrap;
165
+ gap: 8px 12px;
166
+ background: #2a2a2a;
167
+ padding: 10px;
168
+ border-radius: 6px;
169
+ border: 1px solid #444;
170
+ }
171
+
172
+ .scad-prompt-toggles label {
173
+ cursor: pointer;
174
+ display: flex;
175
+ align-items: center;
176
+ gap: 4px;
177
+ font-size: 12px;
178
+ user-select: none;
179
+ }
180
+
181
+ .scad-prompt-group {
182
+ background: #2a2a2a;
183
+ padding: 10px;
184
+ border-radius: 6px;
185
+ border: 1px solid #444;
186
+ display: flex;
187
+ flex-direction: column;
188
+ gap: 8px;
189
+ }
190
+
191
+ .scad-prompt-group-header {
192
+ display: flex;
193
+ align-items: center;
194
+ justify-content: space-between;
195
+ font-size: 13px;
196
+ font-weight: bold;
197
+ color: #eee;
198
+ padding-bottom: 6px;
199
+ border-bottom: 1px solid #444;
200
+ }
201
+
202
+ .scad-prompt-group-header label {
203
+ font-weight: normal;
204
+ font-size: 12px;
205
+ cursor: pointer;
206
+ display: flex;
207
+ align-items: center;
208
+ gap: 4px;
209
+ }
210
+
211
+ .scad-prompt-toggles.nested {
212
+ background: transparent;
213
+ padding: 0;
214
+ border: none;
215
+ margin: 0;
216
+ }
217
+
218
+ #scad-prompt-desc {
219
+ width: 100%;
220
+ background: #2a2a2a;
221
+ color: #fff;
222
+ border: 1px solid #444;
223
+ border-radius: 6px;
224
+ padding: 12px;
225
+ font-family: sans-serif;
226
+ font-size: 14px;
227
+ resize: vertical;
228
+ outline: none;
229
+ box-sizing: border-box;
230
+ }
231
+
232
+ #scad-prompt-desc:focus {
233
+ border-color: #8a2be2;
234
+ }
235
+
236
+ .scad-modal-actions {
237
+ display: flex;
238
+ justify-content: flex-end;
239
+ }
240
+
241
+ .scad-primary-btn {
242
+ background: #8a2be2;
243
+ color: white;
244
+ border: none;
245
+ padding: 10px 16px;
246
+ border-radius: 6px;
247
+ cursor: pointer;
248
+ font-weight: bold;
249
+ font-size: 14px;
250
+ transition: background 0.2s;
251
+ }
252
+
253
+ .scad-primary-btn:hover {
254
+ background: #7a1fd1;
255
+ }
@@ -0,0 +1,24 @@
1
+ import{i as e,n as t,r as n,t as r}from"./assets/prompt-ui-8EFRz0ju.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
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
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
+ <polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
5
+ <line x1="12" y1="22.08" x2="12" y2="12"></line>
6
+ </svg>
7
+ <span>Preview 3D</span>
8
+ `,t.onpointerdown=e=>e.stopPropagation(),t.onmousedown=e=>e.stopPropagation(),t.onclick=t=>{t.preventDefault(),t.stopPropagation();let n=e.querySelector(`code`);n&&s(n.innerText)};let n=e.querySelector(`button[aria-label*="copy" i], button[aria-label*="download" i], button[aria-label*="collapse" i], button[mattooltip*="copy" i], button[mattooltip*="download" i], button[mattooltip*="collapse" i], button[title*="Copy" i], button[title*="Download" i]`),r=n?.parentElement||e.querySelector(`.actions`)||e.querySelector(`.header-actions`)||e.querySelector(`.action-buttons`)||e.querySelector(`.buttons`)||e.querySelector(`.header`)||e.querySelector(`.code-header`);if(r)n?r.insertBefore(t,n):r.prepend(t);else{let n=e.querySelector(`pre`);n?n.parentNode.insertBefore(t,n):e.appendChild(t)}})}).observe(document.body,{childList:!0,subtree:!0});function a(){if(document.getElementById(`scad-prompt-btn`))return;let e=document.createElement(`button`);e.id=`scad-prompt-btn`,e.innerText=`✨ SCAD`,e.className=`scad-prompt-btn`,e.onclick=e=>{e.preventDefault(),e.stopPropagation();let t=document.getElementById(`scad-prompt-modal`);if(t){let e=document.getElementById(`scad-prompt-desc`);e&&(e.value=``),t.style.display=`flex`,e&&e.focus()}},document.body.appendChild(e)}function o(){if(document.getElementById(`scad-prompt-modal`))return;let i=document.createElement(`div`);i.id=`scad-prompt-modal`,i.className=`scad-modal-overlay`,i.style.display=`none`,i.innerHTML=`
9
+ <div class="scad-modal-content">
10
+ <div class="scad-modal-header">
11
+ <h3>✨ SCAD Prompt Generator</h3>
12
+ <button id="scad-prompt-close" class="scad-modal-close">X</button>
13
+ </div>
14
+ <p class="scad-help-text">Describe the object you want to generate. The copied prompt will include advanced PBR and Animation rules based on your selection.</p>
15
+
16
+ <div id="scad-prompt-ui-container"></div>
17
+
18
+ <textarea id="scad-prompt-desc" rows="3" placeholder="e.g. A shiny gold ring with an embedded red gem"></textarea>
19
+
20
+ <div class="scad-modal-actions">
21
+ <button id="scad-prompt-submit" class="scad-primary-btn">📋 Generate & Paste</button>
22
+ </div>
23
+ </div>
24
+ `,document.body.appendChild(i);let a=document.getElementById(`scad-prompt-ui-container`);a.innerHTML=r,n(a,`scad_prompt_settings`),document.getElementById(`scad-prompt-close`).onclick=()=>i.style.display=`none`,i.onclick=e=>{e.target===i&&(i.style.display=`none`)},document.getElementById(`scad-prompt-submit`).onclick=async()=>{let n=document.getElementById(`scad-prompt-desc`).value.trim();if(!n){alert(`Please enter a description!`);return}let r=t(a);try{let t=e(n,r),a=document.querySelector(`ms-prompt-box textarea[formcontrolname="promptText"]`)||document.querySelector(`ms-prompt-box textarea`)||document.querySelector(`textarea`);a&&(a.focus(),document.execCommand(`insertText`,!1,t)||(Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype,`value`).set.call(a,t),a.dispatchEvent(new Event(`input`,{bubbles:!0})),a.dispatchEvent(new Event(`change`,{bubbles:!0})))),await navigator.clipboard.writeText(t),i.style.display=`none`;let o=document.getElementById(`scad-prompt-btn`),s=o.innerText;o.innerText=`✅ Prompt Pasted!`,setTimeout(()=>{o.innerText=s},2e3)}catch(e){console.error(`Error generating/copying prompt:`,e),alert(`Error: `+e.message)}}}function s(e){let t=document.getElementById(`scad-preview-iframe`);if(t){t.querySelector(`iframe`).contentWindow.postMessage({type:`RENDER_SCAD`,code:e},`*`);return}let n=document.createElement(`div`);n.id=`scad-preview-iframe`;let r=document.createElement(`button`);r.innerText=`X`,r.className=`scad-close-btn`,r.onclick=()=>n.remove();let i=document.createElement(`iframe`);i.src=chrome.runtime.getURL(`preview.html`),i.allow=`fullscreen`,i.allowFullscreen=!0,i.setAttribute(`allowfullscreen`,`true`),i.setAttribute(`allow`,`fullscreen`),n.appendChild(r),n.appendChild(i),document.body.appendChild(n),i.onload=()=>{i.contentWindow.postMessage({type:`RENDER_SCAD`,code:e},`*`)}}
Binary file
@@ -0,0 +1,187 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Scadify</title>
7
+
8
+ <!-- Primary Meta Tags -->
9
+ <meta
10
+ name="description"
11
+ content="Scadify is a modern, web-based editor and 3D viewer for OpenSCAD supporting WebAssembly compilation, PBR materials, skeletal animations, and texture baking."
12
+ />
13
+ <meta name="theme-color" content="#222222" />
14
+
15
+ <!-- iOS PWA Support -->
16
+ <meta name="mobile-web-app-capable" content="yes" />
17
+ <meta name="apple-mobile-web-app-capable" content="yes" />
18
+ <meta
19
+ name="apple-mobile-web-app-status-bar-style"
20
+ content="black-translucent"
21
+ />
22
+ <meta name="apple-mobile-web-app-title" content="Scadify" />
23
+
24
+ <!-- Icons -->
25
+ <link rel="icon" type="image/png" href="./icon.png" />
26
+ <link rel="apple-touch-icon" href="./icon.png" />
27
+
28
+ <!-- Link the PWA webmanifest (the extension uses manifest.json natively) -->
29
+ <link rel="manifest" href="./manifest.webmanifest" />
30
+
31
+ <!-- Open Graph -->
32
+ <meta property="og:type" content="website" />
33
+ <meta
34
+ property="og:url"
35
+ content="https://iliagrigorevdev.github.io/scad-gltf/"
36
+ />
37
+ <meta property="og:title" content="Scadify" />
38
+ <meta
39
+ property="og:description"
40
+ content="Scadify is a modern, web-based editor and 3D viewer for OpenSCAD supporting WebAssembly compilation, PBR materials, skeletal animations, and texture baking."
41
+ />
42
+ <meta
43
+ property="og:image"
44
+ content="https://iliagrigorevdev.github.io/scad-gltf/icon.png"
45
+ />
46
+
47
+ <script type="module" crossorigin src="./assets/index-V9cEH4KX.js"></script>
48
+ <link rel="modulepreload" crossorigin href="./assets/OutputPass-Bvl6NigM.js">
49
+ <link rel="modulepreload" crossorigin href="./assets/prompt-ui-8EFRz0ju.js">
50
+ <link rel="stylesheet" crossorigin href="./assets/index-t9MYrExo.css">
51
+ <link rel="manifest" href="./manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="./registerSW.js"></script></head>
52
+ <body>
53
+ <div id="app-container">
54
+ <!-- Left / Top Half: Controls -->
55
+ <div id="left-panel">
56
+ <div class="panel-section">
57
+ <h2>1. AI Prompt Generator</h2>
58
+ <p class="help-text">
59
+ Describe the object you want to generate. The copied prompt will
60
+ include advanced PBR and Animation rules based on your selection.
61
+ </p>
62
+ <div id="prompt-ui-container"></div>
63
+
64
+ <textarea
65
+ id="prompt-desc"
66
+ rows="2"
67
+ placeholder="e.g. A shiny gold ring with an embedded red gem"
68
+ ></textarea>
69
+ <button id="copy-prompt-btn">📋 Copy Prompt to Clipboard</button>
70
+ </div>
71
+
72
+ <div class="panel-section flex-grow">
73
+ <h2>2. SCAD Editor</h2>
74
+ <input type="text" id="model-name-input" placeholder="Name" />
75
+ <textarea id="editor" spellcheck="false"></textarea>
76
+
77
+ <div class="controls-row">
78
+ <button id="render-btn" class="primary">▶ Render</button>
79
+ <button id="load-scad-btn">📁 Load</button>
80
+ <button id="download-scad-btn">⬇ SCAD</button>
81
+ <button id="export-gltf-btn">⬇ GLTF</button>
82
+ <button id="capture-image-btn">📷 Image</button>
83
+ <button id="share-btn">🔗 Share</button>
84
+ </div>
85
+ <div class="controls-row toggles">
86
+ <label
87
+ ><input type="checkbox" id="auto-render-cb" checked /> Auto
88
+ Render</label
89
+ >
90
+ <label
91
+ ><input type="checkbox" id="path-tracing-cb" /> Path
92
+ Tracing</label
93
+ >
94
+ <label
95
+ ><input type="checkbox" id="minify-share-cb" /> Minify
96
+ Share</label
97
+ >
98
+ </div>
99
+ </div>
100
+
101
+ <div
102
+ class="panel-section"
103
+ id="anim-controls-section"
104
+ style="display: none"
105
+ >
106
+ <h2>3. Animation Controls</h2>
107
+ <div class="controls-row">
108
+ <button id="anim-play-btn" class="primary" style="width: 90px">
109
+ ⏸ Pause
110
+ </button>
111
+ <select id="anim-select"></select>
112
+ </div>
113
+ <div class="controls-row">
114
+ <input
115
+ type="range"
116
+ id="anim-slider"
117
+ min="0"
118
+ max="1"
119
+ step="0.001"
120
+ value="0"
121
+ />
122
+ </div>
123
+ </div>
124
+
125
+ <div class="panel-section">
126
+ <h2>4. SCAD Serve Backend</h2>
127
+ <div class="controls-row">
128
+ <input
129
+ type="text"
130
+ id="backend-url"
131
+ value="http://localhost:3000"
132
+ placeholder="http://localhost:3000"
133
+ />
134
+ <button id="backend-connect-btn">Connect</button>
135
+ </div>
136
+ <div id="backend-ui">
137
+ <div class="controls-row">
138
+ <select id="backend-select">
139
+ <option value="">-- Create New Model --</option>
140
+ </select>
141
+ <button
142
+ id="backend-single-save-btn"
143
+ class="primary"
144
+ style="display: none"
145
+ >
146
+ Save
147
+ </button>
148
+ <button
149
+ id="backend-delete-btn"
150
+ class="danger"
151
+ style="display: none"
152
+ >
153
+ Delete
154
+ </button>
155
+ </div>
156
+ </div>
157
+ </div>
158
+
159
+ <div class="panel-section footer-section">
160
+ <a
161
+ href="https://github.com/iliagrigorevdev/scad-gltf"
162
+ target="_blank"
163
+ class="source-link"
164
+ >
165
+ ★ View Source Code on GitHub
166
+ </a>
167
+ </div>
168
+ </div>
169
+
170
+ <!-- Right / Bottom Half: Viewer -->
171
+ <div id="right-panel">
172
+ <div id="viewer-controls">
173
+ <label
174
+ ><input type="checkbox" id="show-grid-cb" checked /> Grid</label
175
+ >
176
+ <label><input type="checkbox" id="wireframe-cb" /> Wireframe</label>
177
+ <button id="fullscreen-btn" title="Toggle Full Screen">⛶</button>
178
+ </div>
179
+ <div id="viewer"></div>
180
+ </div>
181
+ </div>
182
+
183
+ <!-- Drag and Drop Overlay -->
184
+ <div id="drag-overlay">Drop .scad or .hdr file here</div>
185
+
186
+ </body>
187
+ </html>