scad-gltf 0.1.3 → 0.2.1
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 +51 -7
- package/bin/scad-bevy.js +74 -0
- package/bin/scad-gen.js +25 -0
- package/bin/scad-godot.js +72 -203
- package/bin/scad-mcp.js +641 -2
- package/bin/scad-play.js +172 -0
- package/bin/scad-web.js +14 -166
- package/editor/dist/assets/{preview-fQfL_FJ-.css → preview-A6M04CFK.css} +1 -1
- package/editor/dist/assets/preview-CCwT_Mrn.js +1 -0
- package/editor/dist/content.css +48 -0
- package/editor/dist/content.js +19 -2
- package/editor/dist/preview.html +14 -2
- package/editor/dist/sw.js +1 -1
- package/godot/examples/kids_digit_writing.js +2532 -0
- package/godot/export_presets.cfg +1 -1
- package/package.json +5 -3
- package/src/cli-utils.js +792 -0
- package/src/godot-utils.js +179 -0
- package/editor/dist/assets/preview-CA_S_fWF.js +0 -1
package/README.md
CHANGED
|
@@ -22,7 +22,8 @@ The C++ source code for this custom OpenSCAD version is included directly in thi
|
|
|
22
22
|
- **CLI Converter:** Bundled `scad-convert` CLI utility for single file and batch compiling `.scad` files with smart dependency hashing.
|
|
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
|
-
- **Godot 4
|
|
25
|
+
- **Godot 4, Rust Bevy & Web Integration:** Native Godot 4 importer addon, Rust Bevy compile-time `build.rs` integration, and prebuilt AI-generated game examples for Godot and Bevy.
|
|
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
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
@@ -219,6 +220,7 @@ When asking an LLM (like Gemini) to generate OpenSCAD code, the extension automa
|
|
|
219
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.
|
|
220
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.
|
|
221
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.
|
|
222
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.
|
|
223
225
|
- **Local Workspace Saving:** Directly save and overwrite models to your local directory when running `scad-serve`.
|
|
224
226
|
|
|
@@ -259,6 +261,7 @@ Instead of generating code blindly, the AI can compile its script, render the 3D
|
|
|
259
261
|
|
|
260
262
|
- `get_scad_prompt`: Injects the custom OpenSCAD syntax rules (PBR, animations, baking) into the AI's context.
|
|
261
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.
|
|
262
265
|
|
|
263
266
|
### Setup
|
|
264
267
|
|
|
@@ -294,6 +297,53 @@ Try prebuilt AI-generated Godot games directly in your browser: [https://iliagri
|
|
|
294
297
|
|
|
295
298
|
---
|
|
296
299
|
|
|
300
|
+
### Automated AI Generation (OpenAI-Compatible API)
|
|
301
|
+
|
|
302
|
+
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.
|
|
303
|
+
|
|
304
|
+
**Option 1: Using OpenAI (GPT-4o)**
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
export OPENAI_BASE_URL="https://api.openai.com/v1"
|
|
308
|
+
export OPENAI_API_KEY="sk-..."
|
|
309
|
+
export OPENAI_MODEL="gpt-4o"
|
|
310
|
+
scad-bevy "A 3D spaceship shooter game"
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
**Option 2: Using Google Gemini (via OpenAI compatibility)**
|
|
314
|
+
Because Gemini provides an official OpenAI-compatible endpoint, you can configure it using standard OpenAI variables:
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
export OPENAI_API_KEY="AIzaSy..."
|
|
318
|
+
export OPENAI_BASE_URL="https://generativelanguage.googleapis.com/v1beta/openai/"
|
|
319
|
+
export OPENAI_MODEL="gemini-3.8-flash"
|
|
320
|
+
scad-godot "A fast-paced 3D hovercraft racing game"
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Option 3: Using Local Server**
|
|
324
|
+
You can use completely local, uncensored, or fine-tuned vision models by overriding the OpenAI Base URL to point to a local inference server (like `llama.cpp`'s API server).
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
export OPENAI_BASE_URL="http://127.0.0.1:8080/v1"
|
|
328
|
+
export OPENAI_MODEL="llama-3" # Or whatever model name you've loaded
|
|
329
|
+
scad-web "A 3D configurator app"
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Passing Parameters via JSON**
|
|
333
|
+
You can also pass credentials inline as a JSON string argument instead of modifying environment variables:
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
scad-godot "A futuristic tank game" '{"openaiBaseUrl": "http://127.0.0.1:8080/v1", "openaiModel": "llama-3"}'
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
_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!_
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
node generate_godot_project.js
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
297
347
|
## Extended OpenSCAD Syntax
|
|
298
348
|
|
|
299
349
|
This custom fork introduces new syntax not found in standard OpenSCAD.
|
|
@@ -420,8 +470,6 @@ const promptContext = generatePrompt(description, {
|
|
|
420
470
|
console.log(promptContext);
|
|
421
471
|
```
|
|
422
472
|
|
|
423
|
-
---
|
|
424
|
-
|
|
425
473
|
### Workflow
|
|
426
474
|
|
|
427
475
|
Once connected, an AI assistant can use the server to execute the following loop:
|
|
@@ -490,7 +538,3 @@ This repository includes a custom fork of OpenSCAD in the `openscad/` subfolder
|
|
|
490
538
|
- **Path Tracing:** The web editor utilizes [three-gpu-pathtracer](https://github.com/gkjohnson/three-gpu-pathtracer) for high-quality rendering.
|
|
491
539
|
- **Environment Map (HDR)**: [Aristea Wreck Puresky](https://polyhaven.com/a/aristea_wreck_puresky) by **Jarod Guest** via [Poly Haven](https://polyhaven.com/). Licensed under [CC0](https://polyhaven.com/license).
|
|
492
540
|
- **License:** See the `LICENSE` file (GPL-2.0 or later, inheriting from standard OpenSCAD).
|
|
493
|
-
|
|
494
|
-
```
|
|
495
|
-
|
|
496
|
-
```
|
package/bin/scad-bevy.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
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
CHANGED
|
@@ -3,143 +3,20 @@
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import url from "node:url";
|
|
6
|
-
import
|
|
6
|
+
import { runCliApp } from "../src/cli-utils.js";
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
const __filename = fs.realpathSync(url.fileURLToPath(import.meta.url));
|
|
8
|
+
const __filename = url.fileURLToPath(import.meta.url);
|
|
10
9
|
const __dirname = path.dirname(__filename);
|
|
11
10
|
const DIR = path.resolve(__dirname, "..");
|
|
12
11
|
|
|
13
|
-
// Safely detect if actual data is being piped into the script via STDIN
|
|
14
|
-
function hasStdinData() {
|
|
15
|
-
try {
|
|
16
|
-
const stat = fs.fstatSync(0); // 0 is the file descriptor for STDIN
|
|
17
|
-
// isFIFO means piped (echo "foo" | script)
|
|
18
|
-
// isFile means redirected (script < foo.txt)
|
|
19
|
-
return stat.isFIFO() || stat.isFile();
|
|
20
|
-
} catch (e) {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Writes text to system clipboard
|
|
26
|
-
async function writeToClipboard(text) {
|
|
27
|
-
const clipboardy = (await import("clipboardy")).default;
|
|
28
|
-
await clipboardy.write(text);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function waitForEnter(message) {
|
|
32
|
-
return new Promise((resolve) => {
|
|
33
|
-
// If standard input was piped/redirected, we need to bypass it and read from the actual terminal
|
|
34
|
-
if (!process.stdin.isTTY) {
|
|
35
|
-
try {
|
|
36
|
-
const tty = process.platform === "win32" ? "CONIN$" : "/dev/tty";
|
|
37
|
-
const fd = fs.openSync(tty, "rs");
|
|
38
|
-
process.stdout.write(message);
|
|
39
|
-
const buf = Buffer.alloc(1);
|
|
40
|
-
fs.readSync(fd, buf, 0, 1, null);
|
|
41
|
-
fs.closeSync(fd);
|
|
42
|
-
console.log();
|
|
43
|
-
resolve();
|
|
44
|
-
return;
|
|
45
|
-
} catch (e) {
|
|
46
|
-
console.log(
|
|
47
|
-
message +
|
|
48
|
-
" (Auto-continuing due to non-interactive terminal environment)",
|
|
49
|
-
);
|
|
50
|
-
resolve();
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// For standard TTY terminals
|
|
56
|
-
const rl = readline.createInterface({
|
|
57
|
-
input: process.stdin,
|
|
58
|
-
output: process.stdout,
|
|
59
|
-
});
|
|
60
|
-
rl.question(message, () => {
|
|
61
|
-
rl.close();
|
|
62
|
-
resolve();
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
12
|
async function main() {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
task = fs.readFileSync(0, "utf-8").trim();
|
|
75
|
-
} catch (e) {
|
|
76
|
-
console.error("Error reading from STDIN:", e);
|
|
77
|
-
}
|
|
78
|
-
if (process.argv[2]) optionsStr = process.argv[2];
|
|
79
|
-
} else {
|
|
80
|
-
if (process.argv[2]) task = process.argv[2];
|
|
81
|
-
if (process.argv[3]) optionsStr = process.argv[3];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (!task) {
|
|
85
|
-
console.error("Error: Task parameter is required.");
|
|
86
|
-
console.error(
|
|
87
|
-
'Usage: scad-godot "<description of the game to generate>" [options_json]',
|
|
88
|
-
);
|
|
89
|
-
console.error(' or: echo "<description>" | scad-godot [options_json]');
|
|
90
|
-
console.error("");
|
|
91
|
-
console.error("Example with JSON options:");
|
|
92
|
-
console.error(
|
|
93
|
-
' scad-godot "Game description" \'{"animation": false, "bakeColors": true}\'',
|
|
94
|
-
);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 2. Parse Options JSON
|
|
99
|
-
// Disable heavy PBR features by default
|
|
100
|
-
let options = {
|
|
101
|
-
transmission: false,
|
|
102
|
-
clearcoat: false,
|
|
103
|
-
sheen: false,
|
|
104
|
-
iridescence: false,
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
if (optionsStr) {
|
|
108
|
-
try {
|
|
109
|
-
const parsed = JSON.parse(optionsStr);
|
|
110
|
-
options = { ...options, ...parsed }; // User provided options override defaults
|
|
111
|
-
} catch (e) {
|
|
112
|
-
console.error(`Invalid JSON options: ${optionsStr}`);
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Disable the modelName instructions specifically for the Godot wrapper context
|
|
118
|
-
options.modelName = false;
|
|
13
|
+
await runCliApp({
|
|
14
|
+
projectType: "godot",
|
|
15
|
+
buildSystemPrompt: (promptRules, options) => {
|
|
16
|
+
const hasUserScadFiles =
|
|
17
|
+
Array.isArray(options.scadFiles) && options.scadFiles.length > 0;
|
|
119
18
|
|
|
120
|
-
|
|
121
|
-
let generatePrompt;
|
|
122
|
-
try {
|
|
123
|
-
const promptJsPath = path.join(DIR, "src", "prompt.js");
|
|
124
|
-
const promptModuleUrl = url.pathToFileURL(promptJsPath).href;
|
|
125
|
-
const m = await import(promptModuleUrl);
|
|
126
|
-
generatePrompt = m.generatePrompt;
|
|
127
|
-
} catch (e) {
|
|
128
|
-
console.error(`Error loading ${path.join("src", "prompt.js")}:`, e);
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
let promptRules = "";
|
|
133
|
-
try {
|
|
134
|
-
promptRules = generatePrompt("the 3D assets for the game", options);
|
|
135
|
-
} catch (e) {
|
|
136
|
-
console.error("Error generating prompt rules from prompt.js:");
|
|
137
|
-
console.error(e);
|
|
138
|
-
process.exit(1);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 4. Construct the Main Godot Prompt System Text (System Instructions)
|
|
142
|
-
const systemPrompt = `You are an expert Godot 4 game developer and procedural 3D technical artist.
|
|
19
|
+
const systemPrompt = `You are an expert Godot 4 game developer and procedural 3D technical artist.
|
|
143
20
|
|
|
144
21
|
NAMING CONVENTION REQUIREMENT:
|
|
145
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\`).
|
|
@@ -185,83 +62,75 @@ ${promptRules}
|
|
|
185
62
|
- The script must embed and write:
|
|
186
63
|
- Your generated \`.scad\` game assets.
|
|
187
64
|
- Your generated Godot project files.
|
|
188
|
-
- The exact source code of the provided \`addons/scad_importer/*\` files, placed in their correct respective paths
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
+
}
|
|
206
88
|
}
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.error("Warning: Could not read addon directory.", e);
|
|
207
91
|
}
|
|
208
|
-
}
|
|
209
|
-
} catch (e) {
|
|
210
|
-
console.error("Warning: Could not read addon directory.", e);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// 6. Format the unified system instructions clipboard output
|
|
214
|
-
let systemClipboardOutput = `${systemPrompt}\n\n`;
|
|
215
|
-
|
|
216
|
-
for (const file of addonFiles) {
|
|
217
|
-
try {
|
|
218
|
-
// Normalize line endings to avoid mixed line-ending layout loops in web editors
|
|
219
|
-
const content = fs.readFileSync(file, "utf-8").replace(/\r\n/g, "\n");
|
|
220
|
-
// Format to use relative paths and force forward slashes for LLM clarity
|
|
221
|
-
const relativePath = path.relative(DIR, file).replace(/\\/g, "/");
|
|
222
|
-
|
|
223
|
-
// Add explicit language tags to prevent catastrophic regex backtracking
|
|
224
|
-
// during the Markdown parser's language auto-detection step.
|
|
225
|
-
const lang = file.endsWith(".gd") ? "gdscript" : "text";
|
|
226
|
-
systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`${lang}\n${content}\n\`\`\`\n\n`;
|
|
227
|
-
} catch (e) {
|
|
228
|
-
console.error(`Warning: Skipping '${file}'. It is not a readable file.`);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
92
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
process.exit(1);
|
|
247
|
-
}
|
|
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
|
+
}
|
|
248
107
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
}
|
|
253
127
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
} catch (err) {
|
|
261
|
-
console.error("Error: Failed to copy input request to the clipboard.");
|
|
262
|
-
console.error(err.message);
|
|
263
|
-
process.exit(1);
|
|
264
|
-
}
|
|
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
|
+
});
|
|
265
134
|
}
|
|
266
135
|
|
|
267
136
|
// Execute and handle unhandled runtime errors
|