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/editor/dist/content.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{i as e,n as t,r as n,t as r}from"./assets/prompt-ui-
|
|
1
|
+
import{i as e,n as t,r as n,t as r}from"./assets/prompt-ui-zw7U8lsl.js";console.log(`🚀 SCAD Preview Extension loaded!`),window.addEventListener(`message`,e=>{if(e.data){if(e.data.type===`CLOSE_PREVIEW`){let e=document.getElementById(`scad-preview-iframe`);e&&e.remove()}else e.data.type===`ADD_IMAGE_TO_CHAT`&&i(e.data.dataUrl)}});async function i(e){try{let t=await(await fetch(e)).blob(),n=new File([t],`preview.png`,{type:`image/png`}),r=document.querySelector(`ms-prompt-box textarea[formcontrolname="promptText"]`)||document.querySelector(`ms-prompt-box textarea`)||document.querySelector(`textarea`);if(r){r.focus();let e=new DataTransfer;e.items.add(n);let t=new ClipboardEvent(`paste`,{clipboardData:e,bubbles:!0,cancelable:!0});r.dispatchEvent(t)}else console.warn(`Could not find chat input to paste the image.`)}catch(e){console.error(`Error adding image to chat:`,e)}}new MutationObserver(()=>{a(),o(),document.querySelectorAll(`ms-code-block[data-test-language="openscad" i], ms-code-block[data-test-language="scad" i]`).forEach(e=>{if(e.querySelector(`.scad-preview-btn`))return;let t=document.createElement(`button`);t.className=`scad-preview-btn`,t.title=`Preview 3D`,t.setAttribute(`aria-label`,`Preview 3D`),t.innerHTML=`
|
|
2
2
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
3
3
|
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"></path>
|
|
4
4
|
<polyline points="3.27 6.96 12 12.01 20.73 6.96"></polyline>
|
package/editor/dist/index.html
CHANGED
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
content="https://iliagrigorevdev.github.io/scad-gltf/icon.png"
|
|
45
45
|
/>
|
|
46
46
|
|
|
47
|
-
<script type="module" crossorigin src="./assets/index-
|
|
48
|
-
<link rel="modulepreload" crossorigin href="./assets/
|
|
49
|
-
<link rel="modulepreload" crossorigin href="./assets/prompt-ui-
|
|
50
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
47
|
+
<script type="module" crossorigin src="./assets/index-b4iPmEjR.js"></script>
|
|
48
|
+
<link rel="modulepreload" crossorigin href="./assets/shared-CDFEFgsW.js">
|
|
49
|
+
<link rel="modulepreload" crossorigin href="./assets/prompt-ui-zw7U8lsl.js">
|
|
50
|
+
<link rel="stylesheet" crossorigin href="./assets/index-D8In1wnN.css">
|
|
51
51
|
<link rel="manifest" href="./manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="./registerSW.js"></script></head>
|
|
52
52
|
<body>
|
|
53
53
|
<div id="app-container">
|
|
@@ -103,14 +103,15 @@
|
|
|
103
103
|
id="anim-controls-section"
|
|
104
104
|
style="display: none"
|
|
105
105
|
>
|
|
106
|
-
<h2>3. Animation
|
|
106
|
+
<h2>3. Animation & Camera</h2>
|
|
107
107
|
<div class="controls-row">
|
|
108
108
|
<button id="anim-play-btn" class="primary" style="width: 90px">
|
|
109
109
|
⏸ Pause
|
|
110
110
|
</button>
|
|
111
111
|
<select id="anim-select"></select>
|
|
112
|
+
<select id="camera-select" style="display: none"></select>
|
|
112
113
|
</div>
|
|
113
|
-
<div class="controls-row">
|
|
114
|
+
<div class="controls-row" id="anim-slider-row">
|
|
114
115
|
<input
|
|
115
116
|
type="range"
|
|
116
117
|
id="anim-slider"
|
|
@@ -174,6 +175,10 @@
|
|
|
174
175
|
><input type="checkbox" id="show-grid-cb" checked /> Grid</label
|
|
175
176
|
>
|
|
176
177
|
<label><input type="checkbox" id="wireframe-cb" /> Wireframe</label>
|
|
178
|
+
<label><input type="checkbox" id="bloom-cb" /> Bloom</label>
|
|
179
|
+
<label id="lights-cb-label" style="display: none"
|
|
180
|
+
><input type="checkbox" id="lights-cb" checked /> Lights</label
|
|
181
|
+
>
|
|
177
182
|
<button id="screenshot-btn" title="Capture Image">📷</button>
|
|
178
183
|
<button id="fullscreen-btn" title="Toggle Full Screen">⛶</button>
|
|
179
184
|
</div>
|
package/editor/dist/preview.html
CHANGED
|
@@ -2,28 +2,33 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
|
-
<script type="module" crossorigin src="./assets/preview-
|
|
6
|
-
<link rel="modulepreload" crossorigin href="./assets/
|
|
7
|
-
<link rel="stylesheet" crossorigin href="./assets/preview-
|
|
5
|
+
<script type="module" crossorigin src="./assets/preview-DfaOpP2w.js"></script>
|
|
6
|
+
<link rel="modulepreload" crossorigin href="./assets/shared-CDFEFgsW.js">
|
|
7
|
+
<link rel="stylesheet" crossorigin href="./assets/preview-Cx3FHqKG.css">
|
|
8
8
|
<link rel="manifest" href="./manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="./registerSW.js"></script></head>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="viewer-container">
|
|
11
|
-
<div id="
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
</
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
11
|
+
<div id="top-ui">
|
|
12
|
+
<div id="save-ui">
|
|
13
|
+
<input type="text" id="filename-input" placeholder="Name" />
|
|
14
|
+
<button id="save-btn">Save</button>
|
|
15
|
+
<button id="open-editor-btn">Edit</button>
|
|
16
|
+
</div>
|
|
17
|
+
<div id="viewer-controls">
|
|
18
|
+
<label
|
|
19
|
+
><input type="checkbox" id="show-grid-cb" checked /> Grid</label
|
|
20
|
+
>
|
|
21
|
+
<label><input type="checkbox" id="wireframe-cb" /> Wireframe</label>
|
|
22
|
+
<button id="screenshot-btn" title="Take Screenshot & Add to Chat">
|
|
23
|
+
📷
|
|
24
|
+
</button>
|
|
25
|
+
<button id="fullscreen-btn" title="Toggle Full Screen">⛶</button>
|
|
26
|
+
</div>
|
|
23
27
|
</div>
|
|
24
28
|
<div id="anim-controls" style="display: none">
|
|
25
29
|
<button id="anim-play-btn">⏸ Pause</button>
|
|
26
30
|
<select id="anim-select"></select>
|
|
31
|
+
<select id="camera-select" style="display: none"></select>
|
|
27
32
|
<input
|
|
28
33
|
type="range"
|
|
29
34
|
id="anim-slider"
|
package/editor/dist/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
if(!self.define){let e,s={};const i=(i,r)=>(i=new URL(i+".js",r).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(r,n)=>{const
|
|
1
|
+
if(!self.define){let e,s={};const i=(i,r)=>(i=new URL(i+".js",r).href,s[i]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()}).then(()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e}));self.define=(r,n)=>{const l=e||("document"in self?document.currentScript.src:"")||location.href;if(s[l])return;let o={};const t=e=>i(e,l),u={module:{uri:l},exports:o,require:t};s[l]=Promise.all(r.map(e=>u[e]||t(e))).then(e=>(n(...e),o))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"402b66900e731ca748771b6fc5e7a068"},{url:"preview.html",revision:"16df0b9b2256a3d9f64f70190660f416"},{url:"index.html",revision:"62b35f003a048e944e6221f0778116bb"},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"content.js",revision:"97c4b6aa1c7f4dd481a03167dee94f25"},{url:"content.css",revision:"4ea93a2d03164b49c80a48138305e564"},{url:"content-loader.js",revision:"d94f1755552d49c1fe3a6da123e8a346"},{url:"aristea_wreck_puresky_2k.hdr",revision:"e764c66f871ab0987f3fac422edc841d"},{url:"assets/shared-CDFEFgsW.js",revision:null},{url:"assets/prompt-ui-zw7U8lsl.js",revision:null},{url:"assets/preview-DfaOpP2w.js",revision:null},{url:"assets/preview-Cx3FHqKG.css",revision:null},{url:"assets/openscad-DJJQxKJM.wasm",revision:null},{url:"assets/index-b4iPmEjR.js",revision:null},{url:"assets/index-D8In1wnN.css",revision:null},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"manifest.webmanifest",revision:"705049c691faa532cc20c736d56d54df"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
# Copyright (c) 2026 Ilia Grigorev. Distributed under the MIT License.
|
|
2
|
-
# See LICENSE in the addon directory for details.
|
|
3
|
-
|
|
4
1
|
@tool
|
|
5
2
|
extends EditorSceneFormatImporter
|
|
6
3
|
|
|
@@ -32,13 +29,9 @@ func _import_scene(path: String, flags: int, options: Dictionary) -> Object:
|
|
|
32
29
|
exit_code = OS.execute("scad-gltf", args, output, true)
|
|
33
30
|
|
|
34
31
|
if exit_code != 0:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if not fallback_success:
|
|
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))
|
|
41
|
-
return null
|
|
32
|
+
push_error(Failed to compile SCAD file: %s. Ensure scad-gltf is installed." % path.get_file())
|
|
33
|
+
push_error("scad-gltf convert output: ", "\n".join(output))
|
|
34
|
+
return null
|
|
42
35
|
|
|
43
36
|
var gltf_doc = GLTFDocument.new()
|
|
44
37
|
var gltf_state = GLTFState.new()
|
|
@@ -55,147 +48,4 @@ func _import_scene(path: String, flags: int, options: Dictionary) -> Object:
|
|
|
55
48
|
if generated_scene:
|
|
56
49
|
generated_scene.name = path.get_file().get_basename()
|
|
57
50
|
|
|
58
|
-
# Godot's scene import pipeline takes ownership over the generated node!
|
|
59
|
-
# It will automatically extract ImporterMeshInstance3D nodes and hook it into Advanced Scene Import.
|
|
60
51
|
return generated_scene
|
|
61
|
-
|
|
62
|
-
func _get_relative_path(base: String, target: String) -> String:
|
|
63
|
-
var base_parts = base.replace("\\", "/").split("/", false)
|
|
64
|
-
var target_parts = target.replace("\\", "/").split("/", false)
|
|
65
|
-
|
|
66
|
-
# If on Windows and drive letters are different, relative paths cannot be resolved
|
|
67
|
-
if OS.get_name() == "Windows":
|
|
68
|
-
if base_parts.size() > 0 and target_parts.size() > 0:
|
|
69
|
-
if base_parts[0].nocasecmp_to(target_parts[0]) != 0:
|
|
70
|
-
return target
|
|
71
|
-
|
|
72
|
-
var common_count = 0
|
|
73
|
-
var min_len = min(base_parts.size(), target_parts.size())
|
|
74
|
-
for i in range(min_len):
|
|
75
|
-
# Case-insensitive comparison for cross-platform compatibility
|
|
76
|
-
if base_parts[i].nocasecmp_to(target_parts[i]) == 0:
|
|
77
|
-
common_count += 1
|
|
78
|
-
else:
|
|
79
|
-
break
|
|
80
|
-
|
|
81
|
-
var rel_parts = PackedStringArray()
|
|
82
|
-
for i in range(common_count, base_parts.size()):
|
|
83
|
-
rel_parts.append("..")
|
|
84
|
-
|
|
85
|
-
for i in range(common_count, target_parts.size()):
|
|
86
|
-
rel_parts.append(target_parts[i])
|
|
87
|
-
|
|
88
|
-
return "/".join(rel_parts)
|
|
89
|
-
|
|
90
|
-
func _get_dependencies_recursive(file_path: String, visited: Dictionary) -> void:
|
|
91
|
-
if visited.has(file_path):
|
|
92
|
-
return
|
|
93
|
-
|
|
94
|
-
visited[file_path] = ""
|
|
95
|
-
|
|
96
|
-
if not FileAccess.file_exists(file_path):
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
var file = FileAccess.open(file_path, FileAccess.READ)
|
|
100
|
-
if not file:
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
var content = file.get_as_text()
|
|
104
|
-
file.close()
|
|
105
|
-
|
|
106
|
-
visited[file_path] = content
|
|
107
|
-
|
|
108
|
-
var regex = RegEx.new()
|
|
109
|
-
regex.compile("(?:include|use)\\s*[<\"]([^>\"]+)[>\"]")
|
|
110
|
-
|
|
111
|
-
var base_dir = file_path.get_base_dir()
|
|
112
|
-
for result in regex.search_all(content):
|
|
113
|
-
var dep_rel_path = result.get_string(1)
|
|
114
|
-
var dep_abs_path = base_dir.path_join(dep_rel_path).simplify_path()
|
|
115
|
-
_get_dependencies_recursive(dep_abs_path, visited)
|
|
116
|
-
|
|
117
|
-
func _get_dependencies(file_path: String) -> Dictionary:
|
|
118
|
-
var visited = {}
|
|
119
|
-
_get_dependencies_recursive(file_path, visited)
|
|
120
|
-
return visited
|
|
121
|
-
|
|
122
|
-
func _try_scad_serve_fallback(source_path: String, out_glb_path: String) -> bool:
|
|
123
|
-
var deps = _get_dependencies(source_path)
|
|
124
|
-
var content = deps.get(source_path, "")
|
|
125
|
-
deps.erase(source_path)
|
|
126
|
-
|
|
127
|
-
if content == "":
|
|
128
|
-
return false
|
|
129
|
-
|
|
130
|
-
var additional_files = {}
|
|
131
|
-
var base_dir = source_path.get_base_dir()
|
|
132
|
-
for dep_path in deps.keys():
|
|
133
|
-
var rel_path = _get_relative_path(base_dir, dep_path)
|
|
134
|
-
additional_files[rel_path] = deps[dep_path]
|
|
135
|
-
|
|
136
|
-
var http = HTTPClient.new()
|
|
137
|
-
var err = http.connect_to_host("127.0.0.1", 3000)
|
|
138
|
-
if err != OK:
|
|
139
|
-
return false
|
|
140
|
-
|
|
141
|
-
# Wait for connection (up to 5 seconds)
|
|
142
|
-
var max_wait = 500
|
|
143
|
-
var wait = 0
|
|
144
|
-
while http.get_status() in [HTTPClient.STATUS_CONNECTING, HTTPClient.STATUS_RESOLVING]:
|
|
145
|
-
http.poll()
|
|
146
|
-
OS.delay_msec(10)
|
|
147
|
-
wait += 1
|
|
148
|
-
if wait > max_wait:
|
|
149
|
-
return false
|
|
150
|
-
|
|
151
|
-
if http.get_status() != HTTPClient.STATUS_CONNECTED:
|
|
152
|
-
return false
|
|
153
|
-
|
|
154
|
-
var headers = PackedStringArray(["Content-Type: application/json"])
|
|
155
|
-
|
|
156
|
-
# Pack the content and dependencies inside options object
|
|
157
|
-
var payload = {
|
|
158
|
-
"content": content,
|
|
159
|
-
"options": {
|
|
160
|
-
"additionalFiles": additional_files
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
var body = JSON.stringify(payload)
|
|
165
|
-
err = http.request(HTTPClient.METHOD_POST, "/api/convert", headers, body)
|
|
166
|
-
if err != OK:
|
|
167
|
-
return false
|
|
168
|
-
|
|
169
|
-
# Wait for request to process (up to 60 seconds)
|
|
170
|
-
max_wait = 6000
|
|
171
|
-
wait = 0
|
|
172
|
-
while http.get_status() == HTTPClient.STATUS_REQUESTING:
|
|
173
|
-
http.poll()
|
|
174
|
-
OS.delay_msec(10)
|
|
175
|
-
wait += 1
|
|
176
|
-
if wait > max_wait:
|
|
177
|
-
return false
|
|
178
|
-
|
|
179
|
-
if http.has_response() and http.get_response_code() == 200:
|
|
180
|
-
var rb = PackedByteArray()
|
|
181
|
-
while http.get_status() == HTTPClient.STATUS_BODY:
|
|
182
|
-
http.poll()
|
|
183
|
-
var chunk = http.read_response_body_chunk()
|
|
184
|
-
if chunk.size() == 0:
|
|
185
|
-
OS.delay_msec(10)
|
|
186
|
-
else:
|
|
187
|
-
rb.append_array(chunk)
|
|
188
|
-
|
|
189
|
-
if rb.is_empty():
|
|
190
|
-
return false
|
|
191
|
-
|
|
192
|
-
var out_file = FileAccess.open(out_glb_path, FileAccess.WRITE)
|
|
193
|
-
if not out_file:
|
|
194
|
-
return false
|
|
195
|
-
out_file.store_buffer(rb)
|
|
196
|
-
out_file.close()
|
|
197
|
-
|
|
198
|
-
print("Successfully compiled %s using scad-gltf serve fallback." % source_path.get_file())
|
|
199
|
-
return true
|
|
200
|
-
|
|
201
|
-
return false
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scad-gltf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
"prepublishOnly": "npm install --prefix editor && npm run build --prefix editor"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
51
|
+
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
52
52
|
"clipboardy": "^5.3.2",
|
|
53
53
|
"express": "^5.2.1",
|
|
54
|
-
"puppeteer": "^25.
|
|
54
|
+
"puppeteer": "^25.12.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"prettier": "3.9.
|
|
57
|
+
"prettier": "3.9.9"
|
|
58
58
|
}
|
|
59
59
|
}
|