scad-gltf 0.1.4 → 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 +50 -3
- package/bin/scad-bevy.js +74 -0
- package/bin/scad-gen.js +25 -0
- package/bin/scad-godot.js +65 -222
- package/bin/scad-mcp.js +641 -2
- package/bin/scad-play.js +172 -0
- package/bin/scad-web.js +14 -166
- package/package.json +5 -2
- package/src/cli-utils.js +792 -0
- package/src/godot-utils.js +179 -0
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
|
|
|
@@ -260,6 +261,7 @@ Instead of generating code blindly, the AI can compile its script, render the 3D
|
|
|
260
261
|
|
|
261
262
|
- `get_scad_prompt`: Injects the custom OpenSCAD syntax rules (PBR, animations, baking) into the AI's context.
|
|
262
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.
|
|
263
265
|
|
|
264
266
|
### Setup
|
|
265
267
|
|
|
@@ -295,6 +297,53 @@ Try prebuilt AI-generated Godot games directly in your browser: [https://iliagri
|
|
|
295
297
|
|
|
296
298
|
---
|
|
297
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
|
+
|
|
298
347
|
## Extended OpenSCAD Syntax
|
|
299
348
|
|
|
300
349
|
This custom fork introduces new syntax not found in standard OpenSCAD.
|
|
@@ -421,8 +470,6 @@ const promptContext = generatePrompt(description, {
|
|
|
421
470
|
console.log(promptContext);
|
|
422
471
|
```
|
|
423
472
|
|
|
424
|
-
---
|
|
425
|
-
|
|
426
473
|
### Workflow
|
|
427
474
|
|
|
428
475
|
Once connected, an AI assistant can use the server to execute the following loop:
|
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,147 +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, "scadFiles": ["player.scad"]}\'',
|
|
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
|
-
scadFiles: [],
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
if (optionsStr) {
|
|
109
|
-
try {
|
|
110
|
-
const parsed = JSON.parse(optionsStr);
|
|
111
|
-
options = { ...options, ...parsed }; // User provided options override defaults
|
|
112
|
-
} catch (e) {
|
|
113
|
-
console.error(`Invalid JSON options: ${optionsStr}`);
|
|
114
|
-
process.exit(1);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Disable the modelName instructions specifically for the Godot wrapper context
|
|
119
|
-
options.modelName = false;
|
|
120
|
-
|
|
121
|
-
// 3. Dynamically import and generate prompt rules from src/prompt.js
|
|
122
|
-
let generatePrompt;
|
|
123
|
-
try {
|
|
124
|
-
const promptJsPath = path.join(DIR, "src", "prompt.js");
|
|
125
|
-
const promptModuleUrl = url.pathToFileURL(promptJsPath).href;
|
|
126
|
-
const m = await import(promptModuleUrl);
|
|
127
|
-
generatePrompt = m.generatePrompt;
|
|
128
|
-
} catch (e) {
|
|
129
|
-
console.error(`Error loading ${path.join("src", "prompt.js")}:`, e);
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
13
|
+
await runCliApp({
|
|
14
|
+
projectType: "godot",
|
|
15
|
+
buildSystemPrompt: (promptRules, options) => {
|
|
16
|
+
const hasUserScadFiles =
|
|
17
|
+
Array.isArray(options.scadFiles) && options.scadFiles.length > 0;
|
|
132
18
|
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
promptRules = generatePrompt("the 3D assets for the game", options);
|
|
136
|
-
} catch (e) {
|
|
137
|
-
console.error("Error generating prompt rules from prompt.js:");
|
|
138
|
-
console.error(e);
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const hasUserScadFiles =
|
|
143
|
-
Array.isArray(options.scadFiles) && options.scadFiles.length > 0;
|
|
144
|
-
|
|
145
|
-
// 4. Construct the Main Godot Prompt System Text (System Instructions)
|
|
146
|
-
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.
|
|
147
20
|
|
|
148
21
|
NAMING CONVENTION REQUIREMENT:
|
|
149
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\`).
|
|
@@ -194,100 +67,70 @@ ${promptRules}
|
|
|
194
67
|
? "\n - The exact source code of the provided user `.scad` files, placed in the appropriate project folders."
|
|
195
68
|
: ""
|
|
196
69
|
}
|
|
197
|
-
- Ensure all string file contents inside the Node.js script are properly escaped
|
|
198
|
-
|
|
199
|
-
// 5. Gather Addon Files content
|
|
200
|
-
const addonDir = path.join(DIR, "godot", "addons", "scad_importer");
|
|
201
|
-
let addonFiles = [];
|
|
202
|
-
try {
|
|
203
|
-
if (fs.existsSync(addonDir)) {
|
|
204
|
-
const files = fs.readdirSync(addonDir);
|
|
205
|
-
for (const file of files) {
|
|
206
|
-
const fullPath = path.join(addonDir, file);
|
|
207
|
-
// Ensure we only read text files to prevent binary/hidden files
|
|
208
|
-
// from introducing control characters that crash browser UIs.
|
|
209
|
-
if (
|
|
210
|
-
fs.statSync(fullPath).isFile() &&
|
|
211
|
-
(file.endsWith(".gd") || file.endsWith(".cfg"))
|
|
212
|
-
) {
|
|
213
|
-
addonFiles.push(fullPath);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
} catch (e) {
|
|
218
|
-
console.error("Warning: Could not read addon directory.", e);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// 6. Format the unified system instructions clipboard output
|
|
222
|
-
let systemClipboardOutput = `${systemPrompt}\n\n`;
|
|
223
|
-
|
|
224
|
-
for (const file of addonFiles) {
|
|
225
|
-
try {
|
|
226
|
-
// Normalize line endings to avoid mixed line-ending layout loops in web editors
|
|
227
|
-
const content = fs.readFileSync(file, "utf-8").replace(/\r\n/g, "\n");
|
|
228
|
-
// Format to use relative paths and force forward slashes for LLM clarity
|
|
229
|
-
const relativePath = path.relative(DIR, file).replace(/\\/g, "/");
|
|
230
|
-
|
|
231
|
-
// Add explicit language tags to prevent catastrophic regex backtracking
|
|
232
|
-
// during the Markdown parser's language auto-detection step.
|
|
233
|
-
const lang = file.endsWith(".gd") ? "gdscript" : "text";
|
|
234
|
-
systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`${lang}\n${content}\n\`\`\`\n\n`;
|
|
235
|
-
} catch (e) {
|
|
236
|
-
console.error(`Warning: Skipping '${file}'. It is not a readable file.`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
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.`;
|
|
239
72
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
for (const file of options.scadFiles) {
|
|
73
|
+
// Gather Addon Files content
|
|
74
|
+
const addonDir = path.join(DIR, "godot", "addons", "scad_importer");
|
|
75
|
+
let addonFiles = [];
|
|
244
76
|
try {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
+
}
|
|
250
89
|
} catch (e) {
|
|
251
|
-
console.error(
|
|
252
|
-
`Warning: Skipping user SCAD file '${file}'. It is not a readable file.`,
|
|
253
|
-
);
|
|
90
|
+
console.error("Warning: Could not read addon directory.", e);
|
|
254
91
|
}
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
systemClipboardOutput = systemClipboardOutput.trimEnd() + "\n";
|
|
259
92
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
+
}
|
|
274
107
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
+
}
|
|
279
127
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
} catch (err) {
|
|
287
|
-
console.error("Error: Failed to copy input request to the clipboard.");
|
|
288
|
-
console.error(err.message);
|
|
289
|
-
process.exit(1);
|
|
290
|
-
}
|
|
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
|
+
});
|
|
291
134
|
}
|
|
292
135
|
|
|
293
136
|
// Execute and handle unhandled runtime errors
|