scad-gltf 0.3.0 → 0.3.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.
package/README.md CHANGED
@@ -18,12 +18,12 @@ The C++ source code for this custom OpenSCAD version is included directly in thi
18
18
  - **Texture Baking:** Automatically generate UVs and bake high-poly details (colors, normals, ORM) onto low-poly meshes using the new `bake()` module.
19
19
  - **Web Editor & Real-time Viewer (Scadify):** In-browser IDE with live WebAssembly compilation, GPU path tracing, animation timeline scrubbing, video/image export, URL sharing, and `.scad` / `.hdr` drag-and-drop.
20
20
  - **LLM Friendly:** Includes a built-in modular prompt generator (`prompt.js` and Web UI) to help AI models (like Gemini, Claude, or ChatGPT) write compatible OpenSCAD scripts utilizing the new features.
21
- - **Local API Server & Editor:** Bundled `scad-serve` CLI utility to manage local `.scad` files remotely via REST API with automatic `include`/`use` dependency resolution.
22
- - **CLI Converter:** Bundled `scad-convert` CLI utility for single file and batch compiling `.scad` files with smart dependency hashing.
23
- - **MCP Server for AI Agents:** Bundled `scad-mcp` server enables MCP clients to iteratively design, compile, and **visually inspect** 3D models via multi-angle headless rendering and animation frame evaluation.
21
+ - **Local API Server & Editor:** Bundled `scad-gltf serve` CLI utility to manage local `.scad` files remotely via REST API with automatic `include`/`use` dependency resolution.
22
+ - **CLI Converter:** Bundled `scad-gltf convert` CLI utility for single file and batch compiling `.scad` files with smart dependency hashing.
23
+ - **MCP Server for AI Agents:** Bundled `scad-gltf mcp` server enables MCP clients to iteratively design, compile, and **visually inspect** 3D models via multi-angle headless rendering and animation frame evaluation.
24
24
  - **AI Studio Extension:** Chrome extension to natively preview, prompt, take chat snapshots, open in Scadify, and locally save AI-generated 3D models directly inside Google AI Studio.
25
25
  - **Godot 4, Rust Bevy & Web Integration:** Native Godot 4 importer addon and Rust Bevy compile-time `build.rs` integration.
26
- - **Automated AI Generation:** Pass an OpenAI-compatible base URL (and optional API key) to the `scad-gen` CLI tool to automatically spawn a local MCP server, query the LLM via standard endpoints, allow it to visually iterate, and output a ready-to-run game project or raw 3D asset script.
26
+ - **Automated AI Generation:** Pass an OpenAI-compatible base URL (and optional API key) to the `scad-gltf gen` CLI tool to automatically spawn a local MCP server, query the LLM via standard endpoints, allow it to visually iterate, and output a ready-to-run game project or raw 3D asset script.
27
27
 
28
28
  ---
29
29
 
@@ -41,7 +41,7 @@ The built-in web editor (**Scadify**) provides a full-featured development envir
41
41
  - **Compressed URL Sharing:** Share your designs instantly via URL hash using client-side raw Deflate compression with an optional **Minify Share** toggle to strip comments and whitespace. Integrates with the Web Share API on supported devices.
42
42
  - **File Management & Drag and Drop:** Load local `.scad` files, drop any `.scad` script or `.hdr` environment map directly onto the viewport, download `.scad` source code, or export `.glb` binaries.
43
43
  - **Automatic Model Naming:** Extracts model names automatically from `/* Model Name: ... */` header comments for file downloads and exports.
44
- - **Local Workspace Sync:** Connect to `scad-serve` on localhost to load, edit, save, and delete `.scad` files with change detection and recursive dependency resolution (`include` / `use`).
44
+ - **Local Workspace Sync:** Connect to `scad-gltf serve` on localhost to load, edit, save, and delete `.scad` files with change detection and recursive dependency resolution (`include` / `use`).
45
45
  - **PWA Support:** Installable as a Progressive Web App for desktop and mobile.
46
46
 
47
47
  ---
@@ -51,7 +51,7 @@ The built-in web editor (**Scadify**) provides a full-featured development envir
51
51
  This package is designed to be installed directly from GitHub.
52
52
 
53
53
  **Option 1: Global Installation (Recommended for CLI usage)**
54
- If you plan to use the `scad-convert`, `scad-serve`, `scad-gen`, or `scad-mcp` command-line tools anywhere on your system:
54
+ If you plan to use the `scad-gltf` command-line tool and its subcommands anywhere on your system:
55
55
 
56
56
  ```bash
57
57
  npm install -g scad-gltf
@@ -148,49 +148,49 @@ const glbData = await convertScadToGltf(scadCode, { wasmUrl });
148
148
 
149
149
  ---
150
150
 
151
- ## Command Line Conversion (`scad-convert`)
151
+ ## Command Line Conversion (`scad-gltf convert`)
152
152
 
153
153
  The package includes a CLI utility to convert `.scad` files to `.glb` directly from your terminal. It supports single files or entire directories, and features smart caching with dependency resolution to speed up build pipelines.
154
154
 
155
155
  **Usage:**
156
156
 
157
157
  ```bash
158
- scad-convert <input.scad | input_dir> <output.glb | output_dir> [options_json] [--cache]
158
+ scad-gltf convert <input.scad | input_dir> <output.glb | output_dir> [options_json] [--cache]
159
159
  ```
160
160
 
161
161
  **Examples:**
162
162
 
163
163
  - **Single File:**
164
164
  ```bash
165
- scad-convert model.scad model.glb
165
+ scad-gltf convert model.scad model.glb
166
166
  ```
167
167
  - **Directory Batch Conversion:**
168
168
  ```bash
169
- scad-convert ./src_models ./out_glbs
169
+ scad-gltf convert ./src_models ./out_glbs
170
170
  ```
171
171
  - **With Smart Caching (`--cache`):**
172
172
  Generates a `.import` file containing a SHA-256 hash of the `.scad` file (including any recursively resolved `include` or `use` files) and compiler options. Subsequent runs skip recompilation if no changes are detected.
173
173
  ```bash
174
- scad-convert ./src_models ./out_glbs --cache
174
+ scad-gltf convert ./src_models ./out_glbs --cache
175
175
  ```
176
176
  - **With Options:**
177
177
  Pass custom compiler options as a JSON string (or Base64 encoded JSON string):
178
178
  ```bash
179
- scad-convert model.scad model.glb '{"variables": {"size": 20}}'
179
+ scad-gltf convert model.scad model.glb '{"variables": {"size": 20}}'
180
180
  ```
181
181
 
182
182
  ---
183
183
 
184
- ## Local File Management & Web Editor (`scad-serve`)
184
+ ## Local File Management & Web Editor (`scad-gltf serve`)
185
185
 
186
- `scad-serve` provides a local REST API to manage `.scad` files and perform in-memory SCAD-to-GLB conversions. It automatically builds and serves the Scadify Web Editor UI.
186
+ `scad-gltf serve` provides a local REST API to manage `.scad` files and perform in-memory SCAD-to-GLB conversions. It automatically builds and serves the Scadify Web Editor UI.
187
187
 
188
188
  It operates strictly on the working directory where the command is executed.
189
189
 
190
190
  **Start the server:**
191
191
 
192
192
  ```bash
193
- scad-serve
193
+ scad-gltf serve
194
194
  ```
195
195
 
196
196
  **Available Endpoints:**
@@ -220,9 +220,9 @@ When asking an LLM (like Gemini) to generate OpenSCAD code, the extension automa
220
220
  - **Instant 3D Preview:** Injects a **"Preview 3D"** button on any OpenSCAD code block to compile and render the model in an embedded 3D viewer with grid, wireframe, and full-screen support.
221
221
  - **Visual Chat Feedback (📷):** Click the snapshot button inside the 3D preview window to capture a PNG snapshot of the model and **automatically paste it into the AI Studio chat input**, allowing Gemini to visually evaluate and fix geometry.
222
222
  - **Smart Prompt Injection:** Click the floating **"✨ SCAD"** button to open the configuration modal. Select your desired engine feature set (PBR, auto-smooth, animations, baking) to automatically generate and inject the prompt rules into your chat input.
223
- - **Local Model Refinement:** Connect to your local `scad-serve` workspace directly from the prompt modal. Select an existing `.scad` file to automatically append its source code to your prompt as a reference, enabling seamless AI iteration and refinement of your existing local designs.
223
+ - **Local Model Refinement:** Connect to your local `scad-gltf serve` workspace directly from the prompt modal. Select an existing `.scad` file to automatically append its source code to your prompt as a reference, enabling seamless AI iteration and refinement of your existing local designs.
224
224
  - **Open in Scadify:** Click **"Edit"** in the preview window to immediately transfer the current script into the full standalone Scadify editor via compressed URL hash.
225
- - **Local Workspace Saving:** Directly save and overwrite models to your local directory when running `scad-serve`.
225
+ - **Local Workspace Saving:** Directly save and overwrite models to your local directory when running `scad-gltf serve`.
226
226
 
227
227
  ### Installation (Chrome / Edge / Brave)
228
228
 
@@ -253,7 +253,7 @@ When asking an LLM (like Gemini) to generate OpenSCAD code, the extension automa
253
253
 
254
254
  ## 🤖 Model Context Protocol (MCP) Server
255
255
 
256
- This package includes a native [MCP Server](https://modelcontextprotocol.io/) (`scad-mcp`) designed to give AI assistants **visual feedback** during the 3D modeling process.
256
+ This package includes a native [MCP Server](https://modelcontextprotocol.io/) (`scad-gltf mcp`) designed to give AI assistants **visual feedback** during the 3D modeling process.
257
257
 
258
258
  Instead of generating code blindly, the AI can compile its script, render the 3D scene in a headless browser (Puppeteer + Three.js), and evaluate multi-angle snapshots to iteratively fix geometric or animation errors.
259
259
 
@@ -265,15 +265,16 @@ Instead of generating code blindly, the AI can compile its script, render the 3D
265
265
 
266
266
  ### Setup
267
267
 
268
- If you installed the package globally, you can configure your MCP client to use the `scad-mcp` command directly.
268
+ If you installed the package globally, you can configure your MCP client to use the `scad-gltf mcp` command directly.
269
269
 
270
270
  **Example `config.json`:**
271
271
 
272
272
  ```json
273
273
  {
274
274
  "mcpServers": {
275
- "scad-mcp": {
276
- "command": "scad-mcp"
275
+ "scad-gltf": {
276
+ "command": "scad-gltf",
277
+ "args": ["mcp"]
277
278
  }
278
279
  }
279
280
  }
@@ -295,7 +296,7 @@ The addon allows you to drag-and-drop `.scad` files directly into your Godot pro
295
296
 
296
297
  ### Automated AI Generation (OpenAI-Compatible API)
297
298
 
298
- By default, the `scad-gen` utility copies a heavily engineered system prompt to your clipboard to paste into an LLM. However, if you provide a **Base URL** (and optional **API Key**), the CLI will fully automate this process. It will automatically spin up the `scad-mcp` server in the background, connect it to your LLM, and allow the AI to _visually evaluate and fix_ its code in real-time before saving the final `generate_project.js` script to your disk.
299
+ By default, the `scad-gltf gen` utility copies a heavily engineered system prompt to your clipboard to paste into an LLM. However, if you provide a **Base URL** (and optional **API Key**), the CLI will fully automate this process. It will automatically spin up the `scad-gltf mcp` server in the background, connect it to your LLM, and allow the AI to _visually evaluate and fix_ its code in real-time before saving the final `generate_project.js` script to your disk.
299
300
 
300
301
  **Supported Generation Types:**
301
302
 
@@ -310,7 +311,7 @@ By default, the `scad-gen` utility copies a heavily engineered system prompt to
310
311
  export OPENAI_BASE_URL="https://api.openai.com/v1"
311
312
  export OPENAI_API_KEY="sk-..."
312
313
  export OPENAI_MODEL="gpt-4o"
313
- scad-gen bevy "A 3D spaceship shooter game"
314
+ scad-gltf gen bevy "A 3D spaceship shooter game"
314
315
  ```
315
316
 
316
317
  **Option 2: Using Google Gemini (via OpenAI compatibility)**
@@ -320,7 +321,7 @@ Because Gemini provides an official OpenAI-compatible endpoint, you can configur
320
321
  export OPENAI_API_KEY="AIzaSy..."
321
322
  export OPENAI_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai/"
322
323
  export OPENAI_MODEL="gemini-3.8-flash"
323
- scad-gen godot "A fast-paced 3D hovercraft racing game"
324
+ scad-gltf gen godot "A fast-paced 3D hovercraft racing game"
324
325
  ```
325
326
 
326
327
  **Option 3: Using Local Server**
@@ -329,14 +330,14 @@ You can use completely local, uncensored, or fine-tuned vision models by overrid
329
330
  ```bash
330
331
  export OPENAI_BASE_URL="http://127.0.0.1:8080/v1"
331
332
  export OPENAI_MODEL="llama-3" # Or whatever model name you've loaded
332
- scad-gen web "A 3D configurator app"
333
+ scad-gltf gen web "A 3D configurator app"
333
334
  ```
334
335
 
335
336
  **Passing Parameters via JSON**
336
337
  You can also pass credentials inline as a JSON string argument instead of modifying environment variables:
337
338
 
338
339
  ```bash
339
- scad-gen godot "A futuristic tank game" '{"openaiBaseUrl": "http://127.0.0.1:8080/v1", "openaiModel": "llama-3"}'
340
+ scad-gltf gen godot "A futuristic tank game" '{"openaiBaseUrl": "http://127.0.0.1:8080/v1", "openaiModel": "llama-3"}'
340
341
  ```
341
342
 
342
343
  _Once the automated process completes for full game projects, simply run the generated Node.js script to assemble your complete Godot, Bevy, or Vite project structure with all assets and code!_
@@ -1 +1 @@
1
- import{A as e,B as t,D as n,E as r,H as i,O as a,R as o,W as s,a as ee,d as c,et as te,f as ne,ht as re,i as ie,l,lt as u,mt as ae,n as oe,o as se,p as d,r as f,s as p,t as ce,u as le,ut as m,w as ue,y as de}from"./OutputPass-B6MrFnGE.js";var h=document.getElementById(`viewer-container`),fe=document.getElementById(`viewer`),g=document.getElementById(`filename-input`),_=document.getElementById(`save-btn`),v=document.getElementById(`open-editor-btn`),y=document.getElementById(`show-grid-cb`),b=document.getElementById(`wireframe-cb`),x=document.getElementById(`fullscreen-btn`),S=document.getElementById(`screenshot-btn`),C=document.getElementById(`anim-controls`),w=document.getElementById(`anim-play-btn`),T=document.getElementById(`anim-select`),E=document.getElementById(`anim-slider`),D=null,O=null,k=!0,A=!1,j=[],M=``,N=!1,P=null,F=null,I=!1,L=new te;L.background=new de(2236962);var R=new i(60,window.innerWidth/window.innerHeight,.1,2e3);R.position.set(50,50,-50);var z=new le({antialias:!0});z.setSize(window.innerWidth,window.innerHeight),z.setPixelRatio(Math.min(window.devicePixelRatio,2)),z.shadowMap.enabled=!0,z.shadowMap.type=2,z.toneMapping=4,z.toneMappingExposure=1,fe.appendChild(z.domElement);var B=new ie(z),pe=new f(L,R);B.addPass(pe);var me=new oe(new u(window.innerWidth,window.innerHeight),.1,.1,2);B.addPass(me);var V=new ce;B.addPass(V);var H=new p(R,z.domElement);H.enableDamping=!0,H.dampingFactor=.1,L.environment=new l(z).fromScene(new ee,.04).texture,L.environmentIntensity=.8;var U=new a;L.add(U);var he=new s(2e3,2e3),ge=new t({color:2236962,roughness:.8,metalness:.1}),W=new o(he,ge);W.rotation.x=-Math.PI/2,W.receiveShadow=!0,L.add(W);var G=new n(2e3,100,5592405,3355443);G.material.transparent=!0,G.material.opacity=.5,L.add(G);var K=new ne(100);L.add(K);var q=new ue(16777215,1.5);q.castShadow=!0,q.shadow.mapSize.width=2048,q.shadow.mapSize.height=2048,q.shadow.bias=-5e-4,U.add(q),U.add(q.target);var J=new e(16777215,4473924,.6);J.position.set(0,20,0),U.add(J),b&&b.addEventListener(`change`,()=>{let e=b.checked;D&&D.traverse(t=>{t.isMesh&&t.material&&(Array.isArray(t.material)?t.material.forEach(t=>{t.wireframe=e}):t.material.wireframe=e)})}),x&&h&&(x.addEventListener(`click`,()=>{document.fullscreenElement?document.exitFullscreen().catch(e=>{console.warn(`Error attempting to exit fullscreen: ${e.message}`)}):h.requestFullscreen().catch(e=>{console.warn(`Error attempting to enable fullscreen: ${e.message}`)})}),document.addEventListener(`fullscreenchange`,()=>{setTimeout(()=>window.dispatchEvent(new Event(`resize`)),100)})),S&&S.addEventListener(`click`,()=>{I=!0;let e=S.innerText;S.innerText=`✅`,setTimeout(()=>{S.innerText=e},1e3)});function Y(){O&&(k=!1,O.paused=!0,w&&(w.innerText=`▶ Play`))}function X(e){if(!F||!j[e])return;O&&O.stop();let t=j[e];O=F.clipAction(t),O.play(),O.time=0,F.update(0),k=!0,O.paused=!1,w&&(w.innerText=`⏸ Pause`),E&&(E.value=0)}T&&T.addEventListener(`change`,e=>{X(parseInt(e.target.value))}),w&&w.addEventListener(`click`,()=>{O&&(k=!k,O.paused=!k,w.innerText=k?`⏸ Pause`:`▶ Play`)}),E&&(E.addEventListener(`mousedown`,()=>{A=!0,Y()}),E.addEventListener(`mouseup`,()=>{A=!1}),E.addEventListener(`touchstart`,()=>{A=!0,Y()},{passive:!0}),E.addEventListener(`touchend`,()=>{A=!1},{passive:!0}),E.addEventListener(`input`,e=>{if(Y(),O){let t=O.getClip().duration;O.time=parseFloat(e.target.value)*t,F&&F.update(0)}}));var Z=performance.now();function Q(){requestAnimationFrame(Q);let e=performance.now(),t=(e-Z)/1e3;if(Z=e,F&&(F.update(t),O&&k&&!A)){let e=O.getClip().duration;if(e>0){let t=O.time%e;t<0&&(t+=e),E&&(E.value=t/e)}}H.update();let n=!y||y.checked;if(G.visible=n,K.visible=n,B.render(),I){I=!1;let e=z.domElement.toDataURL(`image/png`);window.parent.postMessage({type:`ADD_IMAGE_TO_CHAT`,dataUrl:e},`*`)}}Q(),window.addEventListener(`resize`,()=>{let e=h?h.clientWidth:window.innerWidth,t=h?h.clientHeight:window.innerHeight;R.aspect=e/t,R.updateProjectionMatrix(),z.setSize(e,t),B.setSize(e,t)});function _e(e,t){if(!e)return new d;e.updateMatrixWorld(!0);let n=new d().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 ve(e){return new Promise((t,n)=>{D&&=(F&&=(F.stopAllAction(),F.uncacheRoot(F.getRoot()),null),O=null,j=[],L.remove(D),D.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 se().parse(r,``,e=>{D=e.scene,j=e.animations||[];let n=b?b.checked:!1;j.length>0?(F=new c(D),C?(C.style.display=`flex`,T.innerHTML=``,j.forEach((e,t)=>{let n=document.createElement(`option`);n.value=t,n.innerText=e.name||`Animation ${t+1}`,T.appendChild(n)}),X(0)):(j.forEach(e=>{let t=F.clipAction(e);t.play(),t.time=0}),F.update(0))):(C&&(C.style.display=`none`),O=null),D.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))}),L.add(D),ye(),t()},e=>{console.error(e),n(e)})})}function ye(){if(!D)return;let e=_e(D,j);if(e.isEmpty())return;let t=e.getCenter(new m),n=e.getSize(new m),i=Math.max(n.x,n.y,n.z)||10;W.position.y=e.min.y-.01,G.position.y=W.position.y+.001,K.position.y=W.position.y+.002;let a=R.fov*(Math.PI/180),o=i/(2*Math.tan(a/2));R.aspect<1&&(o/=R.aspect),o*=1.5,R.near=Math.max(.01,i*.01),R.far=Math.max(2e3,o*10),R.updateProjectionMatrix(),H.maxDistance=R.far,R.position.set(t.x+o*.8,t.y+o*.8,t.z-o*.8),R.lookAt(t),H.target.copy(t),H.update(),q.position.set(t.x+i,t.y+i*1.5,t.z-i),q.target.position.copy(t),q.target.updateMatrixWorld();let s=i*1.5;q.shadow.camera.left=-s,q.shadow.camera.right=s,q.shadow.camera.top=s,q.shadow.camera.bottom=-s,q.shadow.camera.near=.1,q.shadow.camera.far=i*5,q.shadow.camera.updateProjectionMatrix(),L.fog=new r(2236962,o*1.5,o*5)}async function be(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(N){P=e;return}N=!0;try{let t=await be(e);await ve(await ae(e,{wasmUrl:re,additionalFiles:t}))}catch(e){console.error(e)}finally{if(N=!1,P!==null){let e=P;P=null,$(e)}}}window.addEventListener(`message`,async e=>{if(e.data.type===`RENDER_SCAD`){M=e.data.code;let t=M.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&&(g.value=e)}await $(e.data.code)}}),_.addEventListener(`click`,async()=>{let e=g.value.trim();if(!e){alert(`Please enter a filename.`),g.focus();return}let t=`http://localhost:3000`;try{_.innerText=`Checking...`,_.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?`)){_.innerText=`Save`,_.disabled=!1;return}_.innerText=`Saving...`;let a=await fetch(`${t}/api/scads`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({filename:e,content:M||``})});if(!a.ok){let e=await a.json();throw Error(e.error||`Save failed.`)}_.innerText=`✅ Saved!`,setTimeout(()=>{_.innerText=`Save`,_.disabled=!1},2e3)}catch(e){console.error(e),alert(`Error saving file: `+e.message),_.innerText=`Save`,_.disabled=!1}});async function xe(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(/=+$/,``)}v.addEventListener(`click`,async()=>{if(!M){alert(`No model loaded to open.`);return}try{v.disabled=!0,v.innerText=`Preparing...`;let e=`https://iliagrigorevdev.github.io/scad-gltf/#${await xe(M)}`;window.open(e,`_blank`),window.parent.postMessage({type:`CLOSE_PREVIEW`},`*`),v.innerText=`✅ Opened!`,setTimeout(()=>{v.innerText=`Edit`,v.disabled=!1},2e3)}catch(e){console.error(`Error creating editor link`,e),alert(`Error creating editor link: `+e.message),v.innerText=`Edit`,v.disabled=!1}});
1
+ import{A as e,B as t,D as n,E as r,H as i,O as a,R as o,W as s,a as ee,d as c,et as te,f as ne,ht as re,i as ie,l,lt as u,mt as ae,n as oe,o as se,p as d,r as f,s as p,t as ce,u as le,ut as m,w as ue,y as de}from"./OutputPass-B6MrFnGE.js";var h=document.getElementById(`viewer-container`),fe=document.getElementById(`viewer`),g=document.getElementById(`filename-input`),_=document.getElementById(`save-btn`),v=document.getElementById(`open-editor-btn`),y=document.getElementById(`show-grid-cb`),b=document.getElementById(`wireframe-cb`),x=document.getElementById(`fullscreen-btn`),S=document.getElementById(`screenshot-btn`),C=document.getElementById(`anim-controls`),w=document.getElementById(`anim-play-btn`),T=document.getElementById(`anim-select`),E=document.getElementById(`anim-slider`),D=null,O=null,k=!0,A=!1,j=[],M=``,N=!1,P=null,F=null,I=!1,L=new te;L.background=new de(2236962);var R=new i(60,window.innerWidth/window.innerHeight,.1,2e3);R.position.set(50,50,-50);var z=new le({antialias:!0});z.setSize(window.innerWidth,window.innerHeight),z.setPixelRatio(Math.min(window.devicePixelRatio,2)),z.shadowMap.enabled=!0,z.shadowMap.type=2,z.toneMapping=4,z.toneMappingExposure=1,fe.appendChild(z.domElement);var B=new ie(z),pe=new f(L,R);B.addPass(pe);var me=new oe(new u(window.innerWidth,window.innerHeight),.1,.1,2);B.addPass(me);var V=new ce;B.addPass(V);var H=new p(R,z.domElement);H.enableDamping=!0,H.dampingFactor=.1,L.environment=new l(z).fromScene(new ee,.04).texture,L.environmentIntensity=.8;var U=new a;L.add(U);var he=new s(2e3,2e3),ge=new t({color:2236962,roughness:.8,metalness:.1}),W=new o(he,ge);W.rotation.x=-Math.PI/2,W.receiveShadow=!0,L.add(W);var G=new n(2e3,100,5592405,3355443);G.material.transparent=!0,G.material.opacity=.5,L.add(G);var K=new ne(100);L.add(K);var q=new ue(16777215,1.5);q.castShadow=!0,q.shadow.mapSize.width=2048,q.shadow.mapSize.height=2048,q.shadow.bias=-5e-4,U.add(q),U.add(q.target);var J=new e(16777215,4473924,.6);J.position.set(0,20,0),U.add(J),b&&b.addEventListener(`change`,()=>{let e=b.checked;D&&D.traverse(t=>{t.isMesh&&t.material&&(Array.isArray(t.material)?t.material.forEach(t=>{t.wireframe=e}):t.material.wireframe=e)})}),x&&h&&(x.addEventListener(`click`,()=>{document.fullscreenElement?document.exitFullscreen().catch(e=>{console.warn(`Error attempting to exit fullscreen: ${e.message}`)}):h.requestFullscreen().catch(e=>{console.warn(`Error attempting to enable fullscreen: ${e.message}`)})}),document.addEventListener(`fullscreenchange`,()=>{setTimeout(()=>window.dispatchEvent(new Event(`resize`)),100)})),S&&S.addEventListener(`click`,()=>{I=!0;let e=S.innerText;S.innerText=`✅`,setTimeout(()=>{S.innerText=e},1e3)});function Y(){O&&(k=!1,O.paused=!0,w&&(w.innerText=`▶ Play`))}function X(e){if(!F||!j[e])return;O&&O.stop();let t=j[e];O=F.clipAction(t),O.play(),O.time=0,F.update(0),k=!0,O.paused=!1,w&&(w.innerText=`⏸ Pause`),E&&(E.value=0)}T&&T.addEventListener(`change`,e=>{X(parseInt(e.target.value))}),w&&w.addEventListener(`click`,()=>{O&&(k=!k,O.paused=!k,w.innerText=k?`⏸ Pause`:`▶ Play`)}),E&&(E.addEventListener(`mousedown`,()=>{A=!0,Y()}),E.addEventListener(`mouseup`,()=>{A=!1}),E.addEventListener(`touchstart`,()=>{A=!0,Y()},{passive:!0}),E.addEventListener(`touchend`,()=>{A=!1},{passive:!0}),E.addEventListener(`input`,e=>{if(Y(),O){let t=O.getClip().duration;O.time=parseFloat(e.target.value)*t,F&&F.update(0)}}));var Z=performance.now();function Q(){requestAnimationFrame(Q);let e=performance.now(),t=(e-Z)/1e3;if(Z=e,F&&(F.update(t),O&&k&&!A)){let e=O.getClip().duration;if(e>0){let t=O.time%e;t<0&&(t+=e),E&&(E.value=t/e)}}H.update();let n=!y||y.checked;if(G.visible=n,K.visible=n,B.render(),I){I=!1;let e=z.domElement.toDataURL(`image/png`);window.parent.postMessage({type:`ADD_IMAGE_TO_CHAT`,dataUrl:e},`*`)}}Q(),window.addEventListener(`resize`,()=>{let e=h?h.clientWidth:window.innerWidth,t=h?h.clientHeight:window.innerHeight;R.aspect=e/t,R.updateProjectionMatrix(),z.setSize(e,t),B.setSize(e,t)});function _e(e,t){if(!e)return new d;e.updateMatrixWorld(!0);let n=new d().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 ve(e){return new Promise((t,n)=>{D&&=(F&&=(F.stopAllAction(),F.uncacheRoot(F.getRoot()),null),O=null,j=[],L.remove(D),D.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 se().parse(r,``,e=>{D=e.scene,j=e.animations||[];let n=b?b.checked:!1;j.length>0?(F=new c(D),C?(C.style.display=`flex`,T.innerHTML=``,j.forEach((e,t)=>{let n=document.createElement(`option`);n.value=t,n.innerText=e.name||`Animation ${t+1}`,T.appendChild(n)}),X(0)):(j.forEach(e=>{let t=F.clipAction(e);t.play(),t.time=0}),F.update(0))):(C&&(C.style.display=`none`),O=null),D.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))}),L.add(D),ye(),t()},e=>{console.error(e),n(e)})})}function ye(){if(!D)return;let e=_e(D,j);if(e.isEmpty())return;let t=e.getCenter(new m),n=e.getSize(new m),i=Math.max(n.x,n.y,n.z)||10;W.position.y=e.min.y-.01,G.position.y=W.position.y+.001,K.position.y=W.position.y+.002;let a=R.fov*(Math.PI/180),o=i/(2*Math.tan(a/2));R.aspect<1&&(o/=R.aspect),o*=1.5,R.near=Math.max(.01,i*.01),R.far=Math.max(2e3,o*10),R.updateProjectionMatrix(),H.maxDistance=R.far,R.position.set(t.x+o*.8,t.y+o*.8,t.z-o*.8),R.lookAt(t),H.target.copy(t),H.update(),q.position.set(t.x+i,t.y+i*1.5,t.z-i),q.target.position.copy(t),q.target.updateMatrixWorld();let s=i*1.5;q.shadow.camera.left=-s,q.shadow.camera.right=s,q.shadow.camera.top=s,q.shadow.camera.bottom=-s,q.shadow.camera.near=.1,q.shadow.camera.far=i*5,q.shadow.camera.updateProjectionMatrix(),L.fog=new r(2236962,o*1.5,o*5)}async function be(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(N){P=e;return}N=!0;try{let t=await be(e);await ve(await ae(e,{wasmUrl:re,additionalFiles:t}))}catch(e){console.error(e)}finally{if(N=!1,P!==null){let e=P;P=null,$(e)}}}window.addEventListener(`message`,async e=>{if(e.data.type===`RENDER_SCAD`){M=e.data.code;let t=M.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&&(g.value=e)}await $(e.data.code)}}),_.addEventListener(`click`,async()=>{let e=g.value.trim();if(!e){alert(`Please enter a filename.`),g.focus();return}let t=`http://localhost:3000`;try{_.innerText=`Checking...`,_.disabled=!0;let n=await fetch(`${t}/api/scads`);if(!n.ok)throw Error("Could not reach `scad-gltf serve`.");let r=await n.json(),i=e.toLowerCase().endsWith(`.scad`)?e:`${e}.scad`;if(r.files&&r.files.includes(i)&&!confirm(`File "${i}" already exists. Overwrite?`)){_.innerText=`Save`,_.disabled=!1;return}_.innerText=`Saving...`;let a=await fetch(`${t}/api/scads`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({filename:e,content:M||``})});if(!a.ok){let e=await a.json();throw Error(e.error||`Save failed.`)}_.innerText=`✅ Saved!`,setTimeout(()=>{_.innerText=`Save`,_.disabled=!1},2e3)}catch(e){console.error(e),alert(`Error saving file: `+e.message),_.innerText=`Save`,_.disabled=!1}});async function xe(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(/=+$/,``)}v.addEventListener(`click`,async()=>{if(!M){alert(`No model loaded to open.`);return}try{v.disabled=!0,v.innerText=`Preparing...`;let e=`https://iliagrigorevdev.github.io/scad-gltf/#${await xe(M)}`;window.open(e,`_blank`),window.parent.postMessage({type:`CLOSE_PREVIEW`},`*`),v.innerText=`✅ Opened!`,setTimeout(()=>{v.innerText=`Edit`,v.disabled=!1},2e3)}catch(e){console.error(`Error creating editor link`,e),alert(`Error creating editor link: `+e.message),v.innerText=`Edit`,v.disabled=!1}});
@@ -2,7 +2,7 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
- <script type="module" crossorigin src="./assets/preview-CCwT_Mrn.js"></script>
5
+ <script type="module" crossorigin src="./assets/preview-BbUpKLF4.js"></script>
6
6
  <link rel="modulepreload" crossorigin href="./assets/OutputPass-B6MrFnGE.js">
7
7
  <link rel="stylesheet" crossorigin href="./assets/preview-A6M04CFK.css">
8
8
  <link rel="manifest" href="./manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="./registerSW.js"></script></head>
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:"8a9ab9ab63e023395f78103b72aa9c56"},{url:"index.html",revision:"e09c648877bb53e1fc65cfebb5a020af"},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"content.js",revision:"a0ee0962c4c041063d42d1a33eb0b294"},{url:"content.css",revision:"4ea93a2d03164b49c80a48138305e564"},{url:"content-loader.js",revision:"d94f1755552d49c1fe3a6da123e8a346"},{url:"aristea_wreck_puresky_2k.hdr",revision:"e764c66f871ab0987f3fac422edc841d"},{url:"assets/prompt-ui-B9QCNLfJ.js",revision:null},{url:"assets/preview-CCwT_Mrn.js",revision:null},{url:"assets/preview-A6M04CFK.css",revision:null},{url:"assets/openscad-CdBCY4mx.wasm",revision:null},{url:"assets/index-CgVih1C4.js",revision:null},{url:"assets/index-B1KAb97r.css",revision:null},{url:"assets/OutputPass-B6MrFnGE.js",revision:null},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"manifest.webmanifest",revision:"705049c691faa532cc20c736d56d54df"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
1
+ if(!self.define){let e,s={};const i=(i,r)=>(i=new URL(i+".js",r).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(r,n)=>{const t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let l={};const o=e=>i(e,t),c={module:{uri:t},exports:l,require:o};s[t]=Promise.all(r.map(e=>c[e]||o(e))).then(e=>(n(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"402b66900e731ca748771b6fc5e7a068"},{url:"preview.html",revision:"0cb1d1cf5e8358e608927e2f3efc3b14"},{url:"index.html",revision:"e09c648877bb53e1fc65cfebb5a020af"},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"content.js",revision:"a0ee0962c4c041063d42d1a33eb0b294"},{url:"content.css",revision:"4ea93a2d03164b49c80a48138305e564"},{url:"content-loader.js",revision:"d94f1755552d49c1fe3a6da123e8a346"},{url:"aristea_wreck_puresky_2k.hdr",revision:"e764c66f871ab0987f3fac422edc841d"},{url:"assets/prompt-ui-B9QCNLfJ.js",revision:null},{url:"assets/preview-BbUpKLF4.js",revision:null},{url:"assets/preview-A6M04CFK.css",revision:null},{url:"assets/openscad-CdBCY4mx.wasm",revision:null},{url:"assets/index-CgVih1C4.js",revision:null},{url:"assets/index-B1KAb97r.css",revision:null},{url:"assets/OutputPass-B6MrFnGE.js",revision:null},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"manifest.webmanifest",revision:"705049c691faa532cc20c736d56d54df"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scad-gltf",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "WASM build of a custom OpenSCAD fork that compiles .scad scripts directly to glTF/GLB with PBR and animation support",
5
5
  "type": "module",
6
6
  "main": "./src/ext/openscad.js",
@@ -82,7 +82,7 @@ ${promptRules}
82
82
  - Ensure all string file contents inside the Node.js script are properly escaped.`,
83
83
  buildInputRequest: (task) =>
84
84
  `Design and implement a Rust Bevy engine game for the following concept: "${task}"`,
85
- allowedTools: ["render_scad_model", "compile_bevy_project"],
85
+ allowedTools: ["render_scad_model", "compile_rust_project"],
86
86
  };
87
87
  }
88
88