AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
# ***************************************************************************
# * Copyright (c) 2025 Samuel Abels <knipknap@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import Path
from typing import Any, Dict, List, Optional
class DetachedDocumentObject:
"""
A lightweight class mimicking the property API of a FreeCAD DocumentObject.
This class is used by ToolBit instances when they are not associated
with a real FreeCAD DocumentObject, allowing properties to be stored
and accessed in a detached state.
"""
def __init__(self, label: str = "DetachedObject"):
self.Label: str = label
self.Name: str = label.replace(" ", "_")
self.PropertiesList: List[str] = []
self._properties: Dict[str, Any] = {}
self._property_groups: Dict[str, Optional[str]] = {}
self._property_types: Dict[str, Optional[str]] = {}
self._property_docs: Dict[str, Optional[str]] = {}
self._editor_modes: Dict[str, int] = {}
self._property_enums: Dict[str, List[str]] = {}
def addProperty(
self,
thetype: Optional[str],
name: str,
group: Optional[str],
doc: Optional[str],
) -> None:
"""Mimics FreeCAD DocumentObject.addProperty."""
if name not in self._properties:
self.PropertiesList.append(name)
self._properties[name] = None
self._property_groups[name] = group
self._property_types[name] = thetype
self._property_docs[name] = doc
if thetype in [
"App::PropertyQuantity",
"App::PropertyLength",
"App::PropertyArea",
"App::PropertyVolume",
"App::PropertyAngle",
]:
# Initialize Quantity properties with a default value
self._properties[name] = FreeCAD.Units.Quantity(0.0)
def removeProperty(self, name: str) -> None:
"""Removes a property from the detached object."""
if name in self._properties:
if name in self.PropertiesList:
self.PropertiesList.remove(name)
del self._properties[name]
self._property_groups.pop(name, None)
self._property_types.pop(name, None)
self._property_docs.pop(name, None)
self._editor_modes.pop(name, None)
self._property_enums.pop(name, None)
def getPropertyByName(self, name: str) -> Any:
"""Mimics FreeCAD DocumentObject.getPropertyByName."""
return self._properties.get(name)
def setPropertyByName(self, name: str, value: Any) -> None:
"""Mimics FreeCAD DocumentObject.setPropertyByName."""
self._properties[name] = value
def __setattr__(self, name: str, value: Any) -> None:
"""
Intercept attribute assignment. This is done to behave like
FreeCAD's DocumentObject, which may have any property assigned,
pre-defined or not.
Without this, code linters report an error when trying to set
a property that is not defined in the class.
Handles assignment of enumeration choices (lists/tuples) and
converts string representations of Quantity types to Quantity objects.
"""
if name in ("PropertiesList", "Label", "Name") or name.startswith("_"):
super().__setattr__(name, value)
return
# Handle assignment of enumeration choices (list/tuple)
prop_type = self._property_types.get(name)
if prop_type == "App::PropertyEnumeration" and isinstance(value, (list, tuple)):
self._property_enums[name] = list(value)
assert len(value) > 0, f"Enum property '{name}' must have at least one entry"
self._properties.setdefault(name, value[0])
return
# Attempt to convert string values to Quantity if the property type is Quantity
elif prop_type in [
"App::PropertyQuantity",
"App::PropertyLength",
"App::PropertyArea",
"App::PropertyVolume",
"App::PropertyAngle",
]:
value = FreeCAD.Units.Quantity(value)
# Store the (potentially converted) value
self._properties[name] = value
Path.Log.debug(
f"DetachedDocumentObject: Set property '{name}' to "
f"value {value} (type: {type(value)})"
)
def __getattr__(self, name: str) -> Any:
"""Intercept attribute access."""
if name in self._properties:
return self._properties[name]
# Default behaviour: raise AttributeError
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
def setEditorMode(self, name: str, mode: int) -> None:
"""Stores editor mode settings in detached state."""
self._editor_modes[name] = mode
def getEditorMode(self, name: str) -> int:
"""Stores editor mode settings in detached state."""
return self._editor_modes.get(name, 0) or 0
def getGroupOfProperty(self, name: str) -> Optional[str]:
"""Returns the stored group for a property in detached state."""
return self._property_groups.get(name)
def getTypeIdOfProperty(self, name: str) -> Optional[str]:
"""Returns the stored type string for a property in detached state."""
return self._property_types.get(name)
def getEnumerationsOfProperty(self, name: str) -> List[str]:
"""Returns the stored enumeration list for a property."""
return self._property_enums.get(name, [])
@property
def ExpressionEngine(self) -> List[Any]:
"""Mimics the ExpressionEngine attribute of a real DocumentObject."""
return [] # Return an empty list to satisfy iteration
def copy_to(self, obj: FreeCAD.DocumentObject) -> None:
"""
Copies properties from this detached object to a real DocumentObject.
"""
for prop_name in self.PropertiesList:
if not hasattr(self, prop_name):
continue
prop_value = self.getPropertyByName(prop_name)
prop_type = self._property_types.get(prop_name)
prop_group = self._property_groups.get(prop_name)
prop_doc = self._property_docs.get(prop_name, "")
prop_editor_mode = self._editor_modes.get(prop_name)
# If the property doesn't exist in the target object, add it
if not hasattr(obj, prop_name):
# For enums, addProperty expects "App::PropertyEnumeration"
# The list of choices is set afterwards.
obj.addProperty(prop_type, prop_name, prop_group, prop_doc)
# If it's an enumeration, set its list of choices first
if prop_type == "App::PropertyEnumeration":
enum_choices = self._property_enums.get(prop_name)
assert enum_choices is not None
setattr(obj, prop_name, enum_choices)
# Set the property value and editor mode
try:
if prop_type == "App::PropertyEnumeration":
first_choice = self._property_enums[prop_name][0]
setattr(obj, prop_name, first_choice)
setattr(obj, prop_name, prop_value)
except Exception as e:
Path.Log.error(
f"Error setting property {prop_name} to {prop_value} "
f"(type: {type(prop_value)}, expected type: {prop_type}): {e}"
)
raise
if prop_editor_mode is not None:
obj.setEditorMode(prop_name, prop_editor_mode)