scad-gltf 0.3.1 → 0.3.3
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 +27 -26
- package/bin/gen.js +2 -2
- package/bin/mcp.js +2 -2
- package/bin/play.js +8 -8
- package/bin/scad-gltf.js +1 -1
- package/editor/dist/assets/{preview-CCwT_Mrn.js → preview-BbUpKLF4.js} +1 -1
- package/editor/dist/preview.html +1 -1
- package/editor/dist/sw.js +1 -1
- package/package.json +1 -1
- package/src/cli-utils.js +10 -6
- package/src/project-prompts.js +32 -24
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-
|
|
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-
|
|
276
|
-
"command": "scad-
|
|
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!_
|
package/bin/gen.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { runCliApp } from "../src/cli-utils.js";
|
|
4
4
|
import { getProjectPrompts } from "../src/project-prompts.js";
|
|
5
5
|
|
|
6
|
-
const VALID_TYPES = ["scad", "bevy", "web", "godot"];
|
|
6
|
+
const VALID_TYPES = ["scad", "bevy", "wgpu", "web", "godot"];
|
|
7
7
|
|
|
8
8
|
async function main() {
|
|
9
9
|
const projectType = process.argv[2];
|
|
@@ -14,7 +14,7 @@ async function main() {
|
|
|
14
14
|
`Usage: scad-gltf gen <${VALID_TYPES.join("|")}> "<description>" [options_json]`,
|
|
15
15
|
);
|
|
16
16
|
console.error(
|
|
17
|
-
`Example: scad-gltf gen godot "A 3D
|
|
17
|
+
`Example: scad-gltf gen godot "A 3D physics simulation" '{"openaiModel": "gpt-4o"}'`,
|
|
18
18
|
);
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
package/bin/mcp.js
CHANGED
|
@@ -201,7 +201,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
201
201
|
{
|
|
202
202
|
name: "test_godot_project",
|
|
203
203
|
description:
|
|
204
|
-
"Runs a specified Godot project for a short duration to detect script errors, missing resources, or runtime crashes. Returns the console output logs and a visual screenshot of the
|
|
204
|
+
"Runs a specified Godot project for a short duration to detect script errors, missing resources, or runtime crashes. Returns the console output logs and a visual screenshot of the running project.",
|
|
205
205
|
inputSchema: {
|
|
206
206
|
type: "object",
|
|
207
207
|
properties: {
|
|
@@ -793,7 +793,7 @@ func _ready():
|
|
|
793
793
|
}
|
|
794
794
|
fs.writeFileSync(projectGodotPath, tempProjectGodot, "utf8");
|
|
795
795
|
|
|
796
|
-
// 2.
|
|
796
|
+
// 2. Run Project Step
|
|
797
797
|
// Always try windowed first to get the screenshot.
|
|
798
798
|
// If the environment lacks a display server, we'll catch the error and fallback.
|
|
799
799
|
let playArgs = [
|
package/bin/play.js
CHANGED
|
@@ -62,7 +62,7 @@ function main() {
|
|
|
62
62
|
|
|
63
63
|
// 3. Find the resulting project folder and determine its type
|
|
64
64
|
let projectDir = null;
|
|
65
|
-
let projectType = null; // 'godot' | 'web' | '
|
|
65
|
+
let projectType = null; // 'godot' | 'web' | 'rust'
|
|
66
66
|
let latestTime = 0;
|
|
67
67
|
|
|
68
68
|
const items = fs.readdirSync(tempDir);
|
|
@@ -71,14 +71,14 @@ function main() {
|
|
|
71
71
|
if (fs.statSync(itemPath).isDirectory()) {
|
|
72
72
|
const isGodot = fs.existsSync(path.join(itemPath, "project.godot"));
|
|
73
73
|
const isWeb = fs.existsSync(path.join(itemPath, "package.json"));
|
|
74
|
-
const
|
|
74
|
+
const isRust = fs.existsSync(path.join(itemPath, "Cargo.toml"));
|
|
75
75
|
|
|
76
|
-
if (isGodot || isWeb ||
|
|
76
|
+
if (isGodot || isWeb || isRust) {
|
|
77
77
|
const mtime = fs.statSync(itemPath).mtimeMs;
|
|
78
78
|
if (mtime > latestTime) {
|
|
79
79
|
latestTime = mtime;
|
|
80
80
|
projectDir = itemPath;
|
|
81
|
-
projectType = isGodot ? "godot" :
|
|
81
|
+
projectType = isGodot ? "godot" : isRust ? "rust" : "web";
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
}
|
|
@@ -87,7 +87,7 @@ function main() {
|
|
|
87
87
|
if (!projectDir || !projectType) {
|
|
88
88
|
console.error("\n❌ Error: Could not find a valid generated project.");
|
|
89
89
|
console.error(
|
|
90
|
-
"The script did not produce a folder containing 'project.godot'
|
|
90
|
+
"The script did not produce a folder containing 'project.godot', 'package.json', or 'Cargo.toml'.",
|
|
91
91
|
);
|
|
92
92
|
cleanup();
|
|
93
93
|
process.exit(1);
|
|
@@ -103,7 +103,7 @@ function main() {
|
|
|
103
103
|
|
|
104
104
|
console.log(`\n⚙️ Importing Godot assets (this may take a moment)...`);
|
|
105
105
|
try {
|
|
106
|
-
// Force Godot to build the .godot/imported cache before attempting to run the
|
|
106
|
+
// Force Godot to build the .godot/imported cache before attempting to run the project
|
|
107
107
|
execSync(`"${godotBin}" --headless --editor --quit`, {
|
|
108
108
|
cwd: projectDir,
|
|
109
109
|
stdio: "inherit",
|
|
@@ -137,9 +137,9 @@ function main() {
|
|
|
137
137
|
cwd: projectDir,
|
|
138
138
|
stdio: "inherit",
|
|
139
139
|
});
|
|
140
|
-
} else if (projectType === "
|
|
140
|
+
} else if (projectType === "rust") {
|
|
141
141
|
console.log(
|
|
142
|
-
`\n🦀 Compiling and starting Rust
|
|
142
|
+
`\n🦀 Compiling and starting Rust project (this may take a moment)...`,
|
|
143
143
|
);
|
|
144
144
|
const cargoCmd = process.platform === "win32" ? "cargo.exe" : "cargo";
|
|
145
145
|
appProcess = spawn(cargoCmd, ["run"], {
|
package/bin/scad-gltf.js
CHANGED
|
@@ -17,7 +17,7 @@ if (!command || !commands[command]) {
|
|
|
17
17
|
console.error(" serve Start local API server and Web Editor");
|
|
18
18
|
console.error(" mcp Start the Model Context Protocol (MCP) server");
|
|
19
19
|
console.error(
|
|
20
|
-
" gen Automated AI generation of 3D models and
|
|
20
|
+
" gen Automated AI generation of 3D models, simulations, and interactive projects",
|
|
21
21
|
);
|
|
22
22
|
console.error(" play Test and launch generated Node.js scripts");
|
|
23
23
|
process.exit(1);
|
|
@@ -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(
|
|
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}});
|
package/editor/dist/preview.html
CHANGED
|
@@ -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-
|
|
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:"
|
|
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
package/src/cli-utils.js
CHANGED
|
@@ -495,8 +495,11 @@ export async function runAutomatedAIFlow(
|
|
|
495
495
|
const isBevy =
|
|
496
496
|
projectType === "bevy" &&
|
|
497
497
|
fs.existsSync(path.join(itemPath, "Cargo.toml"));
|
|
498
|
+
const isWgpu =
|
|
499
|
+
projectType === "wgpu" &&
|
|
500
|
+
fs.existsSync(path.join(itemPath, "Cargo.toml"));
|
|
498
501
|
|
|
499
|
-
if (isGodot || isWeb || isBevy) {
|
|
502
|
+
if (isGodot || isWeb || isBevy || isWgpu) {
|
|
500
503
|
const mtime = fs.statSync(itemPath).mtimeMs;
|
|
501
504
|
if (mtime > latestTime) {
|
|
502
505
|
latestTime = mtime;
|
|
@@ -511,7 +514,7 @@ export async function runAutomatedAIFlow(
|
|
|
511
514
|
const expectedFile =
|
|
512
515
|
projectType === "godot"
|
|
513
516
|
? "project.godot"
|
|
514
|
-
: projectType === "bevy"
|
|
517
|
+
: projectType === "bevy" || projectType === "wgpu"
|
|
515
518
|
? "Cargo.toml"
|
|
516
519
|
: "package.json";
|
|
517
520
|
messages.push({
|
|
@@ -554,9 +557,10 @@ export async function runAutomatedAIFlow(
|
|
|
554
557
|
});
|
|
555
558
|
|
|
556
559
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
557
|
-
} else if (projectType === "bevy") {
|
|
560
|
+
} else if (projectType === "bevy" || projectType === "wgpu") {
|
|
561
|
+
const fwName = projectType === "bevy" ? "Bevy" : "wgpu";
|
|
558
562
|
console.log(
|
|
559
|
-
`\n🦀 Building and running
|
|
563
|
+
`\n🦀 Building and running ${fwName} project: ${path.basename(projectDir)}...`,
|
|
560
564
|
);
|
|
561
565
|
const cargoCmd =
|
|
562
566
|
process.platform === "win32" ? "cargo.exe" : "cargo";
|
|
@@ -670,7 +674,7 @@ export async function runCliApp({
|
|
|
670
674
|
);
|
|
671
675
|
} else {
|
|
672
676
|
console.error(
|
|
673
|
-
` scad-gltf gen ${projectType} "A simple 3D
|
|
677
|
+
` scad-gltf gen ${projectType} "A simple 3D simulation" '{"openaiApiKey": "sk-...", "openaiModel": "gpt-4o"}'`,
|
|
674
678
|
);
|
|
675
679
|
}
|
|
676
680
|
process.exit(1);
|
|
@@ -704,7 +708,7 @@ export async function runCliApp({
|
|
|
704
708
|
try {
|
|
705
709
|
const { generatePrompt } = await import("./prompt.js");
|
|
706
710
|
promptRules = generatePrompt(
|
|
707
|
-
isRawScad ? task : "the 3D assets for the
|
|
711
|
+
isRawScad ? task : "the 3D assets for the project",
|
|
708
712
|
options,
|
|
709
713
|
);
|
|
710
714
|
} catch (e) {
|
package/src/project-prompts.js
CHANGED
|
@@ -22,19 +22,23 @@ CRITICAL WORKFLOW:
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
if (projectType === "bevy") {
|
|
25
|
+
if (projectType === "bevy" || projectType === "wgpu") {
|
|
26
|
+
const isBevy = projectType === "bevy";
|
|
27
|
+
const frameworkName = isBevy ? "Bevy" : "wgpu";
|
|
28
|
+
const depName = isBevy ? "bevy" : "wgpu";
|
|
29
|
+
|
|
26
30
|
return {
|
|
27
31
|
buildSystemPrompt: (
|
|
28
32
|
promptRules,
|
|
29
|
-
) => `You are an expert Rust
|
|
33
|
+
) => `You are an expert Rust ${frameworkName} developer and procedural 3D technical artist.
|
|
30
34
|
|
|
31
35
|
NAMING CONVENTION REQUIREMENT:
|
|
32
36
|
- All generated files, directories, models, scripts, and root folders MUST strictly use snake_case (lowercase with underscores, e.g. \`player_character.rs\`, \`enemy_walker.scad\`).
|
|
33
37
|
- NEVER use hyphens/minus signs (\`-\`) or spaces in any file or folder names.
|
|
34
38
|
|
|
35
39
|
What to generate:
|
|
36
|
-
1. 3D
|
|
37
|
-
- Generate procedural 3D models for the
|
|
40
|
+
1. 3D Assets (.scad):
|
|
41
|
+
- Generate procedural 3D models for the project using OpenSCAD.
|
|
38
42
|
- CRITICAL: The SCAD to glTF converter automatically converts OpenSCAD's Z-up coordinate system to the standard glTF Y-up coordinate system. Design your models naturally in OpenSCAD.
|
|
39
43
|
- CRITICAL: You must use the custom OpenSCAD glTF extensions for PBR materials (e.g., \`roughness\`, \`metalness\`, \`emissive\`) and Hierarchical Node Animations (\`armature()\`, \`bone()\`). The rules and syntax for these features are provided below:
|
|
40
44
|
|
|
@@ -42,10 +46,10 @@ What to generate:
|
|
|
42
46
|
${promptRules}
|
|
43
47
|
=============================
|
|
44
48
|
|
|
45
|
-
2. Rust
|
|
46
|
-
- Create the necessary files for a modern Rust
|
|
47
|
-
- In \`Cargo.toml\`, include \`
|
|
48
|
-
- You MUST include this EXACT \`build.rs\` script at the root of the project to automatically compile the \`.scad\` files into \`.glb\` format inside the \`assets/models\` folder before running the
|
|
49
|
+
2. Rust ${frameworkName} Project Files:
|
|
50
|
+
- Create the necessary files for a modern Rust ${frameworkName} application (e.g., \`Cargo.toml\`, \`build.rs\`, \`src/main.rs\`).
|
|
51
|
+
- In \`Cargo.toml\`, include \`${depName}\` as a dependency.
|
|
52
|
+
- You MUST include this EXACT \`build.rs\` script at the root of the project to automatically compile the \`.scad\` files into \`.glb\` format inside the \`assets/models\` folder before running the application via Cargo:
|
|
49
53
|
\`\`\`rust
|
|
50
54
|
use std::process::Command;
|
|
51
55
|
|
|
@@ -70,7 +74,11 @@ ${promptRules}
|
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
\`\`\`
|
|
73
|
-
- Write the core application logic in \`src/main.rs\` to load and display the converted \`.glb\` files interactively
|
|
77
|
+
- Write the core application logic in \`src/main.rs\` to load and display the converted \`.glb\` files interactively.${
|
|
78
|
+
isBevy
|
|
79
|
+
? " Provide standard Bevy systems (camera, lights, movement, etc.)."
|
|
80
|
+
: ""
|
|
81
|
+
}
|
|
74
82
|
|
|
75
83
|
3. Delivery Format (Single Node.js Script):
|
|
76
84
|
- Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
|
|
@@ -78,10 +86,10 @@ ${promptRules}
|
|
|
78
86
|
- When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
|
|
79
87
|
- The script must embed and write:
|
|
80
88
|
- Your generated \`.scad\` 3D assets.
|
|
81
|
-
- Your generated Rust
|
|
89
|
+
- Your generated Rust ${frameworkName} project files (\`Cargo.toml\`, \`build.rs\`, \`src/main.rs\`).
|
|
82
90
|
- Ensure all string file contents inside the Node.js script are properly escaped.`,
|
|
83
91
|
buildInputRequest: (task) =>
|
|
84
|
-
`Design and implement a Rust
|
|
92
|
+
`Design and implement a Rust ${frameworkName} project for the following concept: "${task}"`,
|
|
85
93
|
allowedTools: ["render_scad_model", "compile_rust_project"],
|
|
86
94
|
};
|
|
87
95
|
}
|
|
@@ -94,7 +102,7 @@ ${promptRules}
|
|
|
94
102
|
|
|
95
103
|
What to generate:
|
|
96
104
|
1. 3D Web Assets (.scad):
|
|
97
|
-
- Generate procedural 3D models for the
|
|
105
|
+
- Generate procedural 3D models for the project using OpenSCAD.
|
|
98
106
|
- CRITICAL: The SCAD to glTF converter automatically converts OpenSCAD's Z-up coordinate system to the standard glTF Y-up coordinate system. Design your models naturally in OpenSCAD.
|
|
99
107
|
- CRITICAL: You must use the custom OpenSCAD glTF extensions for PBR materials (e.g., \`roughness\`, \`metalness\`, \`emissive\`) and Hierarchical Node Animations (\`armature()\`, \`bone()\`). The rules and syntax for these features are provided below:
|
|
100
108
|
|
|
@@ -104,14 +112,14 @@ ${promptRules}
|
|
|
104
112
|
|
|
105
113
|
2. Vite Web Project Files (npm based):
|
|
106
114
|
- Create the necessary files for a modern web application (e.g., \`package.json\`, \`index.html\`, \`main.js\`).
|
|
107
|
-
- You can use ANY web 3D library with glTF support (e.g., Three.js, Babylon.js, @google/model-viewer, PlayCanvas, A-Frame, etc.) that fits the
|
|
115
|
+
- You can use ANY web 3D library with glTF support (e.g., Three.js, Babylon.js, @google/model-viewer, PlayCanvas, A-Frame, etc.) that fits the project's requirements.
|
|
108
116
|
- In \`package.json\`, you MUST include the scad to gltf converter tool as a dev dependency:
|
|
109
117
|
\`"scad-gltf": "^0.3.0"\`
|
|
110
118
|
- In \`package.json\`, add npm scripts to automatically compile the \`.scad\` files into \`.glb\` format inside the \`public/\` folder before Vite runs its dev or build steps.
|
|
111
119
|
For example:
|
|
112
120
|
\`"predev": "scad-gltf convert ./scad ./public/models --cache"\`
|
|
113
121
|
\`"prebuild": "scad-gltf convert ./scad ./public/models --cache"\`
|
|
114
|
-
- Write the core
|
|
122
|
+
- Write the core application logic to load and display the converted \`.glb\` files interactively.
|
|
115
123
|
|
|
116
124
|
3. Delivery Format (Single Node.js Script):
|
|
117
125
|
- Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
|
|
@@ -122,7 +130,7 @@ ${promptRules}
|
|
|
122
130
|
- Your generated Vite web project files.
|
|
123
131
|
- Ensure all string file contents inside the Node.js script are properly escaped.`,
|
|
124
132
|
buildInputRequest: (task) =>
|
|
125
|
-
`Design and implement a web-based 3D glTF
|
|
133
|
+
`Design and implement a web-based 3D glTF project using Vite for the following concept: "${task}"`,
|
|
126
134
|
allowedTools: ["render_scad_model"],
|
|
127
135
|
};
|
|
128
136
|
}
|
|
@@ -133,15 +141,15 @@ ${promptRules}
|
|
|
133
141
|
const hasUserScadFiles =
|
|
134
142
|
Array.isArray(options.scadFiles) && options.scadFiles.length > 0;
|
|
135
143
|
|
|
136
|
-
const systemPrompt = `You are an expert Godot 4
|
|
144
|
+
const systemPrompt = `You are an expert Godot 4 developer and procedural 3D technical artist.
|
|
137
145
|
|
|
138
146
|
NAMING CONVENTION REQUIREMENT:
|
|
139
147
|
- All generated files, directories, models, scripts, scenes, and root folders MUST strictly use snake_case (lowercase with underscores, e.g. \`player_character.gd\`, \`main_scene.tscn\`, \`enemy_walker.scad\`).
|
|
140
148
|
- NEVER use hyphens/minus signs (\`-\`) or spaces in any file or folder names.
|
|
141
149
|
|
|
142
150
|
What to generate:
|
|
143
|
-
1. 3D
|
|
144
|
-
- Generate procedural 3D models for the
|
|
151
|
+
1. 3D Assets (.scad):
|
|
152
|
+
- Generate procedural 3D models for the project using OpenSCAD.
|
|
145
153
|
- Scale & Units: 1 OpenSCAD unit = 1 Godot meter. Design your models using realistic meter-based scales (e.g., a character should be ~1.8 units tall). DO NOT use millimeter-based scaling.
|
|
146
154
|
- Coordinate System & Forward Convention: Write standard OpenSCAD Z-up code (+Z is UP, XY plane is ground). Build objects standing upright and facing Front (Positive Y-axis).
|
|
147
155
|
- Left/Right Convention: Always name and position "left" and "right" components (e.g., LeftArm, RightEye) based on the object's anatomical point of view (facing Forward towards +Y), NOT the camera/viewer's screen perspective. Because the object faces +Y, the object's Left side is along the -X axis, and the object's Right side is along the +X axis.
|
|
@@ -159,7 +167,7 @@ ${promptRules}
|
|
|
159
167
|
=============================
|
|
160
168
|
|
|
161
169
|
2. Godot 4 Project Files:
|
|
162
|
-
- Create the necessary GDScript (\`.gd\`) and scene (\`.tscn\`) files to implement the
|
|
170
|
+
- Create the necessary GDScript (\`.gd\`) and scene (\`.tscn\`) files to implement the project logic, responsive user input controls, and a core interaction loop.
|
|
163
171
|
- The scenes should directly instance the generated \`.scad\` files (the provided addon will handle importing them as 3D scenes).
|
|
164
172
|
- GDScript Coordinate, Forward, and Left/Right Conventions:
|
|
165
173
|
* Forward is -Z: In Godot, \`Vector3.FORWARD\` is \`Vector3(0, 0, -1)\`. A 3D node's local forward direction is \`-transform.basis.z\` (or \`-global_transform.basis.z\`). In character movement, forward input (e.g., W or ui_up) must translate along \`-transform.basis.z\`. Never treat +Z as forward.
|
|
@@ -170,14 +178,14 @@ ${promptRules}
|
|
|
170
178
|
* Natural Model Alignment: Because OpenSCAD models face +Y (Forward), they automatically import facing Godot's Forward direction (-Z). Built-in Godot methods like \`look_at()\` orient the node's -Z axis toward the target, which perfectly aligns with the model's front. Do NOT apply compensation rotations (e.g., \`rotate_y(PI)\`) in GDScript to compensate for model orientation.
|
|
171
179
|
- Generate a \`project.godot\` file. It must configure the project and automatically enable the \`scad_importer\` plugin.
|
|
172
180
|
- Generate a \`.gitignore\` file that ignores the \`.godot/\` folder.
|
|
173
|
-
- Generate a \`README.md\` file that documents the project,
|
|
181
|
+
- Generate a \`README.md\` file that documents the project, mechanics/features, and controls.
|
|
174
182
|
|
|
175
183
|
3. Delivery Format (Single Node.js Script):
|
|
176
184
|
- Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
|
|
177
|
-
- CRITICAL: The generated Node.js script MUST first create a root project folder (named using snake_case with underscores, e.g., \`
|
|
185
|
+
- CRITICAL: The generated Node.js script MUST first create a root project folder (named using snake_case with underscores, e.g., \`my_project\`) and output all files and folders inside this newly created project folder.
|
|
178
186
|
- When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
|
|
179
187
|
- The script must embed and write:
|
|
180
|
-
- Your generated \`.scad\`
|
|
188
|
+
- Your generated \`.scad\` assets.
|
|
181
189
|
- Your generated Godot project files.
|
|
182
190
|
- The exact source code of the provided \`addons/scad_importer/*\` files, placed in their correct respective paths.${
|
|
183
191
|
hasUserScadFiles
|
|
@@ -226,7 +234,7 @@ ${promptRules}
|
|
|
226
234
|
|
|
227
235
|
if (hasUserScadFiles) {
|
|
228
236
|
systemClipboardOutput += `=== USER PROVIDED OPENSCAD FILES ===\n`;
|
|
229
|
-
systemClipboardOutput += `The following .scad files are provided as reference or base assets. You MUST embed and write them into the generated project, modifying them if necessary to fit the
|
|
237
|
+
systemClipboardOutput += `The following .scad files are provided as reference or base assets. You MUST embed and write them into the generated project, modifying them if necessary to fit the project logic.\n\n`;
|
|
230
238
|
for (const file of options.scadFiles) {
|
|
231
239
|
try {
|
|
232
240
|
const content = fs
|
|
@@ -247,7 +255,7 @@ ${promptRules}
|
|
|
247
255
|
return systemClipboardOutput.trimEnd() + "\n";
|
|
248
256
|
},
|
|
249
257
|
buildInputRequest: (task) =>
|
|
250
|
-
`Design and implement a Godot 4 project for the following
|
|
258
|
+
`Design and implement a Godot 4 project for the following concept: "${task}"`,
|
|
251
259
|
allowedTools: ["render_scad_model", "test_godot_project"],
|
|
252
260
|
};
|
|
253
261
|
}
|