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.
@@ -1,4 +1,4 @@
1
- import{i as e,n as t,r as n,t as r}from"./assets/prompt-ui-B9QCNLfJ.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=`
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>
@@ -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-CgVih1C4.js"></script>
48
- <link rel="modulepreload" crossorigin href="./assets/OutputPass-B6MrFnGE.js">
49
- <link rel="modulepreload" crossorigin href="./assets/prompt-ui-B9QCNLfJ.js">
50
- <link rel="stylesheet" crossorigin href="./assets/index-B1KAb97r.css">
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 Controls</h2>
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>
@@ -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-BbUpKLF4.js"></script>
6
- <link rel="modulepreload" crossorigin href="./assets/OutputPass-B6MrFnGE.js">
7
- <link rel="stylesheet" crossorigin href="./assets/preview-A6M04CFK.css">
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="viewer-controls">
12
- <label><input type="checkbox" id="show-grid-cb" checked /> Grid</label>
13
- <label><input type="checkbox" id="wireframe-cb" /> Wireframe</label>
14
- <button id="screenshot-btn" title="Take Screenshot & Add to Chat">
15
- 📷
16
- </button>
17
- <button id="fullscreen-btn" title="Toggle Full Screen">⛶</button>
18
- </div>
19
- <div id="save-ui">
20
- <input type="text" id="filename-input" placeholder="Name" />
21
- <button id="save-btn">Save</button>
22
- <button id="open-editor-btn">Edit</button>
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 t=e||("document"in self?document.currentScript.src:"")||location.href;if(s[t])return;let l={};const o=e=>i(e,t),c={module:{uri:t},exports:l,require:o};s[t]=Promise.all(r.map(e=>c[e]||o(e))).then(e=>(n(...e),l))}}define(["./workbox-9c191d2f"],function(e){"use strict";self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"registerSW.js",revision:"402b66900e731ca748771b6fc5e7a068"},{url:"preview.html",revision:"0cb1d1cf5e8358e608927e2f3efc3b14"},{url:"index.html",revision:"e09c648877bb53e1fc65cfebb5a020af"},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"content.js",revision:"a0ee0962c4c041063d42d1a33eb0b294"},{url:"content.css",revision:"4ea93a2d03164b49c80a48138305e564"},{url:"content-loader.js",revision:"d94f1755552d49c1fe3a6da123e8a346"},{url:"aristea_wreck_puresky_2k.hdr",revision:"e764c66f871ab0987f3fac422edc841d"},{url:"assets/prompt-ui-B9QCNLfJ.js",revision:null},{url:"assets/preview-BbUpKLF4.js",revision:null},{url:"assets/preview-A6M04CFK.css",revision:null},{url:"assets/openscad-CdBCY4mx.wasm",revision:null},{url:"assets/index-CgVih1C4.js",revision:null},{url:"assets/index-B1KAb97r.css",revision:null},{url:"assets/OutputPass-B6MrFnGE.js",revision:null},{url:"icon.png",revision:"a22007c1fb99682b77960b0d210e273e"},{url:"manifest.webmanifest",revision:"705049c691faa532cc20c736d56d54df"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html")))});
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")))});
@@ -5,4 +5,3 @@ description="Imports .scad files directly as 3D scenes using scad-gltf"
5
5
  author="Ilia Grigorev"
6
6
  version="0.1"
7
7
  script="scad_plugin.gd"
8
- license="MIT"
@@ -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
- print("scad-gltf convert failed for %s. Attempting fallback to local scad-gltf serve..." % path.get_file())
36
- var fallback_success = _try_scad_serve_fallback(global_source, temp_glb_path)
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
@@ -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 EditorPlugin
6
3
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scad-gltf",
3
- "version": "0.3.3",
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.30.0",
51
+ "@modelcontextprotocol/sdk": "^1.31.0",
52
52
  "clipboardy": "^5.3.2",
53
53
  "express": "^5.2.1",
54
- "puppeteer": "^25.9.0"
54
+ "puppeteer": "^25.12.0"
55
55
  },
56
56
  "devDependencies": {
57
- "prettier": "3.9.6"
57
+ "prettier": "3.9.9"
58
58
  }
59
59
  }