Skip to content

Commit

Permalink
Make FreeCAD compatible after guidata removal (#12)
Browse files Browse the repository at this point in the history
* Skip handling visibility and color properties with guidata

* Restore handling of visibility and color properties in guidata

* Update jupytercad_freecad/freecad/loader.py

* Handle color property for fcstd files

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Bump jcad to `v3.0.0`

* Loading works nicely

* Saving also working

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update jupytercad_freecad/freecad/loader.py

Co-authored-by: martinRenou <[email protected]>

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Handle default visibility

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* make `guidata` an internal attribute in `FCStd` class

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: martinRenou <[email protected]>
  • Loading branch information
3 people authored Sep 27, 2024
1 parent fd6a4a1 commit 2ff256f
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 33 deletions.
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

0 comments on commit 2ff256f

Please sign in to comment.