Skip to content

Commit 25acf9d

Browse files
committed
Fix coordinate duplication
1 parent d3373eb commit 25acf9d

File tree

1 file changed

+96
-75
lines changed

1 file changed

+96
-75
lines changed
Lines changed: 96 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,129 @@
11
import freecad as fc
22
import OfflineRenderingUtils # type: ignore[import]
3-
import base64
43
import tempfile
5-
import os
64
import re
75

86
from .loader import _hex_to_rgb
97
from .props.base_prop import BaseProp
108
from . import props as Props
119

12-
1310
def sanitize_object_name(name: str) -> str:
1411
"""Convert object names to FreeCAD-compatible format."""
15-
# Replace spaces or hyphens with underscores, then drop any other non-word
1612
sanitized = name.replace(" ", "_").replace("-", "_")
1713
return re.sub(r"[^\w_]", "_", sanitized)
1814

19-
20-
def export_jcad_to_fcstd(jcad_dict: dict) -> "fc.Document":
21-
doc = fc.app.newDocument("__jcad_export__")
22-
doc.Meta = jcad_dict.get("metadata", {})
23-
24-
# Build Prop handler lookup
25-
prop_handlers = {
26-
Cls.name(): Cls
27-
for Cls in Props.__dict__.values()
15+
def build_prop_handlers():
16+
return {
17+
Cls.name(): Cls for Cls in Props.__dict__.values()
2818
if isinstance(Cls, type) and issubclass(Cls, BaseProp)
2919
}
3020

31-
# 1) Build a simple original→sanitized name map
32-
name_mapping = {
33-
obj["name"]: sanitize_object_name(obj["name"])
34-
for obj in jcad_dict.get("objects", [])
35-
}
36-
37-
# 2) Rename each object in place, and fix any string or list references in parameters
21+
def update_references(jcad_dict, name_mapping):
3822
for obj in jcad_dict.get("objects", []):
39-
orig = obj["name"]
40-
obj["name"] = name_mapping[orig]
41-
4223
params = obj.get("parameters", {})
4324
for key, val in params.items():
44-
# If it’s a string reference to another object, rewrite it:
4525
if isinstance(val, str) and val in name_mapping:
4626
params[key] = name_mapping[val]
47-
48-
# If it’s a list of references, rewrite every entry that matches a key:
4927
elif isinstance(val, list):
5028
params[key] = [
51-
name_mapping[item] if (isinstance(item, str) and item in name_mapping) else item
29+
name_mapping.get(item, item) if isinstance(item, str) else item
5230
for item in val
5331
]
5432

55-
# 3) Replay sanitized objects into FreeCAD
56-
guidata = {}
57-
for jcad_obj in jcad_dict.get("objects", []):
58-
shape_type = jcad_obj["shape"]
59-
name = jcad_obj["name"]
60-
params = jcad_obj.get("parameters", {})
61-
opts = jcad_dict.get("options", {}).get(name, {})
33+
def create_body_and_coordinates(doc, body_obj, fc_objects):
34+
body_name = body_obj["name"]
35+
body_fc = doc.addObject("PartDesign::Body", body_name)
36+
fc_objects[body_name] = body_fc
37+
38+
if not body_fc.Origin:
39+
return body_fc
40+
41+
fc_objects["Origin"] = body_fc.Origin
42+
role_map = {
43+
'X_Axis': "X_Axis",
44+
'Y_Axis': "Y_Axis",
45+
'Z_Axis': "Z_Axis",
46+
'XY_Plane': "XY_Plane",
47+
'XZ_Plane': "XZ_Plane",
48+
'YZ_Plane': "YZ_Plane"
49+
}
50+
for child in body_fc.Origin.Group:
51+
role = getattr(child, 'Role', '')
52+
if role in role_map:
53+
fc_objects[role_map[role]] = child
54+
return body_fc
55+
56+
def apply_object_properties(fc_obj, jcad_obj, prop_handlers, doc):
57+
params = jcad_obj.get("parameters", {})
58+
for prop, jval in params.items():
59+
if prop == "Color" or not hasattr(fc_obj, prop):
60+
continue
61+
prop_type = fc_obj.getTypeIdOfProperty(prop)
62+
Handler = prop_handlers.get(prop_type)
63+
if not Handler:
64+
continue
65+
try:
66+
new_val = Handler.jcad_to_fc(
67+
jval,
68+
jcad_object=jcad_obj,
69+
fc_prop=getattr(fc_obj, prop),
70+
fc_object=fc_obj,
71+
fc_file=doc,
72+
)
73+
if new_val is not None:
74+
setattr(fc_obj, prop, new_val)
75+
except Exception as e:
76+
print(f"Error setting {prop} on {fc_obj.Name}: {e}")
77+
78+
def build_guidata_entry(name, params, opts):
79+
hexcol = params.get("Color") or opts.get("color", "#808080")
80+
return {
81+
"ShapeColor": {"type": "App::PropertyColor", "value": _hex_to_rgb(hexcol)},
82+
"Visibility": {"type": "App::PropertyBool", "value": opts.get("visible", True)},
83+
}
6284

63-
# Add object to FreeCAD
64-
fc_obj = doc.addObject(shape_type, name)
85+
def export_jcad_to_fcstd(jcad_dict: dict) -> "fc.Document":
86+
doc = fc.app.newDocument("__jcad_export__")
87+
doc.Meta = jcad_dict.get("metadata", {})
88+
prop_handlers = build_prop_handlers()
89+
coordinate_names = {"Origin", "X_Axis", "Y_Axis", "Z_Axis", "XY_Plane", "XZ_Plane", "YZ_Plane"}
6590

66-
# Apply all non-color props via handlers
67-
for prop, jval in params.items():
68-
if prop == "Color":
69-
continue
70-
if hasattr(fc_obj, prop):
71-
t = fc_obj.getTypeIdOfProperty(prop)
72-
Handler = prop_handlers.get(t)
73-
if Handler:
74-
try:
75-
new_val = Handler.jcad_to_fc(
76-
jval,
77-
jcad_object=jcad_obj,
78-
fc_prop=getattr(fc_obj, prop),
79-
fc_object=fc_obj,
80-
fc_file=doc,
81-
)
82-
if new_val is not None:
83-
setattr(fc_obj, prop, new_val)
84-
except Exception as e:
85-
print(f"Error setting {prop} on {name}: {e}")
91+
# Create name mapping and update references
92+
name_mapping = {obj["name"]: sanitize_object_name(obj["name"]) for obj in jcad_dict.get("objects", [])}
93+
for obj in jcad_dict["objects"]:
94+
obj["name"] = name_mapping[obj["name"]]
95+
update_references(jcad_dict, name_mapping)
8696

87-
# Build guidata entry
88-
hexcol = params.get("Color") or opts.get("color") or "#808080"
89-
rgb = _hex_to_rgb(hexcol)
90-
guidata[name] = {
91-
"ShapeColor": {"type": "App::PropertyColor", "value": rgb},
92-
"Visibility": {"type": "App::PropertyBool", "value": opts.get("visible", True)},
93-
}
97+
fc_objects = {}
98+
guidata = {}
99+
body_objs = [o for o in jcad_dict.get("objects", []) if o["shape"] == "PartDesign::Body"]
100+
other_objs = [o for o in jcad_dict["objects"] if o not in body_objs and o["name"] not in coordinate_names]
101+
102+
# Process bodies and coordinates
103+
for body_obj in body_objs:
104+
body_fc = create_body_and_coordinates(doc, body_obj, fc_objects)
105+
apply_object_properties(body_fc, body_obj, prop_handlers, doc)
106+
107+
# Get body properties for coordinates
108+
body_params = body_obj.get("parameters", {})
109+
body_opts = jcad_dict.get("options", {}).get(body_obj["name"], {})
110+
guidata[body_obj["name"]] = build_guidata_entry(body_obj["name"], body_params, body_opts)
111+
112+
# Add coordinate objects to guidata
113+
for coord_name in coordinate_names:
114+
if coord_name in fc_objects:
115+
guidata[coord_name] = build_guidata_entry(coord_name, body_params, body_opts)
116+
117+
# Process other objects
118+
for obj in other_objs:
119+
fc_obj = doc.addObject(obj["shape"], obj["name"])
120+
fc_objects[obj["name"]] = fc_obj
121+
apply_object_properties(fc_obj, obj, prop_handlers, doc)
122+
obj_opts = jcad_dict.get("options", {}).get(obj["name"], {})
123+
guidata[obj["name"]] = build_guidata_entry(obj["name"], obj.get("parameters", {}), obj_opts)
94124

95125
doc.recompute()
96-
97-
# 4) Write out a temp FCStd, then apply colors/visibility
126+
98127
with tempfile.NamedTemporaryFile(delete=False, suffix=".FCStd") as tmp:
99-
tmp_path = tmp.name
100-
doc.saveAs(tmp_path)
101-
102-
OfflineRenderingUtils.save(
103-
doc,
104-
filename=tmp_path,
105-
guidata=guidata
106-
)
107-
108-
return fc.app.openDocument(tmp_path)
128+
OfflineRenderingUtils.save(doc, filename=tmp.name, guidata=guidata)
129+
return fc.app.openDocument(tmp.name)

0 commit comments

Comments
 (0)