@forgent3d/cad-runtime 0.1.1 → 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 +1 -1
- package/python/export_runner.py +114 -2
package/package.json
CHANGED
package/python/export_runner.py
CHANGED
|
@@ -94,12 +94,124 @@ def _write_stl(shape, path_out):
|
|
|
94
94
|
raise RuntimeError(f"Unable to export STL: {exc}")
|
|
95
95
|
|
|
96
96
|
|
|
97
|
-
|
|
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):
|
|
98
209
|
try:
|
|
99
210
|
from build123d import export_gltf, Unit # type: ignore
|
|
100
211
|
except Exception as exc:
|
|
101
212
|
raise RuntimeError(f"build123d.export_gltf unavailable: {exc}")
|
|
102
213
|
try:
|
|
214
|
+
_apply_viewer_colors(shape, params)
|
|
103
215
|
export_gltf(shape, path_out, unit=Unit.MM, binary=True)
|
|
104
216
|
return "build123d.export_gltf"
|
|
105
217
|
except Exception as exc:
|
|
@@ -289,7 +401,7 @@ def build_one(model_name: str, part_name: str = None, export_format: str = "brep
|
|
|
289
401
|
elif fmt == "step":
|
|
290
402
|
method = _write_step(result, out)
|
|
291
403
|
elif fmt == "glb":
|
|
292
|
-
method = _write_glb(result, out)
|
|
404
|
+
method = _write_glb(result, out, _load_model_params(model_name))
|
|
293
405
|
else:
|
|
294
406
|
method = _write_stl(result, out)
|
|
295
407
|
export_elapsed = time.perf_counter() - export_started
|