Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make FreeCAD compatible after guidata removal #12

Merged
merged 15 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 80 additions & 29 deletions jupytercad_freecad/freecad/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@
fc = None


def _rgb_to_hex(rgb):
"""Converts a list of RGB values [0-1] to a hex color string"""
return "#{:02x}{:02x}{:02x}".format(
int(rgb[0] * 255), int(rgb[1] * 255), int(rgb[2] * 255)
)


def _hex_to_rgb(hex_color):
"""Convert hex color string to an RGB tuple"""
hex_color = hex_color.lstrip("#")
return tuple(int(hex_color[i : i + 2], 16) / 255.0 for i in (0, 2, 4))


def _guidata_to_options(guidata):
"""Converts freecad guidata into options that JupyterCad understands"""
options = {}
Expand All @@ -34,11 +47,14 @@ def _guidata_to_options(guidata):
options[obj_name] = data
continue

# Handle FreeCAD's ShapeColor property and map to JupyterCad's color
if "ShapeColor" in data:
obj_options["color"] = list(data["ShapeColor"]["value"])
color_rgb = data["ShapeColor"]["value"]
obj_options["color"] = _rgb_to_hex(color_rgb)

# Handle FreeCAD's Visibility property and map to JupyterCad's visible property
if "Visibility" in data:
obj_options["visibility"] = data["Visibility"]["value"]
obj_options["visible"] = data["Visibility"]["value"]

options[obj_name] = obj_options

Expand All @@ -55,17 +71,18 @@ def _options_to_guidata(options):
# We need to make a special case to "GuiCameraSettings" because freecad's
# OfflineRenderingUtils mixes the camera settings with 3D objects
if obj_name == "GuiCameraSettings":
options[obj_name] = data
gui_data[obj_name] = data
continue

# Handle color property from JupyterCad to FreeCAD's ShapeColor
if "color" in data:
obj_data["ShapeColor"] = dict(
type="App::PropertyColor", value=tuple(data["color"])
)
rgb_value = _hex_to_rgb(data["color"])
obj_data["ShapeColor"] = dict(type="App::PropertyColor", value=rgb_value)

if "visibility" in data:
# Handle visibility property from JupyterCad to FreeCAD's Visibility
if "visible" in data:
obj_data["Visibility"] = dict(
type="App::PropertyBool", value=data["visibility"]
type="App::PropertyBool", value=data["visible"]
)

gui_data[obj_name] = obj_data
Expand All @@ -81,6 +98,7 @@ def __init__(self) -> None:
self._metadata = {}
self._id = None
self._visible = True
self._guidata = {}
self._prop_handlers: Dict[str, Type[BaseProp]] = {}
for Cls in Props.__dict__.values():
if isinstance(Cls, type) and issubclass(Cls, BaseProp):
Expand Down Expand Up @@ -115,15 +133,31 @@ def load(self, base64_content: str) -> None:
# Get metadata
self._metadata = fc_file.Meta

# Get GuiData (metadata from the GuiDocument.xml file)
self._options["guidata"] = _guidata_to_options(
OfflineRenderingUtils.getGuiData(tmp.name)
)
# Get GuiData and assign it to the internal attribute
self._guidata = _guidata_to_options(OfflineRenderingUtils.getGuiData(tmp.name))

# Get objects
self._objects = []
for obj in fc_file.Objects:
self._objects.append(self._fc_to_jcad_obj(obj))
obj_name = obj.Name

obj_data = self._fc_to_jcad_obj(obj)

if obj_name in self._guidata:
if "color" in self._guidata[obj_name]:
default_color = "#808080"
gui_data_color = self._guidata[obj_name]["color"]

obj_data["parameters"]["Color"] = (
gui_data_color if gui_data_color else default_color
)
if "visible" in self._guidata[obj_name]:
gui_data_visible = self._guidata[obj_name]["visible"]
obj_data["visible"] = (
gui_data_visible if gui_data_visible is not None else True
)

self._objects.append(obj_data)

os.remove(tmp.name)

Expand Down Expand Up @@ -152,29 +186,46 @@ def save(self, objects: List, options: Dict, metadata: Dict) -> None:

for obj_name in to_update:
py_obj = new_objs[obj_name]

fc_obj = fc_file.getObject(py_obj["name"])

for prop, jcad_prop_value in py_obj["parameters"].items():
prop_type = fc_obj.getTypeIdOfProperty(prop)
prop_handler = self._prop_handlers.get(prop_type, None)
if prop_handler is not None:
fc_value = prop_handler.jcad_to_fc(
jcad_prop_value,
jcad_object=objects,
fc_prop=getattr(fc_obj, prop),
fc_object=fc_obj,
fc_file=fc_file,
if hasattr(fc_obj, prop):
try:
prop_type = fc_obj.getTypeIdOfProperty(prop)
prop_handler = self._prop_handlers.get(prop_type, None)
if prop_handler is not None:
fc_value = prop_handler.jcad_to_fc(
jcad_prop_value,
jcad_object=objects,
fc_prop=getattr(fc_obj, prop),
fc_object=fc_obj,
fc_file=fc_file,
)
if fc_value:
setattr(fc_obj, prop, fc_value)
except AttributeError as e:
print(
f"Error accessing property '{prop}' on object '{fc_obj.Name}': {e}"
)
else:
print(
f"Property '{prop}' does not exist on object '{fc_obj.Name}' and is not handled"
)
if fc_value:
try:
setattr(fc_obj, prop, fc_value)
except Exception:
pass

# Handle updating the color in guidata
if "Color" in py_obj["parameters"]:
new_hex_color = py_obj["parameters"]["Color"]
else:
new_hex_color = "#808080" # Default to gray if no color is provided

if obj_name in self._guidata:
self._guidata[obj_name]["color"] = new_hex_color
else:
self._guidata[obj_name] = {"color": new_hex_color}

OfflineRenderingUtils.save(
fc_file,
guidata=_options_to_guidata(options.get("guidata", {})),
guidata=_options_to_guidata(self._guidata),
)

fc_file.recompute()
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@
"dependencies": {
"@jupyter/collaboration": "^2.0.0",
"@jupyter/docprovider": "^2.0.0",
"@jupytercad/base": "^2.0.0",
"@jupytercad/jupytercad-core": "^2.0.0",
"@jupytercad/schema": "^2.0.0",
"@jupytercad/base": "^3.0.0-alpha.1",
"@jupytercad/jupytercad-core": "^3.0.0-alpha.1",
"@jupytercad/schema": "^3.0.0-alpha.1",
"@jupyterlab/application": "^4.0.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ classifiers = [
]
dependencies = [
"jupyter_ydoc>=2,<3",
"jupytercad_core>=2.0.0,<3",
"jupytercad_core>=3.0.0a1,<4",
]
dynamic = ["version", "description", "authors", "urls", "keywords"]

Expand Down
Loading