scad-gltf 0.3.3 → 0.5.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 +46 -18
- package/bin/gen.js +1 -1
- package/bin/mcp.js +198 -69
- package/editor/dist/assets/{index-B1KAb97r.css → index-D8In1wnN.css} +1 -1
- package/editor/dist/assets/{index-CgVih1C4.js → index-b4iPmEjR.js} +204 -92
- package/editor/dist/assets/{openscad-CdBCY4mx.wasm → openscad-DJJQxKJM.wasm} +0 -0
- package/editor/dist/assets/preview-Cx3FHqKG.css +1 -0
- package/editor/dist/assets/preview-DfaOpP2w.js +1 -0
- package/editor/dist/assets/{prompt-ui-B9QCNLfJ.js → prompt-ui-zw7U8lsl.js} +5 -3
- package/editor/dist/assets/shared-CDFEFgsW.js +4315 -0
- package/editor/dist/content.js +1 -1
- package/editor/dist/index.html +11 -6
- package/editor/dist/preview.html +20 -15
- package/editor/dist/sw.js +1 -1
- package/godot/addons/scad_importer/plugin.cfg +0 -1
- package/godot/addons/scad_importer/scad_importer.gd +3 -153
- package/godot/addons/scad_importer/scad_plugin.gd +0 -3
- package/package.json +4 -4
- package/src/cli-utils.js +406 -376
- package/src/ext/openscad.js +1 -1
- package/src/ext/openscad.wasm +0 -0
- package/src/project-prompts.js +111 -87
- package/src/prompt.js +21 -0
- package/editor/dist/assets/OutputPass-B6MrFnGE.js +0 -4317
- package/editor/dist/assets/preview-A6M04CFK.css +0 -1
- package/editor/dist/assets/preview-BbUpKLF4.js +0 -1
- package/godot/README.md +0 -64
- package/godot/addons/scad_importer/LICENSE +0 -21
package/src/project-prompts.js
CHANGED
|
@@ -6,18 +6,57 @@ const __filename = url.fileURLToPath(import.meta.url);
|
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
const DIR = path.resolve(__dirname, "..");
|
|
8
8
|
|
|
9
|
+
function readFile(filePath) {
|
|
10
|
+
try {
|
|
11
|
+
return fs.readFileSync(filePath, "utf-8").replace(/\r\n/g, "\n");
|
|
12
|
+
} catch (e) {
|
|
13
|
+
console.error(`Warning: Skipping file '${filePath}'. It is not readable.`);
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatMarkdownFile(displayPath, lang, content) {
|
|
19
|
+
return `### ${displayPath}\n---\n\`\`\`${lang}\n${content}\n\`\`\`\n\n`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function appendUserScadFiles(options = {}, isRawScad = false) {
|
|
23
|
+
if (
|
|
24
|
+
!options.scadFiles ||
|
|
25
|
+
!Array.isArray(options.scadFiles) ||
|
|
26
|
+
options.scadFiles.length === 0
|
|
27
|
+
) {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let output = `\n\n=== USER PROVIDED OPENSCAD FILES ===\n`;
|
|
32
|
+
if (isRawScad) {
|
|
33
|
+
output += `The following .scad files are provided as reference or base assets. You MUST embed their contents directly into your final single OpenSCAD file (or use them as context), modifying them if necessary to fit the project logic.\n\n`;
|
|
34
|
+
} else {
|
|
35
|
+
output += `The following .scad files are provided as reference or base assets. You MUST embed and write them into the generated project (or use them as context), modifying them if necessary to fit the project logic.\n\n`;
|
|
36
|
+
}
|
|
37
|
+
for (const file of options.scadFiles) {
|
|
38
|
+
const content = readFile(file);
|
|
39
|
+
if (content !== null) {
|
|
40
|
+
const relativePath = path.isAbsolute(file)
|
|
41
|
+
? path.relative(process.cwd(), file).replace(/\\/g, "/")
|
|
42
|
+
: file.replace(/\\/g, "/");
|
|
43
|
+
output += formatMarkdownFile(relativePath, "openscad", content);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return output;
|
|
47
|
+
}
|
|
48
|
+
|
|
9
49
|
export function getProjectPrompts(projectType) {
|
|
10
50
|
if (projectType === "scad") {
|
|
11
51
|
return {
|
|
12
|
-
buildSystemPrompt: () =>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
4. Provide your final OpenSCAD code in a standard markdown block (\`\`\`openscad).`,
|
|
52
|
+
buildSystemPrompt: (promptRules, options = {}) => {
|
|
53
|
+
let output = promptRules;
|
|
54
|
+
if (Array.isArray(options.scadFiles) && options.scadFiles.length > 0) {
|
|
55
|
+
output += appendUserScadFiles(options, true);
|
|
56
|
+
}
|
|
57
|
+
return output;
|
|
58
|
+
},
|
|
59
|
+
buildInputRequest: (task) => task,
|
|
21
60
|
allowedTools: ["render_scad_model"],
|
|
22
61
|
};
|
|
23
62
|
}
|
|
@@ -28,9 +67,11 @@ CRITICAL WORKFLOW:
|
|
|
28
67
|
const depName = isBevy ? "bevy" : "wgpu";
|
|
29
68
|
|
|
30
69
|
return {
|
|
31
|
-
buildSystemPrompt: (
|
|
32
|
-
|
|
33
|
-
|
|
70
|
+
buildSystemPrompt: (promptRules, options = {}) => {
|
|
71
|
+
const hasUserScadFiles =
|
|
72
|
+
Array.isArray(options.scadFiles) && options.scadFiles.length > 0;
|
|
73
|
+
|
|
74
|
+
const systemPrompt = `You are an expert Rust ${frameworkName} developer and procedural 3D technical artist.
|
|
34
75
|
|
|
35
76
|
NAMING CONVENTION REQUIREMENT:
|
|
36
77
|
- 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\`).
|
|
@@ -49,31 +90,7 @@ ${promptRules}
|
|
|
49
90
|
2. Rust ${frameworkName} Project Files:
|
|
50
91
|
- Create the necessary files for a modern Rust ${frameworkName} application (e.g., \`Cargo.toml\`, \`build.rs\`, \`src/main.rs\`).
|
|
51
92
|
- In \`Cargo.toml\`, include \`${depName}\` as a dependency.
|
|
52
|
-
- You MUST include
|
|
53
|
-
\`\`\`rust
|
|
54
|
-
use std::process::Command;
|
|
55
|
-
|
|
56
|
-
fn main() {
|
|
57
|
-
// Tell Cargo to re-run this script only if the 'scad' directory changes
|
|
58
|
-
println!("cargo::rerun-if-changed=scad");
|
|
59
|
-
|
|
60
|
-
// Ensure cross-platform compatibility for npm global binaries
|
|
61
|
-
let cmd = if cfg!(target_os = "windows") {
|
|
62
|
-
"scad-gltf.cmd"
|
|
63
|
-
} else {
|
|
64
|
-
"scad-gltf"
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
let status = Command::new(cmd)
|
|
68
|
-
.args(["convert", "./scad", "./assets/models", "--cache"])
|
|
69
|
-
.status()
|
|
70
|
-
.expect("Failed to execute scad-gltf. Is scad-gltf installed globally?");
|
|
71
|
-
|
|
72
|
-
if !status.success() {
|
|
73
|
-
panic!("scad-gltf convert failed with status: {}", status);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
\`\`\`
|
|
93
|
+
- You MUST include the provided \`build.rs\` script at the root of the project to automatically compile the \`.scad\` files into \`.glb\` format inside the \`assets/models\` folder before running the application via Cargo.
|
|
77
94
|
- Write the core application logic in \`src/main.rs\` to load and display the converted \`.glb\` files interactively.${
|
|
78
95
|
isBevy
|
|
79
96
|
? " Provide standard Bevy systems (camera, lights, movement, etc.)."
|
|
@@ -83,11 +100,34 @@ ${promptRules}
|
|
|
83
100
|
3. Delivery Format (Single Node.js Script):
|
|
84
101
|
- Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
|
|
85
102
|
- 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.
|
|
103
|
+
- CRITICAL: You MUST use \`process.cwd()\` (and NOT \`__dirname\`) when defining the target directory path (e.g. \`const rootDir = path.join(process.cwd(), "my_project");\`). This ensures the script extracts correctly into whatever temporary folder it is executed from.
|
|
86
104
|
- When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
|
|
87
105
|
- The script must embed and write:
|
|
88
106
|
- Your generated \`.scad\` 3D assets.
|
|
89
|
-
- Your generated Rust ${frameworkName} project files (\`Cargo.toml\`, \`
|
|
90
|
-
|
|
107
|
+
- Your generated Rust ${frameworkName} project files (\`Cargo.toml\`, \`src/main.rs\`).
|
|
108
|
+
- The exact source code of the provided \`build.rs\` file, placed in the project root.${
|
|
109
|
+
hasUserScadFiles
|
|
110
|
+
? "\n - The provided user `.scad` files (modified if necessary), placed in the appropriate project folders."
|
|
111
|
+
: ""
|
|
112
|
+
}
|
|
113
|
+
- Ensure all string file contents inside the Node.js script are properly escaped.`;
|
|
114
|
+
|
|
115
|
+
let systemClipboardOutput = `${systemPrompt}\n\n`;
|
|
116
|
+
|
|
117
|
+
const buildRsPath = path.join(DIR, "rust", "build.rs");
|
|
118
|
+
const buildRsContent = readFile(buildRsPath);
|
|
119
|
+
if (buildRsContent !== null) {
|
|
120
|
+
systemClipboardOutput += formatMarkdownFile(
|
|
121
|
+
"build.rs",
|
|
122
|
+
"rust",
|
|
123
|
+
buildRsContent,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
systemClipboardOutput += appendUserScadFiles(options);
|
|
128
|
+
|
|
129
|
+
return systemClipboardOutput.trimEnd() + "\n";
|
|
130
|
+
},
|
|
91
131
|
buildInputRequest: (task) =>
|
|
92
132
|
`Design and implement a Rust ${frameworkName} project for the following concept: "${task}"`,
|
|
93
133
|
allowedTools: ["render_scad_model", "compile_rust_project"],
|
|
@@ -96,9 +136,12 @@ ${promptRules}
|
|
|
96
136
|
|
|
97
137
|
if (projectType === "web") {
|
|
98
138
|
return {
|
|
99
|
-
buildSystemPrompt: (
|
|
100
|
-
|
|
101
|
-
|
|
139
|
+
buildSystemPrompt: (promptRules, options = {}) => {
|
|
140
|
+
const hasUserScadFiles =
|
|
141
|
+
Array.isArray(options.scadFiles) && options.scadFiles.length > 0;
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
`You are an expert Web 3D developer and procedural 3D technical artist.
|
|
102
145
|
|
|
103
146
|
What to generate:
|
|
104
147
|
1. 3D Web Assets (.scad):
|
|
@@ -124,11 +167,19 @@ ${promptRules}
|
|
|
124
167
|
3. Delivery Format (Single Node.js Script):
|
|
125
168
|
- Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
|
|
126
169
|
- 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.
|
|
170
|
+
- CRITICAL: You MUST use \`process.cwd()\` (and NOT \`__dirname\`) when defining the target directory path (e.g. \`const rootDir = path.join(process.cwd(), "my_project");\`). This ensures the script extracts correctly into whatever temporary folder it is executed from.
|
|
127
171
|
- When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
|
|
128
172
|
- The script must embed and write:
|
|
129
173
|
- Your generated \`.scad\` 3D assets.
|
|
130
|
-
- Your generated Vite web project files
|
|
131
|
-
|
|
174
|
+
- Your generated Vite web project files.${
|
|
175
|
+
hasUserScadFiles
|
|
176
|
+
? "\n - The provided user `.scad` files (modified if necessary), placed in the appropriate project folders."
|
|
177
|
+
: ""
|
|
178
|
+
}
|
|
179
|
+
- Ensure all string file contents inside the Node.js script are properly escaped.` +
|
|
180
|
+
appendUserScadFiles(options)
|
|
181
|
+
);
|
|
182
|
+
},
|
|
132
183
|
buildInputRequest: (task) =>
|
|
133
184
|
`Design and implement a web-based 3D glTF project using Vite for the following concept: "${task}"`,
|
|
134
185
|
allowedTools: ["render_scad_model"],
|
|
@@ -177,27 +228,26 @@ ${promptRules}
|
|
|
177
228
|
* 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\`).
|
|
178
229
|
* 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.
|
|
179
230
|
- Generate a \`project.godot\` file. It must configure the project and automatically enable the \`scad_importer\` plugin.
|
|
180
|
-
- Generate a \`.gitignore\` file that ignores the \`.godot/\` folder.
|
|
181
|
-
- Generate a \`README.md\` file that documents the project, mechanics/features, and controls.
|
|
182
231
|
|
|
183
232
|
3. Delivery Format (Single Node.js Script):
|
|
184
233
|
- Output exactly ONE self-contained Node.js script. Do not output manual setup instructions.
|
|
185
234
|
- CRITICAL: The generated Node.js script MUST first create a root project folder (named using snake_case with underscores, e.g., \`my_project\`) and output all files and folders inside this newly created project folder.
|
|
235
|
+
- CRITICAL: You MUST use \`process.cwd()\` (and NOT \`__dirname\`) when defining the target directory path (e.g. \`const rootDir = path.join(process.cwd(), "my_project");\`). This ensures the script extracts correctly into whatever temporary folder it is executed from.
|
|
186
236
|
- When executed, this script must programmatically create the entire project directory structure and write all the files to disk using the \`fs\` module.
|
|
187
237
|
- The script must embed and write:
|
|
188
238
|
- Your generated \`.scad\` assets.
|
|
189
239
|
- Your generated Godot project files.
|
|
190
240
|
- The exact source code of the provided \`addons/scad_importer/*\` files, placed in their correct respective paths.${
|
|
191
241
|
hasUserScadFiles
|
|
192
|
-
? "\n - The
|
|
242
|
+
? "\n - The provided user `.scad` files (modified if necessary), placed in the appropriate project folders."
|
|
193
243
|
: ""
|
|
194
244
|
}
|
|
195
|
-
- Ensure all string file contents inside the Node.js script are properly escaped
|
|
196
|
-
|
|
245
|
+
- Ensure all string file contents inside the Node.js script are properly escaped.`;
|
|
246
|
+
|
|
247
|
+
let systemClipboardOutput = `${systemPrompt}\n\n`;
|
|
197
248
|
|
|
198
249
|
// Gather Addon Files content
|
|
199
250
|
const addonDir = path.join(DIR, "godot", "addons", "scad_importer");
|
|
200
|
-
let addonFiles = [];
|
|
201
251
|
try {
|
|
202
252
|
if (fs.existsSync(addonDir)) {
|
|
203
253
|
const files = fs.readdirSync(addonDir);
|
|
@@ -207,7 +257,16 @@ ${promptRules}
|
|
|
207
257
|
fs.statSync(fullPath).isFile() &&
|
|
208
258
|
(file.endsWith(".gd") || file.endsWith(".cfg"))
|
|
209
259
|
) {
|
|
210
|
-
|
|
260
|
+
const content = readFile(fullPath);
|
|
261
|
+
if (content !== null) {
|
|
262
|
+
const relativePath = `addons/scad_importer/${file}`;
|
|
263
|
+
const lang = file.endsWith(".gd") ? "gdscript" : "text";
|
|
264
|
+
systemClipboardOutput += formatMarkdownFile(
|
|
265
|
+
relativePath,
|
|
266
|
+
lang,
|
|
267
|
+
content,
|
|
268
|
+
);
|
|
269
|
+
}
|
|
211
270
|
}
|
|
212
271
|
}
|
|
213
272
|
}
|
|
@@ -215,42 +274,7 @@ ${promptRules}
|
|
|
215
274
|
console.error("Warning: Could not read addon directory.", e);
|
|
216
275
|
}
|
|
217
276
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
for (const file of addonFiles) {
|
|
221
|
-
try {
|
|
222
|
-
const content = fs
|
|
223
|
-
.readFileSync(file, "utf-8")
|
|
224
|
-
.replace(/\r\n/g, "\n");
|
|
225
|
-
const relativePath = path.relative(DIR, file).replace(/\\/g, "/");
|
|
226
|
-
const lang = file.endsWith(".gd") ? "gdscript" : "text";
|
|
227
|
-
systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`${lang}\n${content}\n\`\`\`\n\n`;
|
|
228
|
-
} catch (e) {
|
|
229
|
-
console.error(
|
|
230
|
-
`Warning: Skipping '${file}'. It is not a readable file.`,
|
|
231
|
-
);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (hasUserScadFiles) {
|
|
236
|
-
systemClipboardOutput += `=== USER PROVIDED OPENSCAD FILES ===\n`;
|
|
237
|
-
systemClipboardOutput += `The following .scad files are provided as reference or base assets. You MUST embed and write them into the generated project, modifying them if necessary to fit the project logic.\n\n`;
|
|
238
|
-
for (const file of options.scadFiles) {
|
|
239
|
-
try {
|
|
240
|
-
const content = fs
|
|
241
|
-
.readFileSync(file, "utf-8")
|
|
242
|
-
.replace(/\r\n/g, "\n");
|
|
243
|
-
const relativePath = path.isAbsolute(file)
|
|
244
|
-
? path.relative(process.cwd(), file).replace(/\\/g, "/")
|
|
245
|
-
: file.replace(/\\/g, "/");
|
|
246
|
-
systemClipboardOutput += `### ${relativePath}\n---\n\`\`\`openscad\n${content}\n\`\`\`\n\n`;
|
|
247
|
-
} catch (e) {
|
|
248
|
-
console.error(
|
|
249
|
-
`Warning: Skipping user SCAD file '${file}'. It is not a readable file.`,
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
277
|
+
systemClipboardOutput += appendUserScadFiles(options);
|
|
254
278
|
|
|
255
279
|
return systemClipboardOutput.trimEnd() + "\n";
|
|
256
280
|
},
|
package/src/prompt.js
CHANGED
|
@@ -27,6 +27,7 @@ export function generatePrompt(description, options = {}) {
|
|
|
27
27
|
animation: options.animation ?? true,
|
|
28
28
|
lazyUnion: options.lazyUnion ?? false,
|
|
29
29
|
modelName: options.modelName ?? true,
|
|
30
|
+
lights: options.lights ?? false,
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
let prompt = `Generate an OpenSCAD script to design the following: ${description}.`;
|
|
@@ -117,6 +118,7 @@ export function generatePrompt(description, options = {}) {
|
|
|
117
118
|
prompt += `\n\nImportant Animation rules:
|
|
118
119
|
- Wrapping: Use the 'armature(animations=...)' module at the root to wrap all animated components.
|
|
119
120
|
- Hierarchies: Use the 'bone(name="BoneName", t=[x,y,z], r=[x,y,z])' module to define hierarchical animated parts.
|
|
121
|
+
- Camera Animation: You can create an animated glTF camera by naming a bone "Camera" or "camera". You can append numbers or separators to the name (e.g., "Camera1", "Camera_fov60" for Perspective 60 deg FOV, or "Camera_ortho_mag10" for Orthographic, scale 10). Physical parts like "CameraMount" are safely ignored. It will animate just like any other bone! Note: The camera looks forward along the +Y axis, with +Z as its UP direction.
|
|
120
122
|
- Auto-Unioning: Any child meshes (e.g., cube, cylinder, imported objects) placed directly inside an 'armature()' or 'bone()' node are automatically unioned together by the engine and rigidly attached to that bone's transform node. Child bones remain separate nodes in the hierarchy.
|
|
121
123
|
- Animation Data: The 'animations' property is an array of named animation sequences. Each sequence contains an array of tracks defining keyframes for each bone. Format:
|
|
122
124
|
animations = [
|
|
@@ -229,5 +231,24 @@ bake(uvs=true) {
|
|
|
229
231
|
\`\`\``;
|
|
230
232
|
}
|
|
231
233
|
|
|
234
|
+
if (opts.lights) {
|
|
235
|
+
prompt += `\n\nImportant Lighting rules:
|
|
236
|
+
- Lights: Use the 'light()' module to add lights to the scene.
|
|
237
|
+
- Syntax: \`light(type="point", color=[1,1,1], intensity=2000.0, range=0.0, innerConeAngle=0.0, outerConeAngle=45.0) { ... }\`
|
|
238
|
+
- Supported light types: "directional", "point", "spot".
|
|
239
|
+
- Intensity (Physically Correct): Directional lights use Lux (e.g., 300-500 for indoor lighting, 100000 for the sun). Point and Spot lights use Candelas and decay over distance via the inverse-square law. They require VERY HIGH values (e.g., 1000.0 to 10000.0+) to illuminate objects!
|
|
240
|
+
- Orientation: A directional or spot light shines in the -Z direction by default. Use \`translate()\` and \`rotate()\` around the \`light()\` module to position and orient it.
|
|
241
|
+
- Spot lights accept \`innerConeAngle\` and \`outerConeAngle\` (in degrees). Point/Spot lights accept \`range\` (0.0 means infinite).
|
|
242
|
+
- Children inside a \`light()\` block are rendered normally in OpenSCAD and exported. The light transform automatically follows its enclosing transformations.
|
|
243
|
+
|
|
244
|
+
Example Lighting Usage:
|
|
245
|
+
\`\`\`openscad
|
|
246
|
+
// A spot light pointing down at an angle (Note the high intensity of 5000)
|
|
247
|
+
translate([0, 0, 50]) rotate([0, 30, 0])
|
|
248
|
+
light(type="spot", color=[1, 0.8, 0.8], intensity=5000.0, innerConeAngle=15.0, outerConeAngle=30.0)
|
|
249
|
+
color("gray") cylinder(h=2, r=1, center=true); // A visual representation of the light fixture
|
|
250
|
+
\`\`\``;
|
|
251
|
+
}
|
|
252
|
+
|
|
232
253
|
return prompt;
|
|
233
254
|
}
|