scad-gltf 0.1.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.
Files changed (37) hide show
  1. package/README.md +489 -0
  2. package/bin/scad-convert.js +221 -0
  3. package/bin/scad-godot.js +247 -0
  4. package/bin/scad-mcp.js +508 -0
  5. package/bin/scad-serve.js +225 -0
  6. package/bin/scad-web.js +206 -0
  7. package/editor/dist/aristea_wreck_puresky_2k.hdr +0 -0
  8. package/editor/dist/assets/OutputPass-Bvl6NigM.js +4317 -0
  9. package/editor/dist/assets/index-V9cEH4KX.js +4318 -0
  10. package/editor/dist/assets/index-t9MYrExo.css +1 -0
  11. package/editor/dist/assets/openscad-CdBCY4mx.wasm +0 -0
  12. package/editor/dist/assets/preview-C1zc24MJ.js +1 -0
  13. package/editor/dist/assets/preview-fQfL_FJ-.css +1 -0
  14. package/editor/dist/assets/prompt-ui-8EFRz0ju.js +126 -0
  15. package/editor/dist/content-loader.js +4 -0
  16. package/editor/dist/content.css +255 -0
  17. package/editor/dist/content.js +24 -0
  18. package/editor/dist/icon.png +0 -0
  19. package/editor/dist/index.html +187 -0
  20. package/editor/dist/manifest.json +23 -0
  21. package/editor/dist/manifest.webmanifest +1 -0
  22. package/editor/dist/preview.html +27 -0
  23. package/editor/dist/registerSW.js +1 -0
  24. package/editor/dist/sw.js +1 -0
  25. package/editor/dist/workbox-9c191d2f.js +1 -0
  26. package/godot/README.md +64 -0
  27. package/godot/addons/scad_importer/plugin.cfg +7 -0
  28. package/godot/addons/scad_importer/scad_importer.gd +197 -0
  29. package/godot/addons/scad_importer/scad_plugin.gd +12 -0
  30. package/godot/examples/README.md +82 -0
  31. package/godot/examples/fruit_fusion_3d.js +1574 -0
  32. package/godot/examples/package.json +5 -0
  33. package/package.json +63 -0
  34. package/src/convert.js +94 -0
  35. package/src/ext/openscad.js +14 -0
  36. package/src/ext/openscad.wasm +0 -0
  37. package/src/prompt.js +229 -0
package/src/prompt.js ADDED
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Generates an LLM prompt containing the required syntax rules for PBR, Animations,
3
+ * and Texture Baking in this custom OpenSCAD fork.
4
+ *
5
+ * @param {string} description - The description of the object you want the AI to design.
6
+ * @param {Object} options - Toggles for different prompt blocks
7
+ * @returns {string} The fully formatted LLM prompt.
8
+ */
9
+ export function generatePrompt(description, options = {}) {
10
+ if (!description) {
11
+ throw new Error("A description is required to generate a prompt.");
12
+ }
13
+
14
+ const opts = {
15
+ basic: options.basic ?? true,
16
+ transmission: options.transmission ?? true,
17
+ clearcoat: options.clearcoat ?? true,
18
+ sheen: options.sheen ?? true,
19
+ emissive: options.emissive ?? true,
20
+ specular: options.specular ?? true,
21
+ iridescence: options.iridescence ?? true,
22
+ bakeColors: options.bakeColors ?? false,
23
+ bakeNormals: options.bakeNormals ?? false,
24
+ bakeOrm: options.bakeOrm ?? false,
25
+ bakeUvs: options.bakeUvs ?? false,
26
+ autoSmoothAngle: options.autoSmoothAngle ?? true,
27
+ animation: options.animation ?? true,
28
+ lazyUnion: options.lazyUnion ?? false,
29
+ modelName: options.modelName ?? true,
30
+ };
31
+
32
+ let prompt = `Generate an OpenSCAD script to design the following: ${description}.`;
33
+
34
+ if (opts.modelName) {
35
+ prompt += `\n\nImportant Output Rules:
36
+ - You MUST wrap your code in a standard Markdown code block using the \`\`\`openscad language tag.
37
+ - Inside the code block, on the first line, include a block comment with a concise filename in snake_case (lowercase and underscores).
38
+ - Use this exact format: /* Model Name: your_model_name_here */`;
39
+ }
40
+
41
+ let attrs = [];
42
+ if (opts.basic) attrs.push("'roughness'", "'metalness'");
43
+ if (opts.clearcoat) attrs.push("'clearcoat'", "'clearcoatRoughness'");
44
+ if (opts.sheen) attrs.push("'sheen'", "'sheenColor'", "'sheenRoughness'");
45
+ if (opts.transmission)
46
+ attrs.push(
47
+ "'transmission'",
48
+ "'thickness'",
49
+ "'attenuationColor'",
50
+ "'attenuationDistance'",
51
+ "'ior'",
52
+ );
53
+ if (opts.emissive) attrs.push("'emissive'", "'emissiveIntensity'");
54
+ if (opts.specular) attrs.push("'specularColor'", "'specularIntensity'");
55
+ if (opts.iridescence) attrs.push("'iridescence'", "'iridescenceIOR'");
56
+
57
+ if (attrs.length > 0 || opts.autoSmoothAngle) {
58
+ if (attrs.length > 0) {
59
+ prompt += `\n\nPlease utilize extended color attributes, specifically including ${attrs.join(", ")} parameters.`;
60
+ }
61
+ prompt += `\n\nImportant PBR & Shading rules:`;
62
+
63
+ if (opts.basic) {
64
+ prompt += `\n- Metalness: For solid metallic materials (e.g., gold, steel), use metalness near 1.0. High metalness blocks light transmission. (Default: 0.0)`;
65
+ prompt += `\n- Roughness: Controls surface finish. 0.0 is perfectly smooth/glossy, while 1.0 is completely matte. (Default: 1.0)`;
66
+ }
67
+ if (opts.transmission) {
68
+ prompt += `\n- Transmission: Degree of optical transparency (0.0 to 1.0) for materials like glass or water. Note: When transmission is non-zero, alpha (opacity) should be set to 1.0. (Default: 0.0)`;
69
+ prompt += `\n- Thickness: The thickness of the volume beneath the surface. If 0.0, the material is thin-walled (like a bubble). If > 0, it acts as a solid volume boundary (like a block of glass). (Default: 0.0)`;
70
+ prompt += `\n- Attenuation Color & Distance: Used with transmission and thickness to simulate volume absorption (colored glass or liquids). Distance is how far light travels to reach the attenuationColor. (Defaults: [1.0, 1.0, 1.0] and 0.0)`;
71
+ prompt += `\n- IOR (Index of Refraction): Controls how much light bends when entering a transmissive or clearcoat material. Water is ~1.33, Window Glass ~1.5, Diamond ~2.4. (Default: 1.5)`;
72
+ }
73
+ if (opts.clearcoat) {
74
+ prompt += `\n- Clearcoat: Adds a clear, reflective layer on top of the base material (car paint, varnished wood, or wet surfaces). 1.0 is fully coated. (Default: 0.0)`;
75
+ prompt += `\n- Clearcoat Roughness: Controls the smoothness of the clearcoat layer. (Default: 0.0)`;
76
+ }
77
+ if (opts.sheen) {
78
+ prompt += `\n- Sheen: Simulates backscattering from microfibers, creating a soft velvet-like rim light useful for cloth and fabrics. 1.0 is full intensity. (Default: 0.0)`;
79
+ prompt += `\n- Sheen Color: Sets the RGB tint of the sheen layer (e.g., sheenColor = [1.0, 0.5, 0.5]). (Default: [0.0, 0.0, 0.0])`;
80
+ prompt += `\n- Sheen Roughness: Controls the roughness of the sheen layer. (Default: 0.0)`;
81
+ }
82
+ if (opts.emissive) {
83
+ prompt += `\n- Emissive & Emissive Intensity: Makes the material glow. Emissive is an RGB color vector, intensity is a float multiplier. (Defaults: [0.0, 0.0, 0.0] and 1.0)`;
84
+ }
85
+ if (opts.specular) {
86
+ prompt += `\n- Specular Color & Intensity: Overrides the default specular reflection. (Defaults: [1.0, 1.0, 1.0] and 1.0)`;
87
+ }
88
+ if (opts.iridescence) {
89
+ prompt += `\n- Iridescence & Iridescence IOR: Simulates thin-film interference like soap bubbles, oil spills, or pearlescent surfaces. (Defaults: 0.0 and 1.3)`;
90
+ }
91
+ if (opts.autoSmoothAngle) {
92
+ prompt += `\n- Auto Smooth Angle: Generates smooth vertex normals for adjoining faces with an angle difference less than this value (in degrees). Use > 0 (e.g., 30 or 45) for curved/smooth surfaces, 0.0 for flat shading. Can be set globally using the special variable $asa (e.g., $asa=30;), or overridden per-material via the $asa parameter INSIDE the color() module. IMPORTANT: $asa ONLY affects surface shading (normals). It DOES NOT alter the actual geometry or polygon count. You must still use standard variables like $fn to increase geometric resolution. DO NOT pass $asa directly to geometry modules like sphere() or cylinder(). (Default: 0.0)`;
93
+ }
94
+
95
+ let exampleParams = [];
96
+ if (opts.basic) exampleParams.push("metalness=1.0", "roughness=0.3");
97
+ if (opts.transmission) exampleParams.push("transmission=0.8", "ior=1.5");
98
+ if (opts.clearcoat) exampleParams.push("clearcoat=1.0");
99
+ if (opts.sheen) exampleParams.push("sheen=1.0");
100
+ if (opts.iridescence) exampleParams.push("iridescence=1.0");
101
+ if (opts.emissive)
102
+ exampleParams.push("emissive=[0.0, 0.5, 1.0]", "emissiveIntensity=2.0");
103
+ if (opts.specular) exampleParams.push("specularIntensity=1.0");
104
+ if (opts.autoSmoothAngle) exampleParams.push("$asa=45.0");
105
+
106
+ let exampleStr =
107
+ exampleParams.length > 0 ? ", " + exampleParams.join(", ") : "";
108
+
109
+ prompt += `\n\nExample Material Usage:\n// Syntax: color(c=color_value, alpha=1.0, [named PBR parameters...])\ncolor([0.2, 0.2, 0.2], alpha=1.0${exampleStr})\n cube([10, 10, 10]);`;
110
+ }
111
+
112
+ if (opts.lazyUnion) {
113
+ prompt += `\n\nImportant Geometry rules:\n- The compiler runs with "lazy-union" enabled. This means top-level objects, module children, and items inside loops ('for') or conditionals ('if') are NOT implicitly boolean-unioned together. They are evaluated and exported as separate discrete meshes.`;
114
+ }
115
+
116
+ if (opts.animation) {
117
+ prompt += `\n\nImportant Animation rules:
118
+ - Wrapping: Use the 'armature(animations=...)' module at the root to wrap all animated components.
119
+ - Hierarchies: Use the 'bone(name="BoneName", t=[x,y,z], r=[x,y,z])' module to define hierarchical animated parts.
120
+ - 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. Child bones remain separate nodes in the hierarchy.
121
+ - 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
+ animations = [
123
+ ["AnimationName", [
124
+ ["BoneName", [
125
+ [time_in_seconds, [rot_x, rot_y, rot_z], [trans_x, trans_y, trans_z]], // Translation is optional
126
+ [1.0, [0, 90, 0], [0, 5, 0]],
127
+ ...
128
+ ]]
129
+ ]]
130
+ ];
131
+ - Rotation Keyframes: Due to glTF Quaternion shortest-path interpolation, NEVER rotate more than 90 degrees between consecutive keyframes. To perform a full 360-degree rotation, you MUST manually subdivide it into 90-degree increments (e.g., 0, 90, 180, 270, 360).
132
+ - Translational & Rotational Keyframes: Keyframe translations and rotations are ABSOLUTE in local space. They completely replace the bone's resting 't' and 'r' attributes during the animation. If a bone's resting translation is [0, 0, 2] and it needs to move 10 units up, the keyframe translation must be [0, 0, 12]. If translation is omitted, it defaults to the resting position.
133
+
134
+ Example Animation Usage:
135
+ anim_data = [
136
+ ["Action 1", [
137
+ ["BaseSpinner", [
138
+ [0.0, [0, 0, 0]],
139
+ [1.0, [0, 0, 90]],
140
+ [2.0, [0, 0, 180]],
141
+ [3.0, [0, 0, 270]],
142
+ [4.0, [0, 0, 360]]
143
+ ]],
144
+ ["ChildSlider", [
145
+ [0.0, [0, 0, 0], [0, 0, 2]],
146
+ [2.0, [0, 0, 0], [0, 0, 12]],
147
+ [4.0, [0, 0, 0], [0, 0, 2]]
148
+ ]]
149
+ ]]
150
+ ];
151
+
152
+ armature(animations=anim_data) {
153
+ // Root bone
154
+ bone(name="BaseSpinner", t=[0, 0, 0], r=[0, 0, 0]) {
155
+ // Mesh attached to BaseSpinner
156
+ color([0.2, 0.5, 0.8]) cube([10, 10, 2], center=true);
157
+
158
+ // Nested child bone (inherits parent's transform)
159
+ bone(name="ChildSlider", t=[0, 0, 2], r=[0, 0, 0]) {
160
+ // Mesh attached to ChildSlider
161
+ color([0.8, 0.2, 0.2]) cylinder(h=5, r=2);
162
+ }
163
+ }
164
+ }`;
165
+ }
166
+
167
+ if (opts.bakeColors || opts.bakeNormals || opts.bakeOrm || opts.bakeUvs) {
168
+ const flags = [];
169
+ const explanations = [];
170
+ if (opts.bakeColors) {
171
+ flags.push("colors=true");
172
+ explanations.push(
173
+ "- Set 'colors=true' (default false) to project and bake the high-poly's solid colors onto the low-poly mesh.",
174
+ );
175
+ }
176
+ if (opts.bakeNormals) {
177
+ flags.push("normals=true");
178
+ explanations.push(
179
+ "- Set 'normals=true' (default false) to project and bake the high-poly's physical geometric details as a tangent-space normal map onto the low-poly mesh.",
180
+ );
181
+ }
182
+ if (opts.bakeOrm) {
183
+ flags.push("orm=true");
184
+ explanations.push(
185
+ "- Set 'orm=true' (default false) to project and bake the high-poly's Roughness, and Metallic values onto the low-poly mesh.",
186
+ );
187
+ }
188
+ if (opts.bakeUvs) {
189
+ flags.push("uvs=true");
190
+ explanations.push(
191
+ "- Set 'uvs=true' (default false) when you only want to generate UV coordinates and Tangent vectors without baking any image textures. Note that UVs and Tangents are automatically generated whenever 'colors', 'normals', or 'orm' are enabled, so 'uvs=true' is only explicitly needed for textureless UV-only exports.",
192
+ );
193
+ }
194
+ explanations.push(
195
+ "- You can customize the baking process using 'distance' (max ray length, default: 2.0), 'bias' (ray origin offset, default: 1e-4), 'dilation' (pixel padding around UV islands, default: 2), 'resolution' (texture dimensions, default: 512), 'msaa' (super-sampling anti-aliasing level, default: 2), 'index' (atlas group identifier, default: 0), and 'rotate_uvs' (allow UV islands to be rotated for better packing, default: true).",
196
+ );
197
+ explanations.push(
198
+ "- The 'index' parameter enables multi-atlas texture baking. Low-poly meshes configured with the same 'index' will be packed together into a shared texture atlas, while meshes with distinct indices will be split into separate output image maps.",
199
+ );
200
+
201
+ flags.push("resolution=1024");
202
+ const bakeSig = flags.length > 0 ? `bake(${flags.join(", ")})` : "bake()";
203
+
204
+ const explanationText =
205
+ explanations.length > 0
206
+ ? explanations.join("\n")
207
+ : "- You can toggle what gets baked using the 'colors', 'normals', 'orm', and 'uvs' boolean parameters (all default to false).";
208
+
209
+ prompt += `\n\nImportant Texture Baking rules:
210
+ - Baking: Use the 'bake()' module to project details from a high-resolution mesh onto a low-resolution mesh.
211
+ - UV Unwrapping: The engine automatically generates UV coordinates and bakes the textures for the low-poly child mesh; you do not need to manually map textures.
212
+ - Usage: The 'bake()' module strictly requires exactly TWO children. The FIRST child is the high-poly geometry, and the SECOND child is the low-poly geometry.
213
+ ${explanationText}
214
+
215
+ Example Baking Usage:
216
+ // Bake the selected details of a high-resolution sphere onto a low-resolution one
217
+ ${bakeSig} {
218
+ color("white") sphere(r=10, $fn=100); // Child 1: High Poly
219
+ color("white", roughness=0.5, $asa=45) sphere(r=10, $fn=20); // Child 2: Low Poly
220
+ }
221
+
222
+ // Alternatively, generate UVs/Tangents for a mesh WITHOUT a high-poly source by providing only 1 child
223
+ bake(uvs=true) {
224
+ color("white") cube([10, 10, 10]);
225
+ }`;
226
+ }
227
+
228
+ return prompt;
229
+ }