scad-gltf 0.1.1 → 0.1.2

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.
@@ -4081,19 +4081,34 @@ armature(animations=anim_data) {
4081
4081
 
4082
4082
  // Deep Volumetric Sapphire
4083
4083
  // Showcasing: $asa=0.0 (Preserves faceted "cut" look of the crystal)
4084
- color([1.0, 1.0, 1.0, 1.0], transmission=1.0, roughness=0.0, thickness=8.0, ior=1.76, attenuationColor=[0.1, 0.3, 0.9], attenuationDistance=3.0) {
4084
+ color([1.0, 1.0, 1.0, 1.0], transmission=1.0, roughness=0.0, thickness=8.0, ior=1.76, attenuationColor=[0.1, 0.3, 0.9], attenuationDistance=6.0, $asa=30) {
4085
+
4086
+ // Use difference() to carve out an inner cavity so the shell is hollow,
4087
+ // allowing physically correct index-of-refraction depth sorting.
4088
+ difference() {
4089
+ // Octagonal faceted crystal shape
4090
+ union() {
4091
+ cylinder(h=5, r1=4, r2=0, $fn=8);
4092
+ translate([0,0,-5]) cylinder(h=5, r1=0, r2=4, $fn=8);
4093
+ }
4094
+
4095
+ // Hollow cavity
4096
+ sphere(r=0.9, $fn=32);
4097
+ }
4098
+ }
4085
4099
 
4086
- // Octagonal faceted crystal shape
4087
- union() {
4088
- cylinder(h=5, r1=4, r2=0, $fn=8);
4089
- translate([0,0,-5]) cylinder(h=5, r1=0, r2=4, $fn=8);
4100
+ // 4. Glowing Core (Child of Gem Shell)
4101
+ // Placed inside its own bone to prevent auto-unioning with the transparent gem
4102
+ bone(name="Core", t=[0,0,0]) {
4103
+ color([0.0, 0.0, 0.0, 1.0], emissive=[0.5, 0.8, 1.0], emissiveIntensity=20.0, $asa=45) {
4104
+ sphere(r=0.6, $fn=32);
4090
4105
  }
4091
4106
  }
4092
4107
  }
4093
4108
  }
4094
4109
  }
4095
4110
  }
4096
- `,To=document.getElementById(`prompt-desc`),Eo=document.getElementById(`copy-prompt-btn`),Do=document.getElementById(`model-name-input`),z=document.getElementById(`editor`),Oo=document.getElementById(`render-btn`),ko=document.getElementById(`load-scad-btn`),Ao=document.getElementById(`download-scad-btn`),jo=document.getElementById(`export-gltf-btn`),Mo=document.getElementById(`capture-image-btn`),No=document.getElementById(`share-btn`),Po=document.getElementById(`auto-render-cb`),Fo=document.getElementById(`minify-share-cb`),B=document.getElementById(`viewer`),Io=document.getElementById(`right-panel`),Lo=document.getElementById(`backend-url`),Ro=document.getElementById(`backend-connect-btn`),zo=document.getElementById(`backend-ui`),Bo=document.getElementById(`backend-select`),Vo=document.getElementById(`backend-single-save-btn`),Ho=document.getElementById(`backend-delete-btn`),Uo=document.getElementById(`anim-controls-section`),Wo=document.getElementById(`anim-play-btn`),Go=document.getElementById(`anim-select`),Ko=document.getElementById(`anim-slider`),qo=document.getElementById(`prompt-ui-container`),Jo=document.getElementById(`show-grid-cb`),Yo=document.getElementById(`wireframe-cb`),Xo=document.getElementById(`fullscreen-btn`),Zo=``,V=null,Qo=null,$o=[],es=!1,ts=null,H=null,ns=!1,rs=!1,is=null,as=[],os=!1,ss=!1,cs={isNew:!0,options:{},content:``},U=null,ls=!0,us=!1;function ds(){return ss?z.value:z.value||`// Global detail setting
4111
+ `,To=document.getElementById(`prompt-desc`),Eo=document.getElementById(`copy-prompt-btn`),Do=document.getElementById(`model-name-input`),z=document.getElementById(`editor`),Oo=document.getElementById(`render-btn`),ko=document.getElementById(`load-scad-btn`),Ao=document.getElementById(`download-scad-btn`),jo=document.getElementById(`export-gltf-btn`),Mo=document.getElementById(`capture-image-btn`),No=document.getElementById(`share-btn`),Po=document.getElementById(`auto-render-cb`),Fo=document.getElementById(`minify-share-cb`),B=document.getElementById(`viewer`),Io=document.getElementById(`right-panel`),Lo=document.getElementById(`backend-url`),Ro=document.getElementById(`backend-connect-btn`),zo=document.getElementById(`backend-ui`),Bo=document.getElementById(`backend-select`),Vo=document.getElementById(`backend-single-save-btn`),Ho=document.getElementById(`backend-delete-btn`),Uo=document.getElementById(`anim-controls-section`),Wo=document.getElementById(`anim-play-btn`),Go=document.getElementById(`anim-select`),Ko=document.getElementById(`anim-slider`),qo=document.getElementById(`prompt-ui-container`),Jo=document.getElementById(`show-grid-cb`),Yo=document.getElementById(`wireframe-cb`),Xo=document.getElementById(`fullscreen-btn`),Zo=document.getElementById(`screenshot-btn`),Qo=``,V=null,$o=null,es=[],ts=!1,ns=null,H=null,rs=!1,is=!1,as=null,os=[],ss=!1,cs=!1,ls={isNew:!0,options:{},content:``},U=null,us=!0,ds=!1;function fs(){return cs?z.value:z.value||`// Global detail setting
4097
4112
  $fn = 64;
4098
4113
 
4099
4114
  // Animation Data Dictionary
@@ -4192,20 +4207,35 @@ armature(animations=anim_data) {
4192
4207
 
4193
4208
  // Deep Volumetric Sapphire
4194
4209
  // Showcasing: $asa=0.0 (Preserves faceted "cut" look of the crystal)
4195
- color([1.0, 1.0, 1.0, 1.0], transmission=1.0, roughness=0.0, thickness=8.0, ior=1.76, attenuationColor=[0.1, 0.3, 0.9], attenuationDistance=3.0) {
4210
+ color([1.0, 1.0, 1.0, 1.0], transmission=1.0, roughness=0.0, thickness=8.0, ior=1.76, attenuationColor=[0.1, 0.3, 0.9], attenuationDistance=6.0, $asa=30) {
4211
+
4212
+ // Use difference() to carve out an inner cavity so the shell is hollow,
4213
+ // allowing physically correct index-of-refraction depth sorting.
4214
+ difference() {
4215
+ // Octagonal faceted crystal shape
4216
+ union() {
4217
+ cylinder(h=5, r1=4, r2=0, $fn=8);
4218
+ translate([0,0,-5]) cylinder(h=5, r1=0, r2=4, $fn=8);
4219
+ }
4220
+
4221
+ // Hollow cavity
4222
+ sphere(r=0.9, $fn=32);
4223
+ }
4224
+ }
4196
4225
 
4197
- // Octagonal faceted crystal shape
4198
- union() {
4199
- cylinder(h=5, r1=4, r2=0, $fn=8);
4200
- translate([0,0,-5]) cylinder(h=5, r1=0, r2=4, $fn=8);
4226
+ // 4. Glowing Core (Child of Gem Shell)
4227
+ // Placed inside its own bone to prevent auto-unioning with the transparent gem
4228
+ bone(name="Core", t=[0,0,0]) {
4229
+ color([0.0, 0.0, 0.0, 1.0], emissive=[0.5, 0.8, 1.0], emissiveIntensity=20.0, $asa=45) {
4230
+ sphere(r=0.6, $fn=32);
4201
4231
  }
4202
4232
  }
4203
4233
  }
4204
4234
  }
4205
4235
  }
4206
4236
  }
4207
- `}function fs(){return Do.value.trim()||`model`}Do.addEventListener(`input`,Ds),qo&&(qo.innerHTML=We,Ue(qo,`scad_editor_prompt_settings`)),Eo.onclick=async()=>{let e=To.value.trim()||`an object`,t=He(qo),n=Ve(e,t);try{await navigator.clipboard.writeText(n);let e=Eo.innerText;Eo.innerText=`✅ Copied!`,setTimeout(()=>{Eo.innerText=e},2e3)}catch(e){alert(`Failed to copy clipboard: `+e)}};async function ps(e){let t={};if(!ss)return t;let n=new Set;async function r(e){let i=/(?:include|use)\s*([<"])([^>"]+)([>"])/g,a;for(;(a=i.exec(e))!==null;){let e=a[2],i=e.split(/[/\\]/).pop();if(!n.has(e)){n.add(e);try{let n=await fetch(`${Cs}/api/scads/${encodeURIComponent(i)}`);if(n.ok){let i=await n.json();t[e]=i.content,await r(i.content)}}catch{console.warn(`Failed to load dependency: ${e}`)}}}}return await r(e),t}async function ms(e){if(typeof e!=`string`)return;if(es){ts=e;return}if(e.trim()===``){Rs();return}let t=e.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&&Do.value!==e&&(Do.value=e,Ds())}es=!0;try{let t=await ps(e);Qo=await xe(e,{wasmUrl:me,additionalFiles:t}),await Bs(Qo)}catch(e){console.error(e)}finally{if(es=!1,ts!==null){let e=ts;ts=null,ms(e)}}}Oo.onclick=()=>ms(ds());var hs;z.addEventListener(`input`,()=>{Ds(),clearTimeout(hs),Po.checked&&(hs=setTimeout(()=>{ms(ds())},800))}),Po.addEventListener(`change`,()=>{Po.checked&&ms(ds())}),ko.onclick=()=>{let e=document.createElement(`input`);e.type=`file`,e.accept=`.scad`,e.onchange=async e=>{if(e.target.files&&e.target.files.length>0)try{let t=await e.target.files[0].text();z.value=t,Ds(),ms(t)}catch(e){alert(`Failed to read file: `+e.message)}},e.click()};function gs(e,t){let n=URL.createObjectURL(e),r=document.createElement(`a`);r.href=n,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(n)}Ao.onclick=()=>{let e=ds();gs(new Blob([e],{type:`text/plain`}),`${fs()}.scad`)},jo.onclick=()=>{Qo&&gs(new Blob([Qo],{type:`application/octet-stream`}),`${fs()}.glb`)};function _s(){Mo.innerText=rs?`🔴 Rec...`:U&&ls?`🎥 Video`:`📷 Image`}Mo.onclick=()=>{if(rs){os=!0,is&&is.state!==`inactive`&&is.stop();return}if(U&&ls){if(typeof MediaRecorder<`u`){let e=q!==void 0&&J&&J.checked;U.time=0,H&&H.update(0),G.aspect=1,G.updateProjectionMatrix(),K.setSize(512,512,!1),ks.setSize(512,512),e&&q.updateCamera();let t=K.domElement,n=document.createElement(`canvas`);n.width=512,n.height=512;let r=n.getContext(`2d`),i=n.captureStream(30),a=25e5,o=`mp4`,s={mimeType:`video/mp4;codecs=avc1`,videoBitsPerSecond:a};MediaRecorder.isTypeSupported(s.mimeType)||(s={mimeType:`video/mp4`,videoBitsPerSecond:a},MediaRecorder.isTypeSupported(s.mimeType)||(o=`webm`,s={mimeType:`video/webm;codecs=vp9`,videoBitsPerSecond:a},MediaRecorder.isTypeSupported(s.mimeType)||(s={mimeType:`video/webm;codecs=vp8`,videoBitsPerSecond:a},MediaRecorder.isTypeSupported(s.mimeType)||(s={mimeType:`video/webm`,videoBitsPerSecond:a}))));let c=null;os=!1,rs=!0,_s();let l=U.getClip().duration,u=Math.round(l*30),d=0,f=[],p=()=>{if(os||d>=u){if(f.length===0){rs=!1,G.aspect=B.clientWidth/B.clientHeight,G.updateProjectionMatrix(),K.setSize(B.clientWidth,B.clientHeight),ks.setSize(B.clientWidth,B.clientHeight),e&&q!==void 0&&q.updateCamera(),_s();return}os=!1,is=new MediaRecorder(i,s),as=[],is.ondataavailable=e=>{e.data.size>0&&as.push(e.data)},is.onstop=()=>{c&&clearTimeout(c),gs(new Blob(as,{type:s.mimeType.split(`;`)[0]}),`${fs()}.${o}`),rs=!1,G.aspect=B.clientWidth/B.clientHeight,G.updateProjectionMatrix(),K.setSize(B.clientWidth,B.clientHeight),ks.setSize(B.clientWidth,B.clientHeight),e&&q!==void 0&&q.updateCamera(),_s()},r.drawImage(f[0],0,0,512,512),is.start();let t=0,n=performance.now(),a=()=>{if(os){is.stop();return}if(t<f.length){r.drawImage(f[t],0,0,512,512),t++;let e=n+1e3/30*t,i=Math.max(0,e-performance.now());c=setTimeout(a,i)}else c=setTimeout(()=>is.stop(),50)};c=setTimeout(a,0);return}if(U.time=d/30,H&&H.update(0),e){Z!==void 0&&(Z.visible=!1),Y!==void 0&&(Y.visible=!1),Q!==void 0&&(Q.visible=!1),$!==void 0&&($.visible=!1),q.setScene(W,G);let e=()=>{if(os){p();return}let n=0;for(;q.samples<100&&n<5;)q.renderSample(),n++;if(q.samples>=100){let e=document.createElement(`canvas`);e.width=512,e.height=512,e.getContext(`2d`).drawImage(t,0,0,512,512),f.push(e),d++,setTimeout(p,0)}else requestAnimationFrame(e)};e()}else{let e=!Jo||Jo.checked;Z!==void 0&&(Z.visible=!0),Y!==void 0&&(Y.visible=!0),Q!==void 0&&(Q.visible=e),$!==void 0&&($.visible=e),ks.render();let n=document.createElement(`canvas`);n.width=512,n.height=512,n.getContext(`2d`).drawImage(t,0,0,512,512),f.push(n),d++,setTimeout(p,0)}};p()}else alert(`Video recording is not supported in this browser.`)}else ns=!0};function vs(e){let t=e.length%4;return t===2?e+`==`:t===3?e+`=`:e}async function ys(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(/=+$/,``)}async function bs(e){if(!e)return null;let t=e.charAt(0),n=e.substring(1);if(n=vs(n.replace(/-/g,`+`).replace(/_/g,`/`)),t===`c`)try{let e=atob(n),t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n);let r=new Blob([t]).stream().pipeThrough(new DecompressionStream(`deflate-raw`)),i=await new Response(r).arrayBuffer();return new TextDecoder().decode(i)}catch(e){console.warn(`DecompressionStream failed`,e)}else if(t===`u`)try{return decodeURIComponent(escape(atob(n)))}catch(e){console.warn(`Unescape failed`,e)}else try{return decodeURIComponent(escape(atob(vs(e.replace(/-/g,`+`).replace(/_/g,`/`)))))}catch{return decodeURIComponent(e)}return null}No.onclick=async()=>{let e=z.value.trim(),t=e,n=new URL(window.location.href),r=``;if(Fo&&Fo.checked){let t=``,n=!1,r=0;for(;r<e.length;)if(n)t+=e[r],e[r]===`"`&&e[r-1]!==`\\`&&(n=!1),r++;else if(e[r]===`"`)n=!0,t+=e[r],r++;else if(e[r]===`/`&&e[r+1]===`/`)for(r+=2;r<e.length&&e[r]!==`
4208
- `;)r++;else if(e[r]===`/`&&e[r+1]===`*`){for(r+=2;r<e.length&&(e[r]!==`*`||e[r+1]!==`/`);)r++;r+=2}else/\s/.test(e[r])?(t.length>0&&!/\s/.test(t[t.length-1])&&(t+=` `),r++):(t+=e[r],r++);e=t.trim()}if(!e||!ss&&t===`// Global detail setting
4237
+ `}function ps(){return Do.value.trim()||`model`}Do.addEventListener(`input`,Os),qo&&(qo.innerHTML=We,Ue(qo,`scad_editor_prompt_settings`)),Eo.onclick=async()=>{let e=To.value.trim()||`an object`,t=He(qo),n=Ve(e,t);try{await navigator.clipboard.writeText(n);let e=Eo.innerText;Eo.innerText=`✅ Copied!`,setTimeout(()=>{Eo.innerText=e},2e3)}catch(e){alert(`Failed to copy clipboard: `+e)}};async function ms(e){let t={};if(!cs)return t;let n=new Set;async function r(e){let i=/(?:include|use)\s*([<"])([^>"]+)([>"])/g,a;for(;(a=i.exec(e))!==null;){let e=a[2],i=e.split(/[/\\]/).pop();if(!n.has(e)){n.add(e);try{let n=await fetch(`${ws}/api/scads/${encodeURIComponent(i)}`);if(n.ok){let i=await n.json();t[e]=i.content,await r(i.content)}}catch{console.warn(`Failed to load dependency: ${e}`)}}}}return await r(e),t}async function hs(e){if(typeof e!=`string`)return;if(ts){ns=e;return}if(e.trim()===``){zs();return}let t=e.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&&Do.value!==e&&(Do.value=e,Os())}ts=!0;try{let t=await ms(e);$o=await xe(e,{wasmUrl:me,additionalFiles:t}),await Vs($o)}catch(e){console.error(e)}finally{if(ts=!1,ns!==null){let e=ns;ns=null,hs(e)}}}Oo.onclick=()=>hs(fs());var gs;z.addEventListener(`input`,()=>{Os(),clearTimeout(gs),Po.checked&&(gs=setTimeout(()=>{hs(fs())},800))}),Po.addEventListener(`change`,()=>{Po.checked&&hs(fs())}),ko.onclick=()=>{let e=document.createElement(`input`);e.type=`file`,e.accept=`.scad`,e.onchange=async e=>{if(e.target.files&&e.target.files.length>0)try{let t=await e.target.files[0].text();z.value=t,Os(),hs(t)}catch(e){alert(`Failed to read file: `+e.message)}},e.click()};function _s(e,t){let n=URL.createObjectURL(e),r=document.createElement(`a`);r.href=n,r.download=t,document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(n)}Ao.onclick=()=>{let e=fs();_s(new Blob([e],{type:`text/plain`}),`${ps()}.scad`)},jo.onclick=()=>{$o&&_s(new Blob([$o],{type:`application/octet-stream`}),`${ps()}.glb`)};function vs(){Mo.innerText=is?`🔴 Rec...`:U&&us?`🎥 Video`:`📷 Image`}Mo.onclick=()=>{if(is){ss=!0,as&&as.state!==`inactive`&&as.stop();return}if(U&&us){if(typeof MediaRecorder<`u`){let e=q!==void 0&&J&&J.checked;U.time=0,H&&H.update(0),G.aspect=1,G.updateProjectionMatrix(),K.setSize(512,512,!1),As.setSize(512,512),e&&q.updateCamera();let t=K.domElement,n=document.createElement(`canvas`);n.width=512,n.height=512;let r=n.getContext(`2d`),i=n.captureStream(30),a=25e5,o=`mp4`,s={mimeType:`video/mp4;codecs=avc1`,videoBitsPerSecond:a};MediaRecorder.isTypeSupported(s.mimeType)||(s={mimeType:`video/mp4`,videoBitsPerSecond:a},MediaRecorder.isTypeSupported(s.mimeType)||(o=`webm`,s={mimeType:`video/webm;codecs=vp9`,videoBitsPerSecond:a},MediaRecorder.isTypeSupported(s.mimeType)||(s={mimeType:`video/webm;codecs=vp8`,videoBitsPerSecond:a},MediaRecorder.isTypeSupported(s.mimeType)||(s={mimeType:`video/webm`,videoBitsPerSecond:a}))));let c=null;ss=!1,is=!0,vs();let l=U.getClip().duration,u=Math.round(l*30),d=0,f=[],p=()=>{if(ss||d>=u){if(f.length===0){is=!1,G.aspect=B.clientWidth/B.clientHeight,G.updateProjectionMatrix(),K.setSize(B.clientWidth,B.clientHeight),As.setSize(B.clientWidth,B.clientHeight),e&&q!==void 0&&q.updateCamera(),vs();return}ss=!1,as=new MediaRecorder(i,s),os=[],as.ondataavailable=e=>{e.data.size>0&&os.push(e.data)},as.onstop=()=>{c&&clearTimeout(c),_s(new Blob(os,{type:s.mimeType.split(`;`)[0]}),`${ps()}.${o}`),is=!1,G.aspect=B.clientWidth/B.clientHeight,G.updateProjectionMatrix(),K.setSize(B.clientWidth,B.clientHeight),As.setSize(B.clientWidth,B.clientHeight),e&&q!==void 0&&q.updateCamera(),vs()},r.drawImage(f[0],0,0,512,512),as.start();let t=0,n=performance.now(),a=()=>{if(ss){as.stop();return}if(t<f.length){r.drawImage(f[t],0,0,512,512),t++;let e=n+1e3/30*t,i=Math.max(0,e-performance.now());c=setTimeout(a,i)}else c=setTimeout(()=>as.stop(),50)};c=setTimeout(a,0);return}if(U.time=d/30,H&&H.update(0),e){Z!==void 0&&(Z.visible=!1),Y!==void 0&&(Y.visible=!1),Q!==void 0&&(Q.visible=!1),$!==void 0&&($.visible=!1),q.setScene(W,G);let e=()=>{if(ss){p();return}let n=0;for(;q.samples<100&&n<5;)q.renderSample(),n++;if(q.samples>=100){let e=document.createElement(`canvas`);e.width=512,e.height=512,e.getContext(`2d`).drawImage(t,0,0,512,512),f.push(e),d++,setTimeout(p,0)}else requestAnimationFrame(e)};e()}else{let e=!Jo||Jo.checked;Z!==void 0&&(Z.visible=!0),Y!==void 0&&(Y.visible=!0),Q!==void 0&&(Q.visible=e),$!==void 0&&($.visible=e),As.render();let n=document.createElement(`canvas`);n.width=512,n.height=512,n.getContext(`2d`).drawImage(t,0,0,512,512),f.push(n),d++,setTimeout(p,0)}};p()}else alert(`Video recording is not supported in this browser.`)}else rs=!0};function ys(e){let t=e.length%4;return t===2?e+`==`:t===3?e+`=`:e}async function bs(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(/=+$/,``)}async function xs(e){if(!e)return null;let t=e.charAt(0),n=e.substring(1);if(n=ys(n.replace(/-/g,`+`).replace(/_/g,`/`)),t===`c`)try{let e=atob(n),t=new Uint8Array(e.length);for(let n=0;n<e.length;n++)t[n]=e.charCodeAt(n);let r=new Blob([t]).stream().pipeThrough(new DecompressionStream(`deflate-raw`)),i=await new Response(r).arrayBuffer();return new TextDecoder().decode(i)}catch(e){console.warn(`DecompressionStream failed`,e)}else if(t===`u`)try{return decodeURIComponent(escape(atob(n)))}catch(e){console.warn(`Unescape failed`,e)}else try{return decodeURIComponent(escape(atob(ys(e.replace(/-/g,`+`).replace(/_/g,`/`)))))}catch{return decodeURIComponent(e)}return null}No.onclick=async()=>{let e=z.value.trim(),t=e,n=new URL(window.location.href),r=``;if(Fo&&Fo.checked){let t=``,n=!1,r=0;for(;r<e.length;)if(n)t+=e[r],e[r]===`"`&&e[r-1]!==`\\`&&(n=!1),r++;else if(e[r]===`"`)n=!0,t+=e[r],r++;else if(e[r]===`/`&&e[r+1]===`/`)for(r+=2;r<e.length&&e[r]!==`
4238
+ `;)r++;else if(e[r]===`/`&&e[r+1]===`*`){for(r+=2;r<e.length&&(e[r]!==`*`||e[r+1]!==`/`);)r++;r+=2}else/\s/.test(e[r])?(t.length>0&&!/\s/.test(t[t.length-1])&&(t+=` `),r++):(t+=e[r],r++);e=t.trim()}if(!e||!cs&&t===`// Global detail setting
4209
4239
  $fn = 64;
4210
4240
 
4211
4241
  // Animation Data Dictionary
@@ -4304,15 +4334,30 @@ armature(animations=anim_data) {
4304
4334
 
4305
4335
  // Deep Volumetric Sapphire
4306
4336
  // Showcasing: $asa=0.0 (Preserves faceted "cut" look of the crystal)
4307
- color([1.0, 1.0, 1.0, 1.0], transmission=1.0, roughness=0.0, thickness=8.0, ior=1.76, attenuationColor=[0.1, 0.3, 0.9], attenuationDistance=3.0) {
4337
+ color([1.0, 1.0, 1.0, 1.0], transmission=1.0, roughness=0.0, thickness=8.0, ior=1.76, attenuationColor=[0.1, 0.3, 0.9], attenuationDistance=6.0, $asa=30) {
4338
+
4339
+ // Use difference() to carve out an inner cavity so the shell is hollow,
4340
+ // allowing physically correct index-of-refraction depth sorting.
4341
+ difference() {
4342
+ // Octagonal faceted crystal shape
4343
+ union() {
4344
+ cylinder(h=5, r1=4, r2=0, $fn=8);
4345
+ translate([0,0,-5]) cylinder(h=5, r1=0, r2=4, $fn=8);
4346
+ }
4347
+
4348
+ // Hollow cavity
4349
+ sphere(r=0.9, $fn=32);
4350
+ }
4351
+ }
4308
4352
 
4309
- // Octagonal faceted crystal shape
4310
- union() {
4311
- cylinder(h=5, r1=4, r2=0, $fn=8);
4312
- translate([0,0,-5]) cylinder(h=5, r1=0, r2=4, $fn=8);
4353
+ // 4. Glowing Core (Child of Gem Shell)
4354
+ // Placed inside its own bone to prevent auto-unioning with the transparent gem
4355
+ bone(name="Core", t=[0,0,0]) {
4356
+ color([0.0, 0.0, 0.0, 1.0], emissive=[0.5, 0.8, 1.0], emissiveIntensity=20.0, $asa=45) {
4357
+ sphere(r=0.6, $fn=32);
4313
4358
  }
4314
4359
  }
4315
4360
  }
4316
4361
  }
4317
4362
  }
4318
- }`)r=n.origin+n.pathname+n.search;else try{n.hash=await ys(e),r=n.toString()}catch{r=n.origin+n.pathname+n.search}window.history.replaceState(null,``,r);try{if(navigator.share){let e=Do.value.trim(),t=e?e.replace(/[-_]/g,` `).replace(/\b\w/g,e=>e.toUpperCase()):`Scadify`;await navigator.share({title:t,url:r});let n=No.innerText;No.innerText=`✅ Shared!`,setTimeout(()=>No.innerText=n,2e3)}else{await navigator.clipboard.writeText(r);let e=No.innerText;No.innerText=`✅ Copied Link!`,setTimeout(()=>No.innerText=e,2e3)}}catch(e){if(e.name!==`AbortError`)try{await navigator.clipboard.writeText(r),No.innerText=`✅ Copied Link!`,setTimeout(()=>No.innerText=`🔗 Share`,2e3)}catch{alert(`Failed to share or copy link.`)}}};var xs=document.getElementById(`drag-overlay`);window.addEventListener(`dragenter`,e=>{e.preventDefault(),xs.classList.add(`active`)}),window.addEventListener(`dragover`,e=>{e.preventDefault(),xs.classList.add(`active`)}),xs.addEventListener(`dragleave`,e=>{e.preventDefault(),xs.classList.remove(`active`)}),window.addEventListener(`drop`,async e=>{if(e.preventDefault(),xs.classList.remove(`active`),e.dataTransfer.files&&e.dataTransfer.files.length>0){let t=e.dataTransfer.files[0],n=t.name.toLowerCase();if(n.endsWith(`.hdr`)){let e=URL.createObjectURL(t);new Co().load(e,e=>{e.mapping=303,W.environment=e,Ps=e,J&&J.checked?(W.background=e,W.backgroundBlurriness=.5,W.backgroundIntensity=.3):(W.background=new ze(2236962),W.backgroundBlurriness=0,W.backgroundIntensity=1),q!==void 0&&(q.setScene(W,G),q.updateCamera())},void 0,e=>{console.warn(`Error loading HDR:`,e),alert(`Failed to load HDR file.`)})}else if(n.endsWith(`.scad`)||!t.type||t.type.includes(`text`))try{let e=await t.text();z.value=e,Ds(),ms(e)}catch(e){alert(`Failed to read file: `+e.message)}else alert(`Please drop a valid .scad or .hdr file.`)}});var Ss=null,Cs=``;async function ws(e){let t=await fetch(`${e}/api/scads`);if(!t.ok)throw Error(`Failed to fetch files`);return(await t.json()).files}function Ts(){Bo.innerHTML=`<option value="">-- Create New Model --</option>`,Array.isArray(Ss)&&Ss.forEach((e,t)=>{let n=document.createElement(`option`);n.value=t,n.innerText=e,Bo.appendChild(n)}),Bo.value=Zo,Ho&&(Ho.style.display=Zo===``?`none`:`flex`)}function Es(){let e=Do.value.trim();return e&&=e.replace(/\.scad$/i,``).split(/[/\\]/).pop()+`.scad`,{filename:e}}function Ds(){if(Ss===null)return;let e=Bo.value===``,{filename:t}=Es(),n=z.value,r=!1;e?(t&&t!==`.scad`||n.trim()!==``)&&(r=!0):n!==cs.content&&(r=!0),r?(Vo.style.display=`flex`,Vo.innerText=`Save`):Vo.style.display=`none`}async function Os(e,t=!1){if(!e)return!1;try{return Ro.innerText=`Connecting...`,Ss=await ws(e),Cs=e,ss=!0,Ro.innerText=`Connected`,zo.classList.add(`active`),z.placeholder=`Enter OpenSCAD code here...`,Zo=``,Ts(),cs={isNew:!0,content:``},Ds(),!0}catch(e){return t?console.warn(`Auto-connect failed:`,e.message):alert(`Connection failed: `+e.message),Ro.innerText=`Connect`,zo.classList.remove(`active`),ss=!1,z.placeholder=wo,!1}}Ro.onclick=async()=>{await Os(Lo.value.trim(),!1)&&ms(ds())},Bo.addEventListener(`change`,async()=>{if(Vo.style.display!==`none`&&!confirm(`You have unsaved changes. Are you sure you want to discard them and load another model?`)){Bo.value=Zo;return}let e=Bo.value;if(Zo=e,Ho&&(Ho.style.display=e===``?`none`:`flex`),e===``)Do.value=``,z.value=``,ms(ds()),cs={isNew:!0,content:``},Ds();else{let t=Ss[e];Do.value=t.replace(/\.scad$/i,``);try{let e=await fetch(`${Cs}/api/scads/${encodeURIComponent(t)}`);if(!e.ok){let t=await e.json();throw Error(t.error||`Failed to load model`)}let n=await e.json();z.value=n.content,ms(ds()),cs={isNew:!1,content:n.content},Ds()}catch(e){alert(`Error loading model: `+e.message)}}}),Vo.onclick=async()=>{let{filename:e}=Es();if(!e||e===`.scad`){let e=Vo.innerText;Vo.innerText=`⚠️ Name Required!`,Vo.style.borderColor=`#ff4444`,Do.focus(),setTimeout(()=>{Vo.innerText=e,Vo.style.borderColor=``},2e3);return}if(Bo.value===``&&Ss&&Ss.includes(e)&&!confirm(`A model named "${e}" already exists. Are you sure you want to overwrite it?`))return;let t={filename:e,content:z.value};try{Vo.innerText=`Saving...`;let n=await fetch(`${Cs}/api/scads`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)});if(!n.ok){let e=await n.json();throw Error(e.error||`Save failed`)}Ss=await ws(Cs);let r=Ss.findIndex(t=>t===e);Zo=r>=0?r.toString():``,Ts(),cs={isNew:!1,content:z.value},Ds()}catch(e){Ds(),alert(`Error: `+e.message)}},Ho.onclick=async()=>{let e=Bo.value;if(e===``)return;let t=Ss[e];if(confirm(`Are you sure you want to delete "${t}"?`))try{Ho.innerText=`Deleting...`;let e=await fetch(`${Cs}/api/scads/${encodeURIComponent(t)}`,{method:`DELETE`});if(!e.ok){let t=`Delete failed`;try{let n=await e.json();n.error&&(t=n.error)}catch{}throw Error(t)}Ss=await ws(Cs),Zo=``,Ts(),Do.value=``,z.value=``,ms(ds()),cs={isNew:!0,content:``},Ds(),Ho.innerText=`Delete`}catch(e){alert(`Error: `+e.message),Ho.innerText=`Delete`}};var W=new le;W.background=new ze(2236962);var G=new c(60,B.clientWidth/B.clientHeight,.1,2e3);G.position.set(50,50,-50);var K=new Fe({antialias:!0,preserveDrawingBuffer:!0});K.setSize(B.clientWidth,B.clientHeight),K.setPixelRatio(Math.min(window.devicePixelRatio,2)),K.toneMapping=4,K.toneMappingExposure=1,K.shadowMap.enabled=!0,K.shadowMap.type=2,B.appendChild(K.domElement);var ks=new he(K),As=new ke(W,G);ks.addPass(As);var js=new Se(new E(B.clientWidth,B.clientHeight),.1,.1,2);ks.addPass(js);var Ms=new Ne;ks.addPass(Ms);var q=new So(K);q.bounces=10,q.transmissiveBounces=10,q.multipleImportanceSampling=!0,q.filterGlossyFactor=.2;var Ns=new je(G,K.domElement);Ns.addEventListener(`change`,()=>{q!==void 0&&q.updateCamera()});var Ps=null;new Co().load(`./aristea_wreck_puresky_2k.hdr`,e=>{e.mapping=303,W.environment=e,Ps=e;let t=J&&J.checked,n=!Jo||Jo.checked;Z!==void 0&&(Z.visible=!t),Y!==void 0&&(Y.visible=!t),Q!==void 0&&(Q.visible=!t&&n),$!==void 0&&($.visible=!t&&n),t?(W.background=e,W.backgroundBlurriness=.5,W.backgroundIntensity=.3):(W.background=new ze(2236962),W.backgroundBlurriness=0,W.backgroundIntensity=1),q!==void 0&&(q.setScene(W,G),q.updateCamera())},void 0,e=>console.warn(`Error loading HDR:`,e));var J=document.getElementById(`path-tracing-cb`);J&&J.addEventListener(`change`,()=>{let e=J.checked,t=!Jo||Jo.checked;Z!==void 0&&(Z.visible=!e),Y!==void 0&&(Y.visible=!e),Q!==void 0&&(Q.visible=!e&&t),$!==void 0&&($.visible=!e&&t),e?Ps&&(W.background=Ps,W.backgroundBlurriness=.5,W.backgroundIntensity=.3):(W.background=new ze(2236962),W.backgroundBlurriness=0,W.backgroundIntensity=1),e&&q!==void 0&&q.setScene(W,G)}),Ns.enableDamping=!0,Ns.dampingFactor=.1,Ns.maxDistance=2e3,W.environment=new ye(K).fromScene(new ne,.04).texture,W.environmentIntensity=.8;var Y=new h;W.add(Y);var X=new Le(16777215,1.5);X.castShadow=!0,X.shadow.mapSize.width=2048,X.shadow.mapSize.height=2048,X.shadow.bias=-5e-4,Y.add(X),Y.add(X.target);var Fs=new t(16777215,4473924,.6);Fs.position.set(0,20,0),Y.add(Fs);var Z=new v(new C(2e3,2e3),new n({color:2236962,roughness:.8,metalness:.1}));Z.rotation.x=-Math.PI/2,Z.receiveShadow=!0,W.add(Z);var Q=new i(2e3,100,5592405,3355443);Q.material.transparent=!0,Q.material.opacity=.5,W.add(Q);var $=new ue(100);W.add($),Yo&&Yo.addEventListener(`change`,()=>{let e=Yo.checked;V&&V.traverse(t=>{t.isMesh&&t.material&&(Array.isArray(t.material)?t.material.forEach(t=>{t.wireframe=e}):t.material.wireframe=e)})}),Xo&&Io&&(Xo.addEventListener(`click`,()=>{document.fullscreenElement?document.exitFullscreen():Io.requestFullscreen().catch(e=>{console.warn(`Error attempting to enable fullscreen: ${e.message}`)})}),document.addEventListener(`fullscreenchange`,()=>{setTimeout(()=>window.dispatchEvent(new Event(`resize`)),100)}));function Is(){U&&(ls=!1,U.paused=!0,Wo.innerText=`▶ Play`,_s())}function Ls(e){if(!H||!$o[e])return;U&&U.stop();let t=$o[e];U=H.clipAction(t),U.play(),U.time=0,H.update(0),ls=!0,U.paused=!1,Wo.innerText=`⏸ Pause`,Ko.value=0,_s()}Go.addEventListener(`change`,e=>{Ls(parseInt(e.target.value))}),Wo.addEventListener(`click`,()=>{U&&(ls=!ls,U.paused=!ls,Wo.innerText=ls?`⏸ Pause`:`▶ Play`,_s())}),Ko.addEventListener(`mousedown`,()=>{us=!0,Is()}),Ko.addEventListener(`mouseup`,()=>{us=!1}),Ko.addEventListener(`touchstart`,()=>{us=!0,Is()},{passive:!0}),Ko.addEventListener(`touchend`,()=>{us=!1},{passive:!0}),Ko.addEventListener(`input`,e=>{if(Is(),U){let t=U.getClip().duration;U.time=parseFloat(e.target.value)*t,H&&(H.update(0),J&&J.checked&&q!==void 0&&(Z!==void 0&&(Z.visible=!1),Y!==void 0&&(Y.visible=!1),Q!==void 0&&(Q.visible=!1),$!==void 0&&($.visible=!1),q.setScene(W,G)))}});function Rs(){rs&&is&&is.state!==`inactive`&&(is.stop(),rs=!1),V&&=(H&&=(H.stopAllAction(),H.uncacheRoot(H.getRoot()),null),U=null,W.remove(V),V.traverse(e=>{e.isMesh&&(e.geometry.dispose(),Array.isArray(e.material)?e.material.forEach(e=>e.dispose()):e.material&&e.material.dispose())}),null),_s()}function zs(e,t){if(!e)return new Ee;e.updateMatrixWorld(!0);let n=new Ee().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 se(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 Bs(e){return new Promise((t,n)=>{let r=null;V&&(H&&U&&(U.time=0,H.update(0),J&&J.checked&&q!==void 0&&(Z!==void 0&&(Z.visible=!1),Y!==void 0&&(Y.visible=!1),Q!==void 0&&(Q.visible=!1),$!==void 0&&($.visible=!1),q.setScene(W,G))),r=zs(V,$o)),Rs();let i=e;e instanceof Uint8Array&&(i=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)),new we().parse(i,``,e=>{V=e.scene,$o=e.animations||[];let n=Yo?Yo.checked:!1;$o.length?(H=new se(V),Uo.style.display=`flex`,Go.innerHTML=``,$o.forEach((e,t)=>{let n=document.createElement(`option`);n.value=t,n.innerText=e.name||`Animation ${t+1}`,Go.appendChild(n)}),Ls(0)):(Uo.style.display=`none`,U=null,_s()),V.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))}),W.add(V);let i=!0;if(r&&!r.isEmpty()){let e=zs(V,$o);if(!e.isEmpty()){let t=.001;r.min.distanceTo(e.min)<t&&r.max.distanceTo(e.max)<t&&(i=!1)}}if(i)typeof Vs==`function`&&Vs();else{let e=J&&J.checked,t=!Jo||Jo.checked;Z!==void 0&&(Z.visible=!e),Y!==void 0&&(Y.visible=!e),Q!==void 0&&(Q.visible=!e&&t),$!==void 0&&($.visible=!e&&t),q!==void 0&&q.setScene(W,G)}t()},n)})}function Vs(){if(!V)return;let e=zs(V,$o);if(e.isEmpty())return;let t=e.getCenter(new D),n=e.getSize(new D),r=Math.max(n.x,n.y,n.z)||10;Z.position.y=e.min.y-.01,Q.position.y=Z.position.y+.001,$.position.y=Z.position.y+.002;let i=G.fov*(Math.PI/180),o=r/(2*Math.tan(i/2));G.aspect<1&&(o/=G.aspect),o*=1.5,G.near=Math.max(.01,r*.01),G.far=Math.max(2e3,o*10),G.updateProjectionMatrix(),Ns.maxDistance=G.far,G.position.set(t.x+o*.8,t.y+o*.8,t.z-o*.8),G.lookAt(t),Ns.target.copy(t),Ns.update(),X.position.set(t.x+r,t.y+r*1.5,t.z-r),X.target.position.copy(t),X.target.updateMatrixWorld();let s=r*1.5;X.shadow.camera.left=-s,X.shadow.camera.right=s,X.shadow.camera.top=s,X.shadow.camera.bottom=-s,X.shadow.camera.near=.1,X.shadow.camera.far=r*5,X.shadow.camera.updateProjectionMatrix(),W.fog=new a(2236962,o*1.5,o*5);let c=J&&J.checked,l=!Jo||Jo.checked;Z!==void 0&&(Z.visible=!c),Y!==void 0&&(Y.visible=!c),Q!==void 0&&(Q.visible=!c&&l),$!==void 0&&($.visible=!c&&l),q!==void 0&&q.setScene(W,G)}var Hs=performance.now();function Us(){requestAnimationFrame(Us);let e=performance.now(),t=(e-Hs)/1e3;if(Hs=e,H&&(!(J&&J.checked)&&!rs&&H.update(t),U&&ls&&!us)){let e=U.getClip().duration;if(e>0){let t=U.time%e;t<0&&(t+=e),Ko.value=t/e}}Ns.update();let n=J&&J.checked,r=!Jo||Jo.checked;n?(Z!==void 0&&(Z.visible=!1),Y!==void 0&&(Y.visible=!1),Q!==void 0&&(Q.visible=!1),$!==void 0&&($.visible=!1),q!==void 0&&!rs&&q.renderSample()):(Z!==void 0&&(Z.visible=!0),Y!==void 0&&(Y.visible=!0),Q!==void 0&&(Q.visible=r),$!==void 0&&($.visible=r),rs||ks.render()),ns&&(ns=!1,K.domElement.toBlob(e=>{e&&gs(e,`${fs()}.png`)},`image/png`))}Us(),window.addEventListener(`resize`,()=>{if(!B||rs)return;let e=B.clientWidth,t=B.clientHeight;G.aspect=e/t,G.updateProjectionMatrix(),K.setSize(e,t),ks.setSize(e,t)}),setTimeout(()=>window.dispatchEvent(new Event(`resize`)),100);var Ws=[`localhost`,`127.0.0.1`,``].includes(window.location.hostname);z.placeholder=Ws?`Enter OpenSCAD code here...`:wo,(async function(){if(Ws&&await Os(Lo.value.trim(),!0),window.location.hash&&window.location.hash.length>1)try{let e=await bs(window.location.hash.substring(1));e&&(z.value=e)}catch(e){console.error(`Failed to decode hash`,e)}Ds(),setTimeout(()=>ms(ds()),500)})();
4363
+ }`)r=n.origin+n.pathname+n.search;else try{n.hash=await bs(e),r=n.toString()}catch{r=n.origin+n.pathname+n.search}window.history.replaceState(null,``,r);try{if(navigator.share){let e=Do.value.trim(),t=e?e.replace(/[-_]/g,` `).replace(/\b\w/g,e=>e.toUpperCase()):`Scadify`;await navigator.share({title:t,url:r});let n=No.innerText;No.innerText=`✅ Shared!`,setTimeout(()=>No.innerText=n,2e3)}else{await navigator.clipboard.writeText(r);let e=No.innerText;No.innerText=`✅ Copied Link!`,setTimeout(()=>No.innerText=e,2e3)}}catch(e){if(e.name!==`AbortError`)try{await navigator.clipboard.writeText(r),No.innerText=`✅ Copied Link!`,setTimeout(()=>No.innerText=`🔗 Share`,2e3)}catch{alert(`Failed to share or copy link.`)}}};var Ss=document.getElementById(`drag-overlay`);window.addEventListener(`dragenter`,e=>{e.preventDefault(),Ss.classList.add(`active`)}),window.addEventListener(`dragover`,e=>{e.preventDefault(),Ss.classList.add(`active`)}),Ss.addEventListener(`dragleave`,e=>{e.preventDefault(),Ss.classList.remove(`active`)}),window.addEventListener(`drop`,async e=>{if(e.preventDefault(),Ss.classList.remove(`active`),e.dataTransfer.files&&e.dataTransfer.files.length>0){let t=e.dataTransfer.files[0],n=t.name.toLowerCase();if(n.endsWith(`.hdr`)){let e=URL.createObjectURL(t);new Co().load(e,e=>{e.mapping=303,W.environment=e,Fs=e,J&&J.checked?(W.background=e,W.backgroundBlurriness=.5,W.backgroundIntensity=.3):(W.background=new ze(2236962),W.backgroundBlurriness=0,W.backgroundIntensity=1),q!==void 0&&(q.setScene(W,G),q.updateCamera())},void 0,e=>{console.warn(`Error loading HDR:`,e),alert(`Failed to load HDR file.`)})}else if(n.endsWith(`.scad`)||!t.type||t.type.includes(`text`))try{let e=await t.text();z.value=e,Os(),hs(e)}catch(e){alert(`Failed to read file: `+e.message)}else alert(`Please drop a valid .scad or .hdr file.`)}});var Cs=null,ws=``;async function Ts(e){let t=await fetch(`${e}/api/scads`);if(!t.ok)throw Error(`Failed to fetch files`);return(await t.json()).files}function Es(){Bo.innerHTML=`<option value="">-- Create New Model --</option>`,Array.isArray(Cs)&&Cs.forEach((e,t)=>{let n=document.createElement(`option`);n.value=t,n.innerText=e,Bo.appendChild(n)}),Bo.value=Qo,Ho&&(Ho.style.display=Qo===``?`none`:`flex`)}function Ds(){let e=Do.value.trim();return e&&=e.replace(/\.scad$/i,``).split(/[/\\]/).pop()+`.scad`,{filename:e}}function Os(){if(Cs===null)return;let e=Bo.value===``,{filename:t}=Ds(),n=z.value,r=!1;e?(t&&t!==`.scad`||n.trim()!==``)&&(r=!0):n!==ls.content&&(r=!0),r?(Vo.style.display=`flex`,Vo.innerText=`Save`):Vo.style.display=`none`}async function ks(e,t=!1){if(!e)return!1;try{return Ro.innerText=`Connecting...`,Cs=await Ts(e),ws=e,cs=!0,Ro.innerText=`Connected`,zo.classList.add(`active`),z.placeholder=`Enter OpenSCAD code here...`,Qo=``,Es(),ls={isNew:!0,content:``},Os(),!0}catch(e){return t?console.warn(`Auto-connect failed:`,e.message):alert(`Connection failed: `+e.message),Ro.innerText=`Connect`,zo.classList.remove(`active`),cs=!1,z.placeholder=wo,!1}}Ro.onclick=async()=>{await ks(Lo.value.trim(),!1)&&hs(fs())},Bo.addEventListener(`change`,async()=>{if(Vo.style.display!==`none`&&!confirm(`You have unsaved changes. Are you sure you want to discard them and load another model?`)){Bo.value=Qo;return}let e=Bo.value;if(Qo=e,Ho&&(Ho.style.display=e===``?`none`:`flex`),e===``)Do.value=``,z.value=``,hs(fs()),ls={isNew:!0,content:``},Os();else{let t=Cs[e];Do.value=t.replace(/\.scad$/i,``);try{let e=await fetch(`${ws}/api/scads/${encodeURIComponent(t)}`);if(!e.ok){let t=await e.json();throw Error(t.error||`Failed to load model`)}let n=await e.json();z.value=n.content,hs(fs()),ls={isNew:!1,content:n.content},Os()}catch(e){alert(`Error loading model: `+e.message)}}}),Vo.onclick=async()=>{let{filename:e}=Ds();if(!e||e===`.scad`){let e=Vo.innerText;Vo.innerText=`⚠️ Name Required!`,Vo.style.borderColor=`#ff4444`,Do.focus(),setTimeout(()=>{Vo.innerText=e,Vo.style.borderColor=``},2e3);return}if(Bo.value===``&&Cs&&Cs.includes(e)&&!confirm(`A model named "${e}" already exists. Are you sure you want to overwrite it?`))return;let t={filename:e,content:z.value};try{Vo.innerText=`Saving...`;let n=await fetch(`${ws}/api/scads`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(t)});if(!n.ok){let e=await n.json();throw Error(e.error||`Save failed`)}Cs=await Ts(ws);let r=Cs.findIndex(t=>t===e);Qo=r>=0?r.toString():``,Es(),ls={isNew:!1,content:z.value},Os()}catch(e){Os(),alert(`Error: `+e.message)}},Ho.onclick=async()=>{let e=Bo.value;if(e===``)return;let t=Cs[e];if(confirm(`Are you sure you want to delete "${t}"?`))try{Ho.innerText=`Deleting...`;let e=await fetch(`${ws}/api/scads/${encodeURIComponent(t)}`,{method:`DELETE`});if(!e.ok){let t=`Delete failed`;try{let n=await e.json();n.error&&(t=n.error)}catch{}throw Error(t)}Cs=await Ts(ws),Qo=``,Es(),Do.value=``,z.value=``,hs(fs()),ls={isNew:!0,content:``},Os(),Ho.innerText=`Delete`}catch(e){alert(`Error: `+e.message),Ho.innerText=`Delete`}};var W=new le;W.background=new ze(2236962);var G=new c(60,B.clientWidth/B.clientHeight,.1,2e3);G.position.set(50,50,-50);var K=new Fe({antialias:!0,preserveDrawingBuffer:!0});K.setSize(B.clientWidth,B.clientHeight),K.setPixelRatio(Math.min(window.devicePixelRatio,2)),K.toneMapping=4,K.toneMappingExposure=1,K.shadowMap.enabled=!0,K.shadowMap.type=2,B.appendChild(K.domElement);var As=new he(K),js=new ke(W,G);As.addPass(js);var Ms=new Se(new E(B.clientWidth,B.clientHeight),.1,.1,2);As.addPass(Ms);var Ns=new Ne;As.addPass(Ns);var q=new So(K);q.bounces=10,q.transmissiveBounces=10,q.multipleImportanceSampling=!0,q.filterGlossyFactor=.2;var Ps=new je(G,K.domElement);Ps.addEventListener(`change`,()=>{q!==void 0&&q.updateCamera()});var Fs=null;new Co().load(`./aristea_wreck_puresky_2k.hdr`,e=>{e.mapping=303,W.environment=e,Fs=e;let t=J&&J.checked,n=!Jo||Jo.checked;Z!==void 0&&(Z.visible=!t),Y!==void 0&&(Y.visible=!t),Q!==void 0&&(Q.visible=!t&&n),$!==void 0&&($.visible=!t&&n),t?(W.background=e,W.backgroundBlurriness=.5,W.backgroundIntensity=.3):(W.background=new ze(2236962),W.backgroundBlurriness=0,W.backgroundIntensity=1),q!==void 0&&(q.setScene(W,G),q.updateCamera())},void 0,e=>console.warn(`Error loading HDR:`,e));var J=document.getElementById(`path-tracing-cb`);J&&J.addEventListener(`change`,()=>{let e=J.checked,t=!Jo||Jo.checked;Z!==void 0&&(Z.visible=!e),Y!==void 0&&(Y.visible=!e),Q!==void 0&&(Q.visible=!e&&t),$!==void 0&&($.visible=!e&&t),e?Fs&&(W.background=Fs,W.backgroundBlurriness=.5,W.backgroundIntensity=.3):(W.background=new ze(2236962),W.backgroundBlurriness=0,W.backgroundIntensity=1),e&&q!==void 0&&q.setScene(W,G)}),Ps.enableDamping=!0,Ps.dampingFactor=.1,Ps.maxDistance=2e3,W.environment=new ye(K).fromScene(new ne,.04).texture,W.environmentIntensity=.8;var Y=new h;W.add(Y);var X=new Le(16777215,1.5);X.castShadow=!0,X.shadow.mapSize.width=2048,X.shadow.mapSize.height=2048,X.shadow.bias=-5e-4,Y.add(X),Y.add(X.target);var Is=new t(16777215,4473924,.6);Is.position.set(0,20,0),Y.add(Is);var Z=new v(new C(2e3,2e3),new n({color:2236962,roughness:.8,metalness:.1}));Z.rotation.x=-Math.PI/2,Z.receiveShadow=!0,W.add(Z);var Q=new i(2e3,100,5592405,3355443);Q.material.transparent=!0,Q.material.opacity=.5,W.add(Q);var $=new ue(100);W.add($),Yo&&Yo.addEventListener(`change`,()=>{let e=Yo.checked;V&&V.traverse(t=>{t.isMesh&&t.material&&(Array.isArray(t.material)?t.material.forEach(t=>{t.wireframe=e}):t.material.wireframe=e)})}),Xo&&Io&&(Xo.addEventListener(`click`,()=>{document.fullscreenElement?document.exitFullscreen():Io.requestFullscreen().catch(e=>{console.warn(`Error attempting to enable fullscreen: ${e.message}`)})}),document.addEventListener(`fullscreenchange`,()=>{setTimeout(()=>window.dispatchEvent(new Event(`resize`)),100)})),Zo&&Zo.addEventListener(`click`,()=>{rs=!0;let e=Zo.innerText;Zo.innerText=`✅`,setTimeout(()=>{Zo.innerText=e},1e3)});function Ls(){U&&(us=!1,U.paused=!0,Wo.innerText=`▶ Play`,vs())}function Rs(e){if(!H||!es[e])return;U&&U.stop();let t=es[e];U=H.clipAction(t),U.play(),U.time=0,H.update(0),us=!0,U.paused=!1,Wo.innerText=`⏸ Pause`,Ko.value=0,vs()}Go.addEventListener(`change`,e=>{Rs(parseInt(e.target.value))}),Wo.addEventListener(`click`,()=>{U&&(us=!us,U.paused=!us,Wo.innerText=us?`⏸ Pause`:`▶ Play`,vs())}),Ko.addEventListener(`mousedown`,()=>{ds=!0,Ls()}),Ko.addEventListener(`mouseup`,()=>{ds=!1}),Ko.addEventListener(`touchstart`,()=>{ds=!0,Ls()},{passive:!0}),Ko.addEventListener(`touchend`,()=>{ds=!1},{passive:!0}),Ko.addEventListener(`input`,e=>{if(Ls(),U){let t=U.getClip().duration;U.time=parseFloat(e.target.value)*t,H&&(H.update(0),J&&J.checked&&q!==void 0&&(Z!==void 0&&(Z.visible=!1),Y!==void 0&&(Y.visible=!1),Q!==void 0&&(Q.visible=!1),$!==void 0&&($.visible=!1),q.setScene(W,G)))}});function zs(){is&&as&&as.state!==`inactive`&&(as.stop(),is=!1),V&&=(H&&=(H.stopAllAction(),H.uncacheRoot(H.getRoot()),null),U=null,W.remove(V),V.traverse(e=>{e.isMesh&&(e.geometry.dispose(),Array.isArray(e.material)?e.material.forEach(e=>e.dispose()):e.material&&e.material.dispose())}),null),vs()}function Bs(e,t){if(!e)return new Ee;e.updateMatrixWorld(!0);let n=new Ee().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 se(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 Vs(e){return new Promise((t,n)=>{let r=null;V&&(H&&U&&(U.time=0,H.update(0),J&&J.checked&&q!==void 0&&(Z!==void 0&&(Z.visible=!1),Y!==void 0&&(Y.visible=!1),Q!==void 0&&(Q.visible=!1),$!==void 0&&($.visible=!1),q.setScene(W,G))),r=Bs(V,es)),zs();let i=e;e instanceof Uint8Array&&(i=e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)),new we().parse(i,``,e=>{V=e.scene,es=e.animations||[];let n=Yo?Yo.checked:!1;es.length?(H=new se(V),Uo.style.display=`flex`,Go.innerHTML=``,es.forEach((e,t)=>{let n=document.createElement(`option`);n.value=t,n.innerText=e.name||`Animation ${t+1}`,Go.appendChild(n)}),Rs(0)):(Uo.style.display=`none`,U=null,vs()),V.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))}),W.add(V);let i=!0;if(r&&!r.isEmpty()){let e=Bs(V,es);if(!e.isEmpty()){let t=.001;r.min.distanceTo(e.min)<t&&r.max.distanceTo(e.max)<t&&(i=!1)}}if(i)typeof Hs==`function`&&Hs();else{let e=J&&J.checked,t=!Jo||Jo.checked;Z!==void 0&&(Z.visible=!e),Y!==void 0&&(Y.visible=!e),Q!==void 0&&(Q.visible=!e&&t),$!==void 0&&($.visible=!e&&t),q!==void 0&&q.setScene(W,G)}t()},n)})}function Hs(){if(!V)return;let e=Bs(V,es);if(e.isEmpty())return;let t=e.getCenter(new D),n=e.getSize(new D),r=Math.max(n.x,n.y,n.z)||10;Z.position.y=e.min.y-.01,Q.position.y=Z.position.y+.001,$.position.y=Z.position.y+.002;let i=G.fov*(Math.PI/180),o=r/(2*Math.tan(i/2));G.aspect<1&&(o/=G.aspect),o*=1.5,G.near=Math.max(.01,r*.01),G.far=Math.max(2e3,o*10),G.updateProjectionMatrix(),Ps.maxDistance=G.far,G.position.set(t.x+o*.8,t.y+o*.8,t.z-o*.8),G.lookAt(t),Ps.target.copy(t),Ps.update(),X.position.set(t.x+r,t.y+r*1.5,t.z-r),X.target.position.copy(t),X.target.updateMatrixWorld();let s=r*1.5;X.shadow.camera.left=-s,X.shadow.camera.right=s,X.shadow.camera.top=s,X.shadow.camera.bottom=-s,X.shadow.camera.near=.1,X.shadow.camera.far=r*5,X.shadow.camera.updateProjectionMatrix(),W.fog=new a(2236962,o*1.5,o*5);let c=J&&J.checked,l=!Jo||Jo.checked;Z!==void 0&&(Z.visible=!c),Y!==void 0&&(Y.visible=!c),Q!==void 0&&(Q.visible=!c&&l),$!==void 0&&($.visible=!c&&l),q!==void 0&&q.setScene(W,G)}var Us=performance.now();function Ws(){requestAnimationFrame(Ws);let e=performance.now(),t=(e-Us)/1e3;if(Us=e,H&&(!(J&&J.checked)&&!is&&H.update(t),U&&us&&!ds)){let e=U.getClip().duration;if(e>0){let t=U.time%e;t<0&&(t+=e),Ko.value=t/e}}Ps.update();let n=J&&J.checked,r=!Jo||Jo.checked;n?(Z!==void 0&&(Z.visible=!1),Y!==void 0&&(Y.visible=!1),Q!==void 0&&(Q.visible=!1),$!==void 0&&($.visible=!1),q!==void 0&&!is&&q.renderSample()):(Z!==void 0&&(Z.visible=!0),Y!==void 0&&(Y.visible=!0),Q!==void 0&&(Q.visible=r),$!==void 0&&($.visible=r),is||As.render()),rs&&(rs=!1,K.domElement.toBlob(e=>{e&&_s(e,`${ps()}.png`)},`image/png`))}Ws(),window.addEventListener(`resize`,()=>{if(!B||is)return;let e=B.clientWidth,t=B.clientHeight;G.aspect=e/t,G.updateProjectionMatrix(),K.setSize(e,t),As.setSize(e,t),q!==void 0&&q.updateCamera()}),setTimeout(()=>window.dispatchEvent(new Event(`resize`)),100);var Gs=[`localhost`,`127.0.0.1`,``].includes(window.location.hostname);z.placeholder=Gs?`Enter OpenSCAD code here...`:wo,(async function(){if(Gs&&await ks(Lo.value.trim(),!0),window.location.hash&&window.location.hash.length>1)try{let e=await xs(window.location.hash.substring(1));e&&(z.value=e)}catch(e){console.error(`Failed to decode hash`,e)}Os(),setTimeout(()=>hs(fs()),500)})();
Binary file
@@ -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-CXqDtloH.js"></script>
47
+ <script type="module" crossorigin src="./assets/index-Cy6q5gqL.js"></script>
48
48
  <link rel="modulepreload" crossorigin href="./assets/OutputPass-Bvl6NigM.js">
49
49
  <link rel="modulepreload" crossorigin href="./assets/prompt-ui-mDTjxcQO.js">
50
- <link rel="stylesheet" crossorigin href="./assets/index-t9MYrExo.css">
50
+ <link rel="stylesheet" crossorigin href="./assets/index-B1KAb97r.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">
@@ -174,6 +174,7 @@
174
174
  ><input type="checkbox" id="show-grid-cb" checked /> Grid</label
175
175
  >
176
176
  <label><input type="checkbox" id="wireframe-cb" /> Wireframe</label>
177
+ <button id="screenshot-btn" title="Capture Image">📷</button>
177
178
  <button id="fullscreen-btn" title="Toggle Full Screen">⛶</button>
178
179
  </div>
179
180
  <div id="viewer"></div>
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:"c90d9c25847a398e770ccae0a6dad222"},{url:"index.html",revision:"3bb2e87bf61bcfac35db086f0fbc4c89"},{url:"icon.png",revision:"20a5bd64b0ab560837e6333f5afd2bf2"},{url:"content.js",revision:"fe4d9df0d512444e8fd3e28dce98b2ad"},{url:"content.css",revision:"8a04232febb2faba7b10edc60e2974dd"},{url:"content-loader.js",revision:"d94f1755552d49c1fe3a6da123e8a346"},{url:"aristea_wreck_puresky_2k.hdr",revision:"e764c66f871ab0987f3fac422edc841d"},{url:"assets/prompt-ui-mDTjxcQO.js",revision:null},{url:"assets/preview-fQfL_FJ-.css",revision:null},{url:"assets/preview-C1zc24MJ.js",revision:null},{url:"assets/openscad-CdBCY4mx.wasm",revision:null},{url:"assets/index-t9MYrExo.css",revision:null},{url:"assets/index-CXqDtloH.js",revision:null},{url:"assets/OutputPass-Bvl6NigM.js",revision:null},{url:"icon.png",revision:"20a5bd64b0ab560837e6333f5afd2bf2"},{url:"manifest.webmanifest",revision:"81dc3433323859b43ec568f4f95cd344"}],{}),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 l=e||("document"in self?document.currentScript.src:"")||location.href;if(s[l])return;let t={};const o=e=>i(e,l),c={module:{uri:l},exports:t,require:o};s[l]=Promise.all(r.map(e=>c[e]||o(e))).then(e=>(n(...e),t))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"402b66900e731ca748771b6fc5e7a068"},{url:"preview.html",revision:"c90d9c25847a398e770ccae0a6dad222"},{url:"index.html",revision:"bb603511d628bd2d3f2ded6c824c31b1"},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"content.js",revision:"fe4d9df0d512444e8fd3e28dce98b2ad"},{url:"content.css",revision:"8a04232febb2faba7b10edc60e2974dd"},{url:"content-loader.js",revision:"d94f1755552d49c1fe3a6da123e8a346"},{url:"aristea_wreck_puresky_2k.hdr",revision:"e764c66f871ab0987f3fac422edc841d"},{url:"assets/prompt-ui-mDTjxcQO.js",revision:null},{url:"assets/preview-fQfL_FJ-.css",revision:null},{url:"assets/preview-C1zc24MJ.js",revision:null},{url:"assets/openscad-CdBCY4mx.wasm",revision:null},{url:"assets/index-Cy6q5gqL.js",revision:null},{url:"assets/index-B1KAb97r.css",revision:null},{url:"assets/OutputPass-Bvl6NigM.js",revision:null},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"manifest.webmanifest",revision:"81dc3433323859b43ec568f4f95cd344"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ilia Grigorev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -5,3 +5,4 @@ description="Imports .scad files directly as 3D scenes using scad-gltf"
5
5
  author="Ilia Grigorev"
6
6
  version="0.1"
7
7
  script="scad_plugin.gd"
8
+ license="MIT"
@@ -1,3 +1,6 @@
1
+ # Copyright (c) 2026 Ilia Grigorev. Distributed under the MIT License.
2
+ # See LICENSE in the addon directory for details.
3
+
1
4
  @tool
2
5
  extends EditorSceneFormatImporter
3
6
 
@@ -1,3 +1,6 @@
1
+ # Copyright (c) 2026 Ilia Grigorev. Distributed under the MIT License.
2
+ # See LICENSE in the addon directory for details.
3
+
1
4
  @tool
2
5
  extends EditorPlugin
3
6
 
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ilia Grigorev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -80,3 +80,7 @@ scad-godot "A simple 3D platformer where you control a rolling ball collecting c
80
80
  ```
81
81
 
82
82
  _(See the main repository documentation for full details on generating your own AI projects)._
83
+
84
+ ## 📜 License
85
+
86
+ These examples and their generated Godot project assets are licensed under the [MIT License](LICENSE).