AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * 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
import Path.Base.Util as PathUtil
from typing import Dict, List, Any, Optional, Tuple
import tempfile
import os
def find_shape_object(doc: "FreeCAD.Document") -> Optional["FreeCAD.DocumentObject"]:
"""
Find the primary object representing the shape in a document.
Looks for PartDesign::Body, then Part::Feature. Falls back to the first
object if no better candidate is found.
Args:
doc (FreeCAD.Document): The document to search within.
Returns:
Optional[FreeCAD.DocumentObject]: The found object or None.
"""
obj = None
# Prioritize Body
for o in doc.Objects:
if o.isDerivedFrom("PartDesign::Body"):
return o
# Keep track of the first Part::Feature found as a fallback
if obj is None and o.isDerivedFrom("Part::Feature"):
obj = o
if obj:
return obj
# Fallback to the very first object if nothing else suitable found
return doc.Objects[0] if doc.Objects else None
def get_unset_value_for(attribute_type: str):
if attribute_type == "App::PropertyLength":
return FreeCAD.Units.Quantity(0)
elif attribute_type == "App::PropertyString":
return ""
elif attribute_type == "App::PropertyInteger":
return 0
return None
def get_object_properties(
obj: "FreeCAD.DocumentObject",
props: Optional[List[str]] = None,
group: Optional[str] = None,
exclude_groups: Optional[List[str]] = None,
) -> Dict[str, Tuple[Any, str]]:
"""
Extract properties from a FreeCAD PropertyBag, including their types.
Issues warnings for missing parameters but does not raise an error.
Args:
obj: The PropertyBag to extract properties from.
props (List[str], optional): A list of property names to look for.
If None, all properties in obj.PropertiesList are considered.
group (str, optional): If provided, only properties belonging to this group are extracted.
Returns:
Dict[str, Tuple[Any, str]]: A dictionary mapping property names to a tuple
(value, type_id). Values are FreeCAD native types.
If a property is missing, its value will be None.
"""
properties = {}
for name in props or obj.PropertiesList:
if group and not obj.getGroupOfProperty(name) == group:
continue
if exclude_groups and obj.getGroupOfProperty(name) in exclude_groups:
continue
if hasattr(obj, name):
value = getattr(obj, name)
type_id = obj.getTypeIdOfProperty(name)
properties[name] = value, type_id
else:
# Log a warning if a parameter expected by the shape class is missing
Path.Log.debug(
f"Parameter '{name}' not found on object '{obj.Label}' "
f"({obj.Name}). Default value will be used by the shape class."
)
properties[name] = None, "App::PropertyString"
return properties
def update_shape_object_properties(
obj: "FreeCAD.DocumentObject", parameters: Dict[str, Any]
) -> None:
"""
Update properties of a FreeCAD PropertyBag based on a dictionary of parameters.
Args:
obj (FreeCAD.DocumentObject): The PropertyBag to update properties on.
parameters (Dict[str, Any]): A dictionary of property names and values.
"""
for name, value in parameters.items():
if hasattr(obj, name):
try:
PathUtil.setProperty(obj, name, value)
except Exception as e:
Path.Log.warning(
f"Failed to set property '{name}' on object '{obj.Label}'"
f" ({obj.Name}) with value '{value}': {e}"
)
else:
# This simply means that the Shape File doesn't have this property in it.
Path.Log.debug(
f"Property '{name}' not found on object '{obj.Label}' ({obj.Name}). Skipping."
)
def get_doc_state() -> Any:
"""
Used to make a "snapshot" of the current state of FreeCAD, to allow
for restoring the ActiveDocument and selection state later.
"""
doc_name = FreeCAD.ActiveDocument.Name if FreeCAD.ActiveDocument else None
if FreeCAD.GuiUp:
import FreeCADGui
selection = FreeCADGui.Selection.getSelection()
else:
selection = []
return doc_name, selection
def restore_doc_state(state):
doc_name, selection = state
if doc_name:
FreeCAD.setActiveDocument(doc_name)
if FreeCAD.GuiUp:
import FreeCADGui
for sel in selection:
FreeCADGui.Selection.addSelection(doc_name, sel.Name)
class ShapeDocFromBytes:
"""
Context manager to create and manage a temporary FreeCAD document,
loading content from a byte string.
"""
def __init__(self, content: bytes):
self._content = content
self._doc = None
self._temp_file = None
self._old_state = None
def __enter__(self) -> "FreeCAD.Document":
"""Creates a new temporary FreeCAD document or loads cache if provided."""
# Create a temporary file and write the cache content to it
with tempfile.NamedTemporaryFile(suffix=".FCStd", delete=False) as tmp_file:
tmp_file.write(self._content)
self._temp_file = tmp_file.name
# When we open a new document, FreeCAD loses the state, of the active
# document (i.e. current selection), even if the newly opened document
# is a hidden one.
# So we need to restore the active document state at the end.
self._old_state = get_doc_state()
# Open the document from the temporary file
# Use a specific name to avoid clashes if multiple docs are open
# Open the document from the temporary file
self._doc = FreeCAD.openDocument(self._temp_file, hidden=True)
if not self._doc:
raise RuntimeError(f"Failed to open document from {self._temp_file}")
return self._doc
def __exit__(self, exc_type, exc_value, traceback) -> None:
"""Closes the temporary FreeCAD document and cleans up the temp file."""
if self._doc:
# Note that .closeDocument() is extremely slow; it takes
# almost 400ms per document - much longer than opening!
FreeCAD.closeDocument(self._doc.Name)
self._doc = None
# Restore the original active document
restore_doc_state(self._old_state)
# Clean up the temporary file if it was created
if self._temp_file and os.path.exists(self._temp_file):
try:
os.remove(self._temp_file)
except Exception as e:
Path.Log.warning(f"Failed to remove temporary file {self._temp_file}: {e}")