@forgent3d/cad-runtime 0.1.0 → 0.1.2
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/package.json
CHANGED
package/python/export_runner.py
CHANGED
|
@@ -94,6 +94,130 @@ def _write_stl(shape, path_out):
|
|
|
94
94
|
raise RuntimeError(f"Unable to export STL: {exc}")
|
|
95
95
|
|
|
96
96
|
|
|
97
|
+
# Keep in sync with packages/electron/src/viewer-materials.ts MATERIAL_PRESETS.
|
|
98
|
+
_VIEWER_PRESET_COLORS = {
|
|
99
|
+
"cad_clay": (0xc8 / 255, 0xd0 / 255, 0xdc / 255, 1.0),
|
|
100
|
+
"matte_plastic": (0xb9 / 255, 0xc2 / 255, 0xd0 / 255, 1.0),
|
|
101
|
+
"gloss_plastic": (0xb9 / 255, 0xc2 / 255, 0xd0 / 255, 1.0),
|
|
102
|
+
"painted_metal": (0x8f / 255, 0xa3 / 255, 0xb8 / 255, 1.0),
|
|
103
|
+
"anodized_aluminum":(0x4f / 255, 0x8f / 255, 0xd8 / 255, 1.0),
|
|
104
|
+
"brushed_steel": (0xa7 / 255, 0xb0 / 255, 0xba / 255, 1.0),
|
|
105
|
+
"dark_steel": (0x4b / 255, 0x55 / 255, 0x63 / 255, 1.0),
|
|
106
|
+
"polished_metal": (0xd0 / 255, 0xd6 / 255, 0xdc / 255, 1.0),
|
|
107
|
+
"rubber": (0x24 / 255, 0x28 / 255, 0x32 / 255, 1.0),
|
|
108
|
+
"glass_clear": (0xd8 / 255, 0xec / 255, 0xff / 255, 0.34),
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _parse_hex_color(text):
|
|
113
|
+
s = str(text or "").strip().lstrip("#")
|
|
114
|
+
if len(s) == 3:
|
|
115
|
+
s = "".join(ch * 2 for ch in s)
|
|
116
|
+
if len(s) not in (6, 8):
|
|
117
|
+
return None
|
|
118
|
+
try:
|
|
119
|
+
r = int(s[0:2], 16) / 255
|
|
120
|
+
g = int(s[2:4], 16) / 255
|
|
121
|
+
b = int(s[4:6], 16) / 255
|
|
122
|
+
a = (int(s[6:8], 16) / 255) if len(s) == 8 else 1.0
|
|
123
|
+
return (r, g, b, a)
|
|
124
|
+
except ValueError:
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _resolve_material_color(spec):
|
|
129
|
+
"""Resolve a __viewer.materials entry to an (r, g, b, a) tuple of floats."""
|
|
130
|
+
if spec is None:
|
|
131
|
+
return None
|
|
132
|
+
if isinstance(spec, bool):
|
|
133
|
+
return None
|
|
134
|
+
if isinstance(spec, int):
|
|
135
|
+
v = spec & 0xffffff
|
|
136
|
+
return (((v >> 16) & 0xff) / 255, ((v >> 8) & 0xff) / 255, (v & 0xff) / 255, 1.0)
|
|
137
|
+
if isinstance(spec, str):
|
|
138
|
+
text = spec.strip()
|
|
139
|
+
if text in _VIEWER_PRESET_COLORS:
|
|
140
|
+
return _VIEWER_PRESET_COLORS[text]
|
|
141
|
+
return _parse_hex_color(text)
|
|
142
|
+
if isinstance(spec, dict):
|
|
143
|
+
explicit = spec.get("color")
|
|
144
|
+
if explicit is not None:
|
|
145
|
+
resolved = _resolve_material_color(explicit)
|
|
146
|
+
if resolved is not None:
|
|
147
|
+
return resolved
|
|
148
|
+
preset_name = str(spec.get("preset") or spec.get("material") or "").strip()
|
|
149
|
+
if preset_name in _VIEWER_PRESET_COLORS:
|
|
150
|
+
return _VIEWER_PRESET_COLORS[preset_name]
|
|
151
|
+
return None
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _load_model_params(model_name: str):
|
|
155
|
+
path = os.path.join(MODELS_DIR, model_name, "params.json")
|
|
156
|
+
if not os.path.isfile(path):
|
|
157
|
+
return None
|
|
158
|
+
try:
|
|
159
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
160
|
+
return json.load(f)
|
|
161
|
+
except Exception:
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _apply_viewer_colors(shape, params):
|
|
166
|
+
"""Stamp build123d .color from params.json __viewer.materials so export_gltf preserves it."""
|
|
167
|
+
if not isinstance(params, dict):
|
|
168
|
+
return
|
|
169
|
+
viewer = params.get("__viewer") or params.get("viewer") or {}
|
|
170
|
+
materials = viewer.get("materials") if isinstance(viewer, dict) else None
|
|
171
|
+
if not isinstance(materials, dict):
|
|
172
|
+
return
|
|
173
|
+
parts_spec = materials.get("parts") if isinstance(materials.get("parts"), dict) else {}
|
|
174
|
+
default_rgba = _resolve_material_color(materials.get("default"))
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
from build123d import Color # type: ignore
|
|
178
|
+
except Exception:
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
def _set(node, rgba):
|
|
182
|
+
if rgba is None:
|
|
183
|
+
return
|
|
184
|
+
try:
|
|
185
|
+
node.color = Color(float(rgba[0]), float(rgba[1]), float(rgba[2]), float(rgba[3]))
|
|
186
|
+
except Exception:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
children = list(getattr(shape, "children", []) or [])
|
|
191
|
+
except TypeError:
|
|
192
|
+
children = []
|
|
193
|
+
|
|
194
|
+
if not children:
|
|
195
|
+
# Single-shape model: apply default color if configured.
|
|
196
|
+
_set(shape, default_rgba)
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
for child in children:
|
|
200
|
+
label = str(getattr(child, "label", "") or "").strip()
|
|
201
|
+
spec = parts_spec.get(label) if label else None
|
|
202
|
+
rgba = _resolve_material_color(spec) if spec is not None else None
|
|
203
|
+
if rgba is None:
|
|
204
|
+
rgba = default_rgba
|
|
205
|
+
_set(child, rgba)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _write_glb(shape, path_out, params=None):
|
|
209
|
+
try:
|
|
210
|
+
from build123d import export_gltf, Unit # type: ignore
|
|
211
|
+
except Exception as exc:
|
|
212
|
+
raise RuntimeError(f"build123d.export_gltf unavailable: {exc}")
|
|
213
|
+
try:
|
|
214
|
+
_apply_viewer_colors(shape, params)
|
|
215
|
+
export_gltf(shape, path_out, unit=Unit.MM, binary=True)
|
|
216
|
+
return "build123d.export_gltf"
|
|
217
|
+
except Exception as exc:
|
|
218
|
+
raise RuntimeError(f"Unable to export GLB: {exc}")
|
|
219
|
+
|
|
220
|
+
|
|
97
221
|
def _resolve_model_source(model_name: str, part_name: str, source_override: str = None):
|
|
98
222
|
if source_override:
|
|
99
223
|
candidate = source_override
|
|
@@ -259,7 +383,7 @@ def build_one(model_name: str, part_name: str = None, export_format: str = "brep
|
|
|
259
383
|
return 8
|
|
260
384
|
|
|
261
385
|
fmt = (export_format or "brep").strip().lower()
|
|
262
|
-
if fmt not in ("brep", "step", "stl"):
|
|
386
|
+
if fmt not in ("brep", "step", "stl", "glb"):
|
|
263
387
|
print(f"[export_runner] Unsupported export format: {fmt}", file=sys.stderr)
|
|
264
388
|
return 7
|
|
265
389
|
|
|
@@ -276,6 +400,8 @@ def build_one(model_name: str, part_name: str = None, export_format: str = "brep
|
|
|
276
400
|
method = _write_brep(result, out)
|
|
277
401
|
elif fmt == "step":
|
|
278
402
|
method = _write_step(result, out)
|
|
403
|
+
elif fmt == "glb":
|
|
404
|
+
method = _write_glb(result, out, _load_model_params(model_name))
|
|
279
405
|
else:
|
|
280
406
|
method = _write_stl(result, out)
|
|
281
407
|
export_elapsed = time.perf_counter() - export_started
|
|
@@ -299,7 +425,7 @@ def main() -> int:
|
|
|
299
425
|
parser.add_argument("--part", default=None, help="Legacy alias for --model")
|
|
300
426
|
parser.add_argument("--part-name", default=None, help="Part directory name inside models/<model>/parts/")
|
|
301
427
|
parser.add_argument("--source", default=None, help="Optional project-relative or absolute source file path (overrides default lookup)")
|
|
302
|
-
parser.add_argument("--export-format", default="brep", choices=["brep", "step", "stl"])
|
|
428
|
+
parser.add_argument("--export-format", default="brep", choices=["brep", "step", "stl", "glb"])
|
|
303
429
|
parser.add_argument("--output", default=None, help="Optional absolute path for exported file")
|
|
304
430
|
args = parser.parse_args()
|
|
305
431
|
global PROJECT_ROOT, MODELS_DIR, CACHE_DIR
|
|
Binary file
|
|
Binary file
|