scad-gltf 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,7 +23,7 @@ The C++ source code for this custom OpenSCAD version is included directly in thi
23
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.
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-godot`, `scad-bevy`, or `scad-web` CLI to automatically spawn a local MCP server, query the LLM via standard endpoints, allow it to visually iterate, and output a ready-to-run project generator script.
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.
27
27
 
28
28
  ---
29
29
 
@@ -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`, or `scad-mcp` command-line tools anywhere on your system:
54
+ If you plan to use the `scad-convert`, `scad-serve`, `scad-gen`, or `scad-mcp` command-line tools anywhere on your system:
55
55
 
56
56
  ```bash
57
57
  npm install -g scad-gltf
@@ -261,7 +261,7 @@ Instead of generating code blindly, the AI can compile its script, render the 3D
261
261
 
262
262
  - `get_scad_prompt`: Injects the custom OpenSCAD syntax rules (PBR, animations, baking) into the AI's context.
263
263
  - `render_scad_model`: Compiles the generated `.scad` code to GLB and returns base64 images from requested camera angles (front, back, left, right, top, bottom, isometric) and specific animation keyframes.
264
- - `compile_bevy_project` / `test_godot_project`: Tests the generated game projects for compilation and runtime errors.
264
+ - `compile_rust_project` / `test_godot_project`: Tests the generated game projects for compilation and runtime errors.
265
265
 
266
266
  ### Setup
267
267
 
@@ -295,7 +295,14 @@ The addon allows you to drag-and-drop `.scad` files directly into your Godot pro
295
295
 
296
296
  ### Automated AI Generation (OpenAI-Compatible API)
297
297
 
298
- By default, `scad-godot`, `scad-bevy`, and `scad-web` copy 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 3D models in real-time before saving the final `generate_project.js` script to your disk.
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
+
300
+ **Supported Generation Types:**
301
+
302
+ - `gen`: Generate a single raw `.scad` asset file.
303
+ - `godot`: Generate a fully packaged Godot 4 game project containing generated assets and GDScript logic.
304
+ - `bevy`: Generate a Rust Bevy game project.
305
+ - `web`: Generate a Vite-based Web3D app (Three.js/Babylon).
299
306
 
300
307
  **Option 1: Using OpenAI (GPT-4o)**
301
308
 
@@ -303,7 +310,7 @@ By default, `scad-godot`, `scad-bevy`, and `scad-web` copy a heavily engineered
303
310
  export OPENAI_BASE_URL="https://api.openai.com/v1"
304
311
  export OPENAI_API_KEY="sk-..."
305
312
  export OPENAI_MODEL="gpt-4o"
306
- scad-bevy "A 3D spaceship shooter game"
313
+ scad-gen bevy "A 3D spaceship shooter game"
307
314
  ```
308
315
 
309
316
  **Option 2: Using Google Gemini (via OpenAI compatibility)**
@@ -313,7 +320,7 @@ Because Gemini provides an official OpenAI-compatible endpoint, you can configur
313
320
  export OPENAI_API_KEY="AIzaSy..."
314
321
  export OPENAI_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai/"
315
322
  export OPENAI_MODEL="gemini-3.8-flash"
316
- scad-godot "A fast-paced 3D hovercraft racing game"
323
+ scad-gen godot "A fast-paced 3D hovercraft racing game"
317
324
  ```
318
325
 
319
326
  **Option 3: Using Local Server**
@@ -322,17 +329,17 @@ You can use completely local, uncensored, or fine-tuned vision models by overrid
322
329
  ```bash
323
330
  export OPENAI_BASE_URL="http://127.0.0.1:8080/v1"
324
331
  export OPENAI_MODEL="llama-3" # Or whatever model name you've loaded
325
- scad-web "A 3D configurator app"
332
+ scad-gen web "A 3D configurator app"
326
333
  ```
327
334
 
328
335
  **Passing Parameters via JSON**
329
336
  You can also pass credentials inline as a JSON string argument instead of modifying environment variables:
330
337
 
331
338
  ```bash
332
- scad-godot "A futuristic tank game" '{"openaiBaseUrl": "http://127.0.0.1:8080/v1", "openaiModel": "llama-3"}'
339
+ scad-gen godot "A futuristic tank game" '{"openaiBaseUrl": "http://127.0.0.1:8080/v1", "openaiModel": "llama-3"}'
333
340
  ```
334
341
 
335
- _Once the automated process completes, simply run the generated Node.js script to assemble your complete Godot, Bevy, or Vite project structure with all assets and code!_
342
+ _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!_
336
343
 
337
344
  ```bash
338
345
  node generate_godot_project.js
@@ -62,7 +62,7 @@ async function run() {
62
62
 
63
63
  if (!inputPath || !outputPath) {
64
64
  console.error(
65
- "Usage: scad-convert <input.scad | input_dir> <output.glb | output_dir> [options_json] [--cache]",
65
+ "Usage: scad-gltf convert <input.scad | input_dir> <output.glb | output_dir> [options_json] [--cache]",
66
66
  );
67
67
  process.exit(1);
68
68
  }
package/bin/gen.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runCliApp } from "../src/cli-utils.js";
4
+ import { getProjectPrompts } from "../src/project-prompts.js";
5
+
6
+ const VALID_TYPES = ["scad", "bevy", "web", "godot"];
7
+
8
+ async function main() {
9
+ const projectType = process.argv[2];
10
+
11
+ if (!projectType || !VALID_TYPES.includes(projectType)) {
12
+ console.error(`Error: Invalid or missing project type.`);
13
+ console.error(
14
+ `Usage: scad-gltf gen <${VALID_TYPES.join("|")}> "<description>" [options_json]`,
15
+ );
16
+ console.error(
17
+ `Example: scad-gltf gen godot "A 3D racing game" '{"openaiModel": "gpt-4o"}'`,
18
+ );
19
+ process.exit(1);
20
+ }
21
+
22
+ await runCliApp({
23
+ projectType,
24
+ ...getProjectPrompts(projectType),
25
+ argStartIndex: 3, // Shift arg parsing right by 1 since projectType is at index 2
26
+ });
27
+ }
28
+
29
+ // Execute and handle unhandled runtime errors
30
+ main().catch((err) => {
31
+ console.error("An unexpected error occurred:", err);
32
+ process.exit(1);
33
+ });
@@ -229,9 +229,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
229
229
  },
230
230
  },
231
231
  {
232
- name: "compile_bevy_project",
232
+ name: "compile_rust_project",
233
233
  description:
234
- "Compiles a specified Rust Bevy project to detect syntax errors, missing dependencies, or compilation failures. Returns the cargo console output logs.",
234
+ "Compiles a specified Rust project to detect syntax errors, missing dependencies, or compilation failures. Returns the cargo console output logs.",
235
235
  inputSchema: {
236
236
  type: "object",
237
237
  properties: {
@@ -243,7 +243,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
243
243
  nodejs_script: {
244
244
  type: "string",
245
245
  description:
246
- "The complete self-contained Node.js script that generates the Bevy project. If provided, the tool will execute this script in a temporary directory to extract the project before compiling it. Use this during the generation phase to test your code before providing the final answer.",
246
+ "The complete self-contained Node.js script that generates the Rust project. If provided, the tool will execute this script in a temporary directory to extract the project before compiling it. Use this during the generation phase to test your code before providing the final answer.",
247
247
  },
248
248
  },
249
249
  },
@@ -946,9 +946,9 @@ func _ready():
946
946
  }
947
947
 
948
948
  // ------------------------------------------
949
- // TOOL 4: compile_bevy_project
949
+ // TOOL 4: compile_rust_project
950
950
  // ------------------------------------------
951
- if (name === "compile_bevy_project") {
951
+ if (name === "compile_rust_project") {
952
952
  let cleanupDir = null;
953
953
  try {
954
954
  let projectDir = null;
@@ -956,7 +956,7 @@ func _ready():
956
956
  if (args.nodejs_script) {
957
957
  // Create a unique temporary directory
958
958
  const tempBase = fs.mkdtempSync(
959
- path.join(os.tmpdir(), "scad-bevy-compile-"),
959
+ path.join(os.tmpdir(), "scad-rust-compile-"),
960
960
  );
961
961
  cleanupDir = tempBase;
962
962
 
@@ -1001,7 +1001,7 @@ func _ready():
1001
1001
  content: [
1002
1002
  {
1003
1003
  type: "text",
1004
- text: `Error: The Node.js script finished running, but NO Bevy project was found. A valid Rust Bevy project requires a 'Cargo.toml' file. Ensure your script creates a root project directory and places 'Cargo.toml' inside it.`,
1004
+ text: `Error: The Node.js script finished running, but NO Rust project was found. A valid Rust project requires a 'Cargo.toml' file. Ensure your script creates a root project directory and places 'Cargo.toml' inside it.`,
1005
1005
  },
1006
1006
  ],
1007
1007
  isError: true,
@@ -1026,7 +1026,7 @@ func _ready():
1026
1026
  content: [
1027
1027
  {
1028
1028
  type: "text",
1029
- text: `Error: The specified directory is not a valid Rust Bevy project. 'Cargo.toml' was not found in: ${projectDir}`,
1029
+ text: `Error: The specified directory is not a valid Rust project. 'Cargo.toml' was not found in: ${projectDir}`,
1030
1030
  },
1031
1031
  ],
1032
1032
  isError: true,
@@ -1055,7 +1055,7 @@ func _ready():
1055
1055
  resolve({ code, output });
1056
1056
  });
1057
1057
 
1058
- // 2-minute timeout for Bevy compilation (can be slow as dependencies download)
1058
+ // 2-minute timeout for Rust compilation (can be slow as dependencies download)
1059
1059
  setTimeout(() => {
1060
1060
  try {
1061
1061
  child.kill();
@@ -1115,7 +1115,7 @@ func _ready():
1115
1115
  content: [
1116
1116
  {
1117
1117
  type: "text",
1118
- text: `Error compiling Bevy project: ${error.message}`,
1118
+ text: `Error compiling Rust project: ${error.message}`,
1119
1119
  },
1120
1120
  ],
1121
1121
  isError: true,
@@ -1138,7 +1138,7 @@ func _ready():
1138
1138
  async function main() {
1139
1139
  const transport = new StdioServerTransport();
1140
1140
  await server.connect(transport);
1141
- console.error("🚀 SCAD MCP Server running on stdio");
1141
+ console.error("🚀 scad-gltf mcp server running on stdio");
1142
1142
  }
1143
1143
 
1144
1144
  main().catch((error) => {
@@ -13,9 +13,7 @@ function main() {
13
13
  console.error(
14
14
  "❌ Error: Path to the packed Node.js generator script is required.",
15
15
  );
16
- console.error(
17
- "Usage: node bin/scad-play.js <path_to_generated_project.js>",
18
- );
16
+ console.error("Usage: scad-gltf play <path_to_generated_project.js>");
19
17
  process.exit(1);
20
18
  }
21
19
 
@@ -64,7 +62,7 @@ function main() {
64
62
 
65
63
  // 3. Find the resulting project folder and determine its type
66
64
  let projectDir = null;
67
- let projectType = null; // 'godot' | 'web'
65
+ let projectType = null; // 'godot' | 'web' | 'bevy'
68
66
  let latestTime = 0;
69
67
 
70
68
  const items = fs.readdirSync(tempDir);
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+
3
+ const command = process.argv[2];
4
+
5
+ const commands = {
6
+ convert: "./convert.js",
7
+ serve: "./serve.js",
8
+ mcp: "./mcp.js",
9
+ gen: "./gen.js",
10
+ play: "./play.js",
11
+ };
12
+
13
+ if (!command || !commands[command]) {
14
+ console.error("Usage: scad-gltf <command> [options]");
15
+ console.error("\nAvailable commands:");
16
+ console.error(" convert Convert .scad files to .glb binaries");
17
+ console.error(" serve Start local API server and Web Editor");
18
+ console.error(" mcp Start the Model Context Protocol (MCP) server");
19
+ console.error(
20
+ " gen Automated AI generation of 3D models and game projects",
21
+ );
22
+ console.error(" play Test and launch generated Node.js scripts");
23
+ process.exit(1);
24
+ }
25
+
26
+ // Remove the subcommand from argv so the delegated scripts parse indices exactly as they did before
27
+ process.argv.splice(2, 1);
28
+
29
+ // Dynamically import the requested command script
30
+ import(commands[command]).catch((err) => {
31
+ console.error(`Failed to execute command '${command}':`, err);
32
+ process.exit(1);
33
+ });
@@ -217,7 +217,7 @@ if (fs.existsSync(editorDistDir)) {
217
217
 
218
218
  // Start Server
219
219
  app.listen(port, () => {
220
- console.log(`🚀 scad-serve listening on http://localhost:${port}`);
220
+ console.log(`🚀 scad-gltf serve listening on http://localhost:${port}`);
221
221
  if (fs.existsSync(editorDistDir)) {
222
222
  console.log(`🌐 Editor available at: http://localhost:${port}/`);
223
223
  }
package/godot/README.md CHANGED
@@ -10,7 +10,7 @@ By leveraging the `scad-gltf` compiler under the hood, this addon allows you to
10
10
  - **Advanced Materials**: Supports standard OpenSCAD plus custom extensions for roughness, metalness, transmission, emission, and more.
11
11
  - **Hierarchical Node Animations**: Automatically parses `armature()` and `bone()` modules into Godot `Node3D` transform hierarchies animated via `AnimationPlayer` (rigid-body hierarchy animation, ideal for mechanical and articulated models).
12
12
  - **Dependency Tracking**: Smart resolution of local `include` and `use` OpenSCAD dependencies.
13
- - **Auto-Fallback Engine**: Attempts to use the high-performance CLI compiler (`scad-convert`) and seamlessly falls back to the HTTP backend (`scad-serve`) if running in a restricted environment.
13
+ - **Auto-Fallback Engine**: Attempts to use the high-performance CLI compiler (`scad-gltf convert`) and seamlessly falls back to the HTTP backend (`scad-gltf serve`) if running in a restricted environment.
14
14
 
15
15
  ---
16
16
 
@@ -42,7 +42,7 @@ When you import or reimport a `.scad` file, the plugin will attempt to compile i
42
42
 
43
43
  ### 1. CLI Mode (Default)
44
44
 
45
- The plugin will try to execute the `scad-convert` CLI command natively through your operating system. This requires the `scad-gltf` package to be installed globally (as shown in Prerequisites). This is the fastest and recommended method for Windows, macOS, and Linux desktops.
45
+ The plugin will try to execute the `scad-gltf convert` CLI command natively through your operating system. This requires the `scad-gltf` package to be installed globally (as shown in Prerequisites). This is the fastest and recommended method for Windows, macOS, and Linux desktops.
46
46
 
47
47
  ### 2. Server Fallback Mode (Termux / Android / Portable)
48
48
 
@@ -53,7 +53,7 @@ It will look for a local backend server running on `127.0.0.1:3000`. To use this
53
53
  1. Open your terminal in your project directory.
54
54
  2. Run the server using `npx`:
55
55
  ```bash
56
- npx -p scad-gltf scad-serve
56
+ npx scad-gltf serve
57
57
  ```
58
58
  3. Leave the server running in the background. Godot will now send your `.scad` code to this local server, compile it in memory, and import the resulting 3D mesh.
59
59
 
@@ -16,27 +16,28 @@ func _import_scene(path: String, flags: int, options: Dictionary) -> Object:
16
16
  var temp_glb_path = ProjectSettings.globalize_path("user://scad_cache_" + unique_id + ".glb")
17
17
 
18
18
  var args = PackedStringArray()
19
+ args.append("convert")
19
20
  args.append(global_source)
20
21
  args.append(temp_glb_path)
21
22
 
22
23
  var output = []
23
- print("Importing %s via scad-convert..." % path.get_file())
24
+ print("Importing %s via scad-gltf..." % path.get_file())
24
25
 
25
26
  var exit_code = -1
26
27
  if OS.get_name() == "Windows":
27
- var win_args = PackedStringArray(["/c", "scad-convert"])
28
+ var win_args = PackedStringArray(["/c", "scad-gltf"])
28
29
  win_args.append_array(args)
29
30
  exit_code = OS.execute("cmd.exe", win_args, output, true)
30
31
  else:
31
- exit_code = OS.execute("scad-convert", args, output, true)
32
+ exit_code = OS.execute("scad-gltf", args, output, true)
32
33
 
33
34
  if exit_code != 0:
34
- print("scad-convert conversion failed for %s. Attempting fallback to local scad-serve..." % path.get_file())
35
+ print("scad-gltf convert failed for %s. Attempting fallback to local scad-gltf serve..." % path.get_file())
35
36
  var fallback_success = _try_scad_serve_fallback(global_source, temp_glb_path)
36
37
 
37
38
  if not fallback_success:
38
- push_error("Failed to compile SCAD file: %s. Ensure Node.js is installed or scad-serve is running." % path.get_file())
39
- push_error("scad-convert output: ", "\n".join(output))
39
+ push_error("Failed to compile SCAD file: %s. Ensure Node.js is installed or scad-gltf serve is running." % path.get_file())
40
+ push_error("scad-gltf convert output: ", "\n".join(output))
40
41
  return null
41
42
 
42
43
  var gltf_doc = GLTFDocument.new()
@@ -194,7 +195,7 @@ func _try_scad_serve_fallback(source_path: String, out_glb_path: String) -> bool
194
195
  out_file.store_buffer(rb)
195
196
  out_file.close()
196
197
 
197
- print("Successfully compiled %s using scad-serve fallback." % source_path.get_file())
198
+ print("Successfully compiled %s using scad-gltf serve fallback." % source_path.get_file())
198
199
  return true
199
200
 
200
201
  return false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scad-gltf",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
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",
@@ -32,14 +32,7 @@
32
32
  "editor/dist"
33
33
  ],
34
34
  "bin": {
35
- "scad-convert": "./bin/scad-convert.js",
36
- "scad-serve": "./bin/scad-serve.js",
37
- "scad-mcp": "./bin/scad-mcp.js",
38
- "scad-godot": "./bin/scad-godot.js",
39
- "scad-web": "./bin/scad-web.js",
40
- "scad-bevy": "./bin/scad-bevy.js",
41
- "scad-gen": "./bin/scad-gen.js",
42
- "scad-play": "./bin/scad-play.js"
35
+ "scad-gltf": "./bin/scad-gltf.js"
43
36
  },
44
37
  "exports": {
45
38
  ".": "./src/ext/openscad.js",
@@ -51,10 +44,8 @@
51
44
  },
52
45
  "scripts": {
53
46
  "format": "prettier --write .",
54
- "mcp": "npx @modelcontextprotocol/inspector node ./bin/scad-mcp.js",
55
- "prepublishOnly": "npm install --prefix editor && npm run build --prefix editor",
56
- "build:godot": "node scripts/build-godot-web.js",
57
- "deploy:godot": "gh-pages -d .godot-dist --repo git@github.com:iliagrigorevdev/scad-godot.git -b gh-pages"
47
+ "mcp": "npx @modelcontextprotocol/inspector node ./bin/mcp.js",
48
+ "prepublishOnly": "npm install --prefix editor && npm run build --prefix editor"
58
49
  },
59
50
  "dependencies": {
60
51
  "@modelcontextprotocol/sdk": "^1.30.0",
@@ -63,7 +54,6 @@
63
54
  "puppeteer": "^25.9.0"
64
55
  },
65
56
  "devDependencies": {
66
- "gh-pages": "^6.3.0",
67
57
  "prettier": "3.9.6"
68
58
  }
69
59
  }
package/src/cli-utils.js CHANGED
@@ -19,7 +19,7 @@ export function hasStdinData() {
19
19
  }
20
20
  }
21
21
 
22
- export function parseTaskAndOptions() {
22
+ export function parseTaskAndOptions(startIndex = 2) {
23
23
  let task = "";
24
24
  let optionsStr = "{}";
25
25
 
@@ -29,10 +29,10 @@ export function parseTaskAndOptions() {
29
29
  } catch (e) {
30
30
  console.error("Error reading from STDIN:", e);
31
31
  }
32
- if (process.argv[2]) optionsStr = process.argv[2];
32
+ if (process.argv[startIndex]) optionsStr = process.argv[startIndex];
33
33
  } else {
34
- if (process.argv[2]) task = process.argv[2];
35
- if (process.argv[3]) optionsStr = process.argv[3];
34
+ if (process.argv[startIndex]) task = process.argv[startIndex];
35
+ if (process.argv[startIndex + 1]) optionsStr = process.argv[startIndex + 1];
36
36
  }
37
37
  return { task, optionsStr };
38
38
  }
@@ -145,7 +145,7 @@ export async function runAutomatedAIFlow(
145
145
  }
146
146
 
147
147
  const outputFilename = `generate_${projectType}_project.js`;
148
- const isRawScad = projectType === "gen";
148
+ const isRawScad = projectType === "scad";
149
149
  const modelTag = modelName ? ` (${modelName})` : "";
150
150
 
151
151
  console.log(`\n🚀 Starting automated AI generation${modelTag}...`);
@@ -163,7 +163,7 @@ export async function runAutomatedAIFlow(
163
163
  console.log("🔌 Starting local SCAD MCP server...");
164
164
  mcpTransport = new mcpClientStdio.StdioClientTransport({
165
165
  command: process.execPath,
166
- args: [path.resolve(__dirname, "../bin/scad-mcp.js")],
166
+ args: [path.resolve(__dirname, "../bin/mcp.js")],
167
167
  });
168
168
 
169
169
  mcpClient = new mcpClientIndex.Client(
@@ -460,7 +460,7 @@ export async function runAutomatedAIFlow(
460
460
  `\n🌐 Starting local viewer to preview ${extractedFilename}...`,
461
461
  );
462
462
 
463
- const scadServePath = path.resolve(__dirname, "../bin/scad-serve.js");
463
+ const scadServePath = path.resolve(__dirname, "../bin/serve.js");
464
464
  runProc = spawn(process.execPath, [scadServePath], {
465
465
  cwd: tempDir,
466
466
  stdio: "inherit",
@@ -645,43 +645,32 @@ export async function runCliApp({
645
645
  buildSystemPrompt,
646
646
  buildInputRequest,
647
647
  allowedTools = ["render_scad_model"],
648
+ argStartIndex = 2,
648
649
  }) {
649
650
  if (!projectType) {
650
651
  throw new Error("projectType is required.");
651
652
  }
652
653
 
653
- const appName = `scad-${projectType}`;
654
- const isRawScad = projectType === "gen";
655
-
656
- const { task, optionsStr } = parseTaskAndOptions();
654
+ const isRawScad = projectType === "scad";
655
+ const { task, optionsStr } = parseTaskAndOptions(argStartIndex);
657
656
 
658
657
  if (!task) {
659
658
  console.error("Error: Task parameter is required.");
659
+ console.error(
660
+ `Usage: scad-gltf gen ${projectType} "<description>" [options_json]`,
661
+ );
662
+ console.error(
663
+ ` or: echo "<description>" | scad-gltf gen ${projectType} [options_json]`,
664
+ );
665
+ console.error("");
666
+ console.error("Examples with JSON options (Automated AI flow):");
660
667
  if (isRawScad) {
661
668
  console.error(
662
- `Usage: ${appName} "<description of the 3D model to generate>" [options_json]`,
663
- );
664
- console.error(` or: echo "<description>" | ${appName} [options_json]`);
665
- console.error("");
666
- console.error("Examples with JSON options (Automated AI flow):");
667
- console.error(
668
- ` ${appName} "A modular sci-fi corridor piece" '{"openaiApiKey": "sk-...", "openaiModel": "gpt-4o"}'`,
669
- );
670
- console.error(
671
- ` ${appName} "A medieval longsword" '{"openaiBaseUrl": "http://127.0.0.1:8080/v1", "openaiModel": "llama-3"}'`,
669
+ ` scad-gltf gen ${projectType} "A modular sci-fi corridor piece" '{"openaiApiKey": "sk-...", "openaiModel": "gpt-4o"}'`,
672
670
  );
673
671
  } else {
674
672
  console.error(
675
- `Usage: ${appName} "<description of the game to generate>" [options_json]`,
676
- );
677
- console.error(` or: echo "<description>" | ${appName} [options_json]`);
678
- console.error("");
679
- console.error("Examples with JSON options (Automated AI flow):");
680
- console.error(
681
- ` ${appName} "A simple 3D game" '{"openaiApiKey": "sk-...", "openaiModel": "gpt-4o"}'`,
682
- );
683
- console.error(
684
- ` ${appName} "A simple 3D game" '{"openaiBaseUrl": "http://127.0.0.1:8080/v1", "openaiModel": "llama-3"}'`,
673
+ ` scad-gltf gen ${projectType} "A simple 3D game" '{"openaiApiKey": "sk-...", "openaiModel": "gpt-4o"}'`,
685
674
  );
686
675
  }
687
676
  process.exit(1);
@@ -0,0 +1,256 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import url from "node:url";
4
+
5
+ const __filename = url.fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ const DIR = path.resolve(__dirname, "..");
8
+
9
+ export function getProjectPrompts(projectType) {
10
+ if (projectType === "scad") {
11
+ return {
12
+ buildSystemPrompt: () =>
13
+ `You are an expert procedural 3D technical artist and OpenSCAD developer.
14
+ Your goal is to generate a single OpenSCAD (.scad) file based on the user's request.
15
+
16
+ CRITICAL WORKFLOW:
17
+ 1. Write the OpenSCAD code using the custom syntax rules provided in the request.
18
+ 2. Call the \`render_scad_model\` tool with your code to visually verify your design.
19
+ 3. If the model looks incorrect, adjust your code and re-render. Iterate until perfect.
20
+ 4. Provide your final OpenSCAD code in a standard markdown block (\`\`\`openscad).`,
21
+ allowedTools: ["render_scad_model"],
22
+ };
23
+ }
24
+
25
+ if (projectType === "bevy") {
26
+ return {
27
+ buildSystemPrompt: (
28
+ promptRules,
29
+ ) => `You are an expert Rust Bevy engine game developer and procedural 3D technical artist.
30
+
31
+ NAMING CONVENTION REQUIREMENT:
32
+ - 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
+ - NEVER use hyphens/minus signs (\`-\`) or spaces in any file or folder names.
34
+
35
+ What to generate:
36
+ 1. 3D Game Assets (.scad):
37
+ - Generate procedural 3D models for the game using OpenSCAD.
38
+ - 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
+ - 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
+
41
+ === OPENSCAD SYNTAX RULES ===
42
+ ${promptRules}
43
+ =============================
44
+
45
+ 2. Rust Bevy Project Files:
46
+ - Create the necessary files for a modern Rust Bevy engine application (e.g., \`Cargo.toml\`, \`build.rs\`, \`src/main.rs\`).
47
+ - In \`Cargo.toml\`, include \`bevy\` as a dependency.
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 game via Cargo:
49
+ \`\`\`rust
50
+ use std::process::Command;
51
+
52
+ fn main() {
53
+ // Tell Cargo to re-run this script only if the 'scad' directory changes
54
+ println!("cargo::rerun-if-changed=scad");
55
+
56
+ // Ensure cross-platform compatibility for npm global binaries
57
+ let cmd = if cfg!(target_os = "windows") {
58
+ "scad-gltf.cmd"
59
+ } else {
60
+ "scad-gltf"
61
+ };
62
+
63
+ let status = Command::new(cmd)
64
+ .args(["convert", "./scad", "./assets/models", "--cache"])
65
+ .status()
66
+ .expect("Failed to execute scad-gltf. Is scad-gltf installed globally?");
67
+
68
+ if !status.success() {
69
+ panic!("scad-gltf convert failed with status: {}", status);
70
+ }
71
+ }
72
+ \`\`\`
73
+ - Write the core application logic in \`src/main.rs\` to load and display the converted \`.glb\` files interactively. Provide standard Bevy game systems (camera, lights, movement, etc.).
74
+
75
+ 3. Delivery Format (Single Node.js Script):
76
+ - Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
77
+ - CRITICAL: The generated Node.js script MUST first create a root project folder (named using a slugified version of the project name) and output all files and folders inside this newly created project folder.
78
+ - When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
79
+ - The script must embed and write:
80
+ - Your generated \`.scad\` 3D assets.
81
+ - Your generated Rust Bevy project files (\`Cargo.toml\`, \`build.rs\`, \`src/main.rs\`).
82
+ - Ensure all string file contents inside the Node.js script are properly escaped.`,
83
+ buildInputRequest: (task) =>
84
+ `Design and implement a Rust Bevy engine game for the following concept: "${task}"`,
85
+ allowedTools: ["render_scad_model", "compile_bevy_project"],
86
+ };
87
+ }
88
+
89
+ if (projectType === "web") {
90
+ return {
91
+ buildSystemPrompt: (
92
+ promptRules,
93
+ ) => `You are an expert Web 3D developer and procedural 3D technical artist.
94
+
95
+ What to generate:
96
+ 1. 3D Web Assets (.scad):
97
+ - Generate procedural 3D models for the game using OpenSCAD.
98
+ - 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
+ - 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
+
101
+ === OPENSCAD SYNTAX RULES ===
102
+ ${promptRules}
103
+ =============================
104
+
105
+ 2. Vite Web Project Files (npm based):
106
+ - 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 game's requirements.
108
+ - In \`package.json\`, you MUST include the scad to gltf converter tool as a dev dependency:
109
+ \`"scad-gltf": "^0.3.0"\`
110
+ - 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
+ For example:
112
+ \`"predev": "scad-gltf convert ./scad ./public/models --cache"\`
113
+ \`"prebuild": "scad-gltf convert ./scad ./public/models --cache"\`
114
+ - Write the core game logic to load and display the converted \`.glb\` files interactively.
115
+
116
+ 3. Delivery Format (Single Node.js Script):
117
+ - Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
118
+ - CRITICAL: The generated Node.js script MUST first create a root project folder (named using a slugified version of the project name) and output all files and folders inside this newly created project folder.
119
+ - When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
120
+ - The script must embed and write:
121
+ - Your generated \`.scad\` 3D assets.
122
+ - Your generated Vite web project files.
123
+ - Ensure all string file contents inside the Node.js script are properly escaped.`,
124
+ buildInputRequest: (task) =>
125
+ `Design and implement a web-based 3D glTF game using Vite for the following concept: "${task}"`,
126
+ allowedTools: ["render_scad_model"],
127
+ };
128
+ }
129
+
130
+ if (projectType === "godot") {
131
+ return {
132
+ buildSystemPrompt: (promptRules, options = {}) => {
133
+ const hasUserScadFiles =
134
+ Array.isArray(options.scadFiles) && options.scadFiles.length > 0;
135
+
136
+ const systemPrompt = `You are an expert Godot 4 game developer and procedural 3D technical artist.
137
+
138
+ NAMING CONVENTION REQUIREMENT:
139
+ - 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
+ - NEVER use hyphens/minus signs (\`-\`) or spaces in any file or folder names.
141
+
142
+ What to generate:
143
+ 1. 3D Game Assets (.scad):
144
+ - Generate procedural 3D models for the game using OpenSCAD.
145
+ - 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
+ - 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
+ - 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.
148
+ - CRITICAL Coordinate Mapping: The SCAD to glTF converter used by the Godot importer automatically converts OpenSCAD's Z-up coordinate system to Godot's Y-up coordinate system. Design your models naturally in OpenSCAD using this exact mapping:
149
+ * OpenSCAD +X (Right) -> Godot +X (Right)
150
+ * OpenSCAD -X (Left) -> Godot -X (Left)
151
+ * OpenSCAD +Y (Forward) -> Godot -Z (Forward)
152
+ * OpenSCAD -Y (Back) -> Godot +Z (Back)
153
+ * OpenSCAD +Z (Up) -> Godot +Y (Up)
154
+ DO NOT manually apply root rotations (e.g., \`rotate([90, 0, 0])\`) to compensate for Godot.
155
+ - 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:
156
+
157
+ === OPENSCAD SYNTAX RULES ===
158
+ ${promptRules}
159
+ =============================
160
+
161
+ 2. Godot 4 Project Files:
162
+ - Create the necessary GDScript (\`.gd\`) and scene (\`.tscn\`) files to implement the game logic, responsive player input controls, and a core gameplay loop.
163
+ - The scenes should directly instance the generated \`.scad\` files (the provided addon will handle importing them as 3D scenes).
164
+ - GDScript Coordinate, Forward, and Left/Right Conventions:
165
+ * 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.
166
+ * Backward is +Z: \`Vector3.BACK\` is \`Vector3(0, 0, 1)\` (\`transform.basis.z\`).
167
+ * Right is +X: \`Vector3.RIGHT\` is \`Vector3(1, 0, 0)\` (\`transform.basis.x\`).
168
+ * Left is -X: \`Vector3.LEFT\` is \`Vector3(-1, 0, 0)\` (\`-transform.basis.x\`).
169
+ * Left/Right Convention in Godot Script: Maintain anatomical consistency in scripts—character Right is along +X (\`transform.basis.x\`) and character Left is along -X (\`-transform.basis.x\`).
170
+ * 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
+ - Generate a \`project.godot\` file. It must configure the project and automatically enable the \`scad_importer\` plugin.
172
+ - Generate a \`.gitignore\` file that ignores the \`.godot/\` folder.
173
+ - Generate a \`README.md\` file that documents the project, gameplay mechanics, and controls.
174
+
175
+ 3. Delivery Format (Single Node.js Script):
176
+ - 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., \`my_game_project\`) and output all files and folders inside this newly created project folder.
178
+ - When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
179
+ - The script must embed and write:
180
+ - Your generated \`.scad\` game assets.
181
+ - Your generated Godot project files.
182
+ - The exact source code of the provided \`addons/scad_importer/*\` files, placed in their correct respective paths.${
183
+ hasUserScadFiles
184
+ ? "\n - The exact source code of the provided user `.scad` files, placed in the appropriate project folders."
185
+ : ""
186
+ }
187
+ - Ensure all string file contents inside the Node.js script are properly escaped.
188
+ - TESTING CAPABILITY: You have access to the \`test_godot_project\` tool. If you want to verify your code before outputting your final response, you can pass your complete generated Node.js script as the \`nodejs_script\` parameter. The server will execute it in a temporary folder and run the Godot tests automatically.`;
189
+
190
+ // Gather Addon Files content
191
+ const addonDir = path.join(DIR, "godot", "addons", "scad_importer");
192
+ let addonFiles = [];
193
+ try {
194
+ if (fs.existsSync(addonDir)) {
195
+ const files = fs.readdirSync(addonDir);
196
+ for (const file of files) {
197
+ const fullPath = path.join(addonDir, file);
198
+ if (
199
+ fs.statSync(fullPath).isFile() &&
200
+ (file.endsWith(".gd") || file.endsWith(".cfg"))
201
+ ) {
202
+ addonFiles.push(fullPath);
203
+ }
204
+ }
205
+ }
206
+ } catch (e) {
207
+ console.error("Warning: Could not read addon directory.", e);
208
+ }
209
+
210
+ let systemClipboardOutput = `${systemPrompt}\n\n`;
211
+
212
+ for (const file of addonFiles) {
213
+ try {
214
+ const content = fs
215
+ .readFileSync(file, "utf-8")
216
+ .replace(/\r\n/g, "\n");
217
+ const relativePath = path.relative(DIR, file).replace(/\\/g, "/");
218
+ const lang = file.endsWith(".gd") ? "gdscript" : "text";
219
+ systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`${lang}\n${content}\n\`\`\`\n\n`;
220
+ } catch (e) {
221
+ console.error(
222
+ `Warning: Skipping '${file}'. It is not a readable file.`,
223
+ );
224
+ }
225
+ }
226
+
227
+ if (hasUserScadFiles) {
228
+ 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 game logic.\n\n`;
230
+ for (const file of options.scadFiles) {
231
+ try {
232
+ const content = fs
233
+ .readFileSync(file, "utf-8")
234
+ .replace(/\r\n/g, "\n");
235
+ const relativePath = path.isAbsolute(file)
236
+ ? path.relative(process.cwd(), file).replace(/\\/g, "/")
237
+ : file.replace(/\\/g, "/");
238
+ systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`openscad\n${content}\n\`\`\`\n\n`;
239
+ } catch (e) {
240
+ console.error(
241
+ `Warning: Skipping user SCAD file '${file}'. It is not a readable file.`,
242
+ );
243
+ }
244
+ }
245
+ }
246
+
247
+ return systemClipboardOutput.trimEnd() + "\n";
248
+ },
249
+ buildInputRequest: (task) =>
250
+ `Design and implement a Godot 4 project for the following game concept: "${task}"`,
251
+ allowedTools: ["render_scad_model", "test_godot_project"],
252
+ };
253
+ }
254
+
255
+ throw new Error(`Unknown project type: ${projectType}`);
256
+ }
package/bin/scad-bevy.js DELETED
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { runCliApp } from "../src/cli-utils.js";
4
-
5
- async function main() {
6
- await runCliApp({
7
- projectType: "bevy",
8
- buildSystemPrompt: (
9
- promptRules,
10
- ) => `You are an expert Rust Bevy engine game developer and procedural 3D technical artist.
11
-
12
- NAMING CONVENTION REQUIREMENT:
13
- - 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\`).
14
- - NEVER use hyphens/minus signs (\`-\`) or spaces in any file or folder names.
15
-
16
- What to generate:
17
- 1. 3D Game Assets (.scad):
18
- - Generate procedural 3D models for the game using OpenSCAD.
19
- - 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.
20
- - 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:
21
-
22
- === OPENSCAD SYNTAX RULES ===
23
- ${promptRules}
24
- =============================
25
-
26
- 2. Rust Bevy Project Files:
27
- - Create the necessary files for a modern Rust Bevy engine application (e.g., \`Cargo.toml\`, \`build.rs\`, \`src/main.rs\`).
28
- - In \`Cargo.toml\`, include \`bevy\` as a dependency.
29
- - 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 game via Cargo:
30
- \`\`\`rust
31
- use std::process::Command;
32
-
33
- fn main() {
34
- // Tell Cargo to re-run this script only if the 'scad' directory changes
35
- println!("cargo::rerun-if-changed=scad");
36
-
37
- // Ensure cross-platform compatibility for npm global binaries
38
- let cmd = if cfg!(target_os = "windows") {
39
- "scad-convert.cmd"
40
- } else {
41
- "scad-convert"
42
- };
43
-
44
- let status = Command::new(cmd)
45
- .args(["./scad", "./assets/models", "--cache"])
46
- .status()
47
- .expect("Failed to execute scad-convert. Is scad-gltf installed globally?");
48
-
49
- if !status.success() {
50
- panic!("scad-convert failed with status: {}", status);
51
- }
52
- }
53
- \`\`\`
54
- - Write the core application logic in \`src/main.rs\` to load and display the converted \`.glb\` files interactively. Provide standard Bevy game systems (camera, lights, movement, etc.).
55
-
56
- 3. Delivery Format (Single Node.js Script):
57
- - Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
58
- - CRITICAL: The generated Node.js script MUST first create a root project folder (named using a slugified version of the project name) and output all files and folders inside this newly created project folder.
59
- - When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
60
- - The script must embed and write:
61
- - Your generated \`.scad\` 3D assets.
62
- - Your generated Rust Bevy project files (\`Cargo.toml\`, \`build.rs\`, \`src/main.rs\`).
63
- - Ensure all string file contents inside the Node.js script are properly escaped.`,
64
- buildInputRequest: (task) =>
65
- `Design and implement a Rust Bevy engine game for the following concept: "${task}"`,
66
- allowedTools: ["render_scad_model", "compile_bevy_project"],
67
- });
68
- }
69
-
70
- // Execute and handle unhandled runtime errors
71
- main().catch((err) => {
72
- console.error("An unexpected error occurred:", err);
73
- process.exit(1);
74
- });
package/bin/scad-gen.js DELETED
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { runCliApp } from "../src/cli-utils.js";
4
-
5
- async function main() {
6
- await runCliApp({
7
- projectType: "gen",
8
- buildSystemPrompt: () =>
9
- `You are an expert procedural 3D technical artist and OpenSCAD developer.
10
- Your goal is to generate a single OpenSCAD (.scad) file based on the user's request.
11
-
12
- CRITICAL WORKFLOW:
13
- 1. Write the OpenSCAD code using the custom syntax rules provided in the request.
14
- 2. Call the \`render_scad_model\` tool with your code to visually verify your design.
15
- 3. If the model looks incorrect, adjust your code and re-render. Iterate until perfect.
16
- 4. Provide your final OpenSCAD code in a standard markdown block (\`\`\`openscad).`,
17
- allowedTools: ["render_scad_model"],
18
- });
19
- }
20
-
21
- // Execute and handle unhandled runtime errors
22
- main().catch((err) => {
23
- console.error("An unexpected error occurred:", err);
24
- process.exit(1);
25
- });
package/bin/scad-godot.js DELETED
@@ -1,140 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import url from "node:url";
6
- import { runCliApp } from "../src/cli-utils.js";
7
-
8
- const __filename = url.fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
- const DIR = path.resolve(__dirname, "..");
11
-
12
- async function main() {
13
- await runCliApp({
14
- projectType: "godot",
15
- buildSystemPrompt: (promptRules, options) => {
16
- const hasUserScadFiles =
17
- Array.isArray(options.scadFiles) && options.scadFiles.length > 0;
18
-
19
- const systemPrompt = `You are an expert Godot 4 game developer and procedural 3D technical artist.
20
-
21
- NAMING CONVENTION REQUIREMENT:
22
- - 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\`).
23
- - NEVER use hyphens/minus signs (\`-\`) or spaces in any file or folder names.
24
-
25
- What to generate:
26
- 1. 3D Game Assets (.scad):
27
- - Generate procedural 3D models for the game using OpenSCAD.
28
- - 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.
29
- - 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).
30
- - 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.
31
- - CRITICAL Coordinate Mapping: The SCAD to glTF converter used by the Godot importer automatically converts OpenSCAD's Z-up coordinate system to Godot's Y-up coordinate system. Design your models naturally in OpenSCAD using this exact mapping:
32
- * OpenSCAD +X (Right) -> Godot +X (Right)
33
- * OpenSCAD -X (Left) -> Godot -X (Left)
34
- * OpenSCAD +Y (Forward) -> Godot -Z (Forward)
35
- * OpenSCAD -Y (Back) -> Godot +Z (Back)
36
- * OpenSCAD +Z (Up) -> Godot +Y (Up)
37
- DO NOT manually apply root rotations (e.g., \`rotate([90, 0, 0])\`) to compensate for Godot.
38
- - 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:
39
-
40
- === OPENSCAD SYNTAX RULES ===
41
- ${promptRules}
42
- =============================
43
-
44
- 2. Godot 4 Project Files:
45
- - Create the necessary GDScript (\`.gd\`) and scene (\`.tscn\`) files to implement the game logic, responsive player input controls, and a core gameplay loop.
46
- - The scenes should directly instance the generated \`.scad\` files (the provided addon will handle importing them as 3D scenes).
47
- - GDScript Coordinate, Forward, and Left/Right Conventions:
48
- * 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.
49
- * Backward is +Z: \`Vector3.BACK\` is \`Vector3(0, 0, 1)\` (\`transform.basis.z\`).
50
- * Right is +X: \`Vector3.RIGHT\` is \`Vector3(1, 0, 0)\` (\`transform.basis.x\`).
51
- * Left is -X: \`Vector3.LEFT\` is \`Vector3(-1, 0, 0)\` (\`-transform.basis.x\`).
52
- * Left/Right Convention in Godot Script: Maintain anatomical consistency in scripts—character Right is along +X (\`transform.basis.x\`) and character Left is along -X (\`-transform.basis.x\`).
53
- * 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.
54
- - Generate a \`project.godot\` file. It must configure the project and automatically enable the \`scad_importer\` plugin.
55
- - Generate a \`.gitignore\` file that ignores the \`.godot/\` folder.
56
- - Generate a \`README.md\` file that documents the project, gameplay mechanics, and controls.
57
-
58
- 3. Delivery Format (Single Node.js Script):
59
- - Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
60
- - CRITICAL: The generated Node.js script MUST first create a root project folder (named using snake_case with underscores, e.g., \`my_game_project\`) and output all files and folders inside this newly created project folder.
61
- - When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
62
- - The script must embed and write:
63
- - Your generated \`.scad\` game assets.
64
- - Your generated Godot project files.
65
- - The exact source code of the provided \`addons/scad_importer/*\` files, placed in their correct respective paths.${
66
- hasUserScadFiles
67
- ? "\n - The exact source code of the provided user `.scad` files, placed in the appropriate project folders."
68
- : ""
69
- }
70
- - Ensure all string file contents inside the Node.js script are properly escaped.
71
- - TESTING CAPABILITY: You have access to the \`test_godot_project\` tool. If you want to verify your code before outputting your final response, you can pass your complete generated Node.js script as the \`nodejs_script\` parameter. The server will execute it in a temporary folder and run the Godot tests automatically.`;
72
-
73
- // Gather Addon Files content
74
- const addonDir = path.join(DIR, "godot", "addons", "scad_importer");
75
- let addonFiles = [];
76
- try {
77
- if (fs.existsSync(addonDir)) {
78
- const files = fs.readdirSync(addonDir);
79
- for (const file of files) {
80
- const fullPath = path.join(addonDir, file);
81
- if (
82
- fs.statSync(fullPath).isFile() &&
83
- (file.endsWith(".gd") || file.endsWith(".cfg"))
84
- ) {
85
- addonFiles.push(fullPath);
86
- }
87
- }
88
- }
89
- } catch (e) {
90
- console.error("Warning: Could not read addon directory.", e);
91
- }
92
-
93
- let systemClipboardOutput = `${systemPrompt}\n\n`;
94
-
95
- for (const file of addonFiles) {
96
- try {
97
- const content = fs.readFileSync(file, "utf-8").replace(/\r\n/g, "\n");
98
- const relativePath = path.relative(DIR, file).replace(/\\/g, "/");
99
- const lang = file.endsWith(".gd") ? "gdscript" : "text";
100
- systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`${lang}\n${content}\n\`\`\`\n\n`;
101
- } catch (e) {
102
- console.error(
103
- `Warning: Skipping '${file}'. It is not a readable file.`,
104
- );
105
- }
106
- }
107
-
108
- if (hasUserScadFiles) {
109
- systemClipboardOutput += `=== USER PROVIDED OPENSCAD FILES ===\n`;
110
- 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 game logic.\n\n`;
111
- for (const file of options.scadFiles) {
112
- try {
113
- const content = fs
114
- .readFileSync(file, "utf-8")
115
- .replace(/\r\n/g, "\n");
116
- const relativePath = path.isAbsolute(file)
117
- ? path.relative(process.cwd(), file).replace(/\\/g, "/")
118
- : file.replace(/\\/g, "/");
119
- systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`openscad\n${content}\n\`\`\`\n\n`;
120
- } catch (e) {
121
- console.error(
122
- `Warning: Skipping user SCAD file '${file}'. It is not a readable file.`,
123
- );
124
- }
125
- }
126
- }
127
-
128
- return systemClipboardOutput.trimEnd() + "\n";
129
- },
130
- buildInputRequest: (task) =>
131
- `Design and implement a Godot 4 project for the following game concept: "${task}"`,
132
- allowedTools: ["render_scad_model", "test_godot_project"],
133
- });
134
- }
135
-
136
- // Execute and handle unhandled runtime errors
137
- main().catch((err) => {
138
- console.error("An unexpected error occurred:", err);
139
- process.exit(1);
140
- });
package/bin/scad-web.js DELETED
@@ -1,51 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { runCliApp } from "../src/cli-utils.js";
4
-
5
- async function main() {
6
- await runCliApp({
7
- projectType: "web",
8
- buildSystemPrompt: (
9
- promptRules,
10
- ) => `You are an expert Web 3D developer and procedural 3D technical artist.
11
-
12
- What to generate:
13
- 1. 3D Web Assets (.scad):
14
- - Generate procedural 3D models for the game using OpenSCAD.
15
- - 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.
16
- - 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:
17
-
18
- === OPENSCAD SYNTAX RULES ===
19
- ${promptRules}
20
- =============================
21
-
22
- 2. Vite Web Project Files (npm based):
23
- - Create the necessary files for a modern web application (e.g., \`package.json\`, \`index.html\`, \`main.js\`).
24
- - 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 game's requirements.
25
- - In \`package.json\`, you MUST include the scad to gltf converter tool as a dev dependency:
26
- \`"scad-gltf": "^0.1.0"\`
27
- - 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.
28
- For example:
29
- \`"predev": "scad-convert ./scad ./public/models --cache"\`
30
- \`"prebuild": "scad-convert ./scad ./public/models --cache"\`
31
- - Write the core game logic to load and display the converted \`.glb\` files interactively.
32
-
33
- 3. Delivery Format (Single Node.js Script):
34
- - Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
35
- - CRITICAL: The generated Node.js script MUST first create a root project folder (named using a slugified version of the project name) and output all files and folders inside this newly created project folder.
36
- - When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
37
- - The script must embed and write:
38
- - Your generated \`.scad\` 3D assets.
39
- - Your generated Vite web project files.
40
- - Ensure all string file contents inside the Node.js script are properly escaped.`,
41
- buildInputRequest: (task) =>
42
- `Design and implement a web-based 3D glTF game using Vite for the following concept: "${task}"`,
43
- allowedTools: ["render_scad_model"],
44
- });
45
- }
46
-
47
- // Execute and handle unhandled runtime errors
48
- main().catch((err) => {
49
- console.error("An unexpected error occurred:", err);
50
- process.exit(1);
51
- });