# *************************************************************************** # * Copyright (c) 2017 Markus Hovorka * # * Copyright (c) 2018 Bernd Hahnebach * # * Copyright (c) 2022 Uwe Stöhr * # * * # * This file is part of the FreeCAD CAx development system. * # * * # * 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 * # * * # *************************************************************************** """Collection of functions for the Fem module. This module contains function for extracting relevant parts of geometry and a few unrelated function useful at various places in the Fem module. """ __title__ = "FEM Utilities" __author__ = "Markus Hovorka, Bernd Hahnebach, Uwe Stöhr" __url__ = "https://www.freecad.org" import os import subprocess from platform import system import FreeCAD if FreeCAD.GuiUp: import FreeCADGui from PySide import QtGui # ************************************************************************************************ # document objects def createObject(doc, name, proxy, viewProxy=None): """Add python object to document using python type string. Add a document object suitable for the *proxy* and the *viewProxy* to *doc* and attach it to the *proxy* and the *viewProxy*. This function can only be used with python proxies that specify their C++ type via the BaseType class member (e.g. Cube.BaseType). If there already exists a object with *name* a suitable unique name is generated. To auto generate a name pass ``""``. :param doc: document object to which the object is added :param name: string of the name of new object in *doc*, use ``""`` to generate a name :param proxy: python proxy for new object :param viewProxy: view proxy for new object :returns: reference to new object """ obj = doc.addObject(proxy.BaseType, name) proxy(obj) if FreeCAD.GuiUp and viewProxy is not None: viewProxy(obj.ViewObject) return obj # typeID and object type defs def type_of_obj(obj): """Return type of *obj* honoring the special typesystem of Fem. Python objects of the Fem workbench define their type via a class member ``.Type``. Return this type if the property exists. If not return the conventional ``TypeId`` value. :para obj: a document object """ if hasattr(obj, "Proxy") and hasattr(obj.Proxy, "Type"): return obj.Proxy.Type return obj.TypeId def is_of_type(obj, ty): """Compare type of *obj* with *ty* honoring Fems typesystem. See :py:func:`type_of_obj` for more info about the special typesystem of the Fem module. :returns: ``True`` if *obj* is of type *ty*, ``False`` otherwise. Type must match exactly: Derived objects are not considered to be of type of one of their super classes. """ return type_of_obj(obj) == ty def is_derived_from(obj, t): """Check if *obj* is derived from *t* honoring Fems typesystem. Essentially just call ``obj.isDerivedFrom(t)`` and return it's value. For objects using Fems typesystem (see :py:func:`type_of_obj`) return always True if the Fem type is equal to *t*. :note: Inheritance of Fem types is not checked. If *obj* uses Fems typesystem the type is just checked for equality. If the type doesn't match ``obj.isDerivedFrom`` is called as usual. See https://forum.freecad.org/viewtopic.php?f=10&t=32625 """ if hasattr(obj, "Proxy") and hasattr(obj.Proxy, "Type") and obj.Proxy.Type == t: return True return obj.isDerivedFrom(t) # ************************************************************************************************ # working dir def get_pref_working_dir(solver_obj): """Return working directory for solver honoring user settings. :throws femtools.errors.MustSaveError: If user setting is set to BESIDE and the document isn't saved. :note: Not working correctly for most cases because this circumvents directory caching of the solver framework. For solver use getMachine from run.py instead. """ from femsolver import settings dir_setting = settings.get_dir_setting() if dir_setting == settings.DirSetting.TEMPORARY: setting_working_dir = get_temp_dir(solver_obj) elif dir_setting == settings.DirSetting.BESIDE: setting_working_dir = get_beside_dir(solver_obj) elif dir_setting == settings.DirSetting.CUSTOM: setting_working_dir = get_custom_dir(solver_obj) else: setting_working_dir = "" return setting_working_dir # these are a duplicate of the methods in src/Mod/Fem/femsolver/run.py # see commit a9c19ca6d42c for more information # the FEM preferences will be used by both def get_temp_dir(obj=None): from tempfile import mkdtemp return mkdtemp(prefix="fcfem_") def get_beside_dir(obj): base = get_beside_base(obj) specific_path = os.path.join(base, obj.Label) if not os.path.isdir(specific_path): make_dir(specific_path) return specific_path def get_custom_dir(obj): base = get_custom_base(obj) specific_path = os.path.join(base, obj.Document.Name, obj.Label) if not os.path.isdir(specific_path): make_dir(specific_path) return specific_path def get_beside_base(obj): fcstdPath = obj.Document.FileName if fcstdPath == "": new_path = get_temp_dir() error_message = ( "Please save the file before executing a solver or creating a mesh. " "This must be done because the location of the working directory " 'is set to "Beside *.FCStd File". For the moment the tmp dir {} is used.'.format( new_path ) ) FreeCAD.Console.PrintError(f"{error_message}\n") # from .errors import MustSaveError # raise MustSaveError() return new_path else: return os.path.splitext(fcstdPath)[0] def get_custom_base(solver): from femsolver.settings import get_custom_dir path = get_custom_dir() if not os.path.isdir(path): new_path = get_temp_dir() error_message = ( "Selected working directory {} doesn't exist. " " For the moment the tmp dir {} is used.".format(path, new_path) ) FreeCAD.Console.PrintError(f"{error_message}\n") # from .errors import DirectoryDoesNotExistError # raise DirectoryDoesNotExistError("Invalid path") return new_path return path def check_working_dir(wdir): # check if working_dir exist, if not use a tmp dir and inform the user # print(wdir) from os.path import isdir if isdir(wdir): return True else: return False def make_dir(specific_path): try: os.makedirs(specific_path) except OSError: new_path = get_temp_dir() # it could fail for various reasons, full disk, etc # beside dir fails on installed FC examples from start wb error_message = ( "Failed to create the directory {}. " " For the moment the tmp dir {} is used.".format(specific_path, new_path) ) FreeCAD.Console.PrintError(f"{error_message}\n") return new_path return specific_path # ************************************************************************************************ # other def getBoundBoxOfAllDocumentShapes(doc): """Calculate bounding box containing all objects inside *doc*. :returns: A bounding box containing all objects that have a *Shape* attribute (all Part and PartDesign objects). If the document contains no such objects or no objects at all return ``None``. """ overallboundbox = None # netgen mesh obj has an attribute Shape which is an Document obj, which has no BB # a FemMesh without a Shape could be clipped too # https://forum.freecad.org/viewtopic.php?f=18&t=52920 for o in doc.Objects: FreeCAD.Console.PrintMessage(":\n") # debug only bb = None try: FreeCAD.Console.PrintMessage( f"trying: {o.Label}: getPropertyOfGeometry()\n" ) # debug only bb = o.getPropertyOfGeometry().BoundBox FreeCAD.Console.PrintMessage(f"{bb}\n") # debug only except Exception: FreeCAD.Console.PrintMessage("exception \n") # debug only if bb is None: try: FreeCAD.Console.PrintMessage(f"trying: {o.Label}: FemMesh\n") # debug only bb = o.FemMesh.BoundBox FreeCAD.Console.PrintMessage(f"{bb}\n") # debug only except Exception: FreeCAD.Console.PrintMessage("exception \n") # debug only if bb: if bb.isValid(): if not overallboundbox: overallboundbox = bb else: overallboundbox.add(bb) else: # debug only FreeCAD.Console.PrintMessage("no bb\n") # debug only FreeCAD.Console.PrintMessage("overallBB:" + str(overallboundbox) + "\n") # debug only return overallboundbox def getSelectedFace(selectionex): """Return selected face if exactly one face is selected. :returns: The selected face as a ``Part::TopoShape`` if exactly one face is selected. Otherwise return ``None``. :param selectionex: A list of selection object like the one Gui.Selection.getSelectionEx() returns. """ aFace = None # print(selectionex) if len(selectionex) != 1: FreeCAD.Console.PrintMessage("No or more than one object selected.\n") else: sel = selectionex[0] if len(sel.SubObjects) != 1: FreeCAD.Console.PrintMessage("More than one element selected.\n") else: aFace = sel.SubObjects[0] if aFace.ShapeType != "Face": FreeCAD.Console.PrintMessage("Not a Face selected.\n") else: FreeCAD.Console.PrintMessage(":-)\n") return aFace return aFace def get_refshape_type(fem_doc_object): """Return shape type the constraints references. Determine single shape type of references of *fem_doc_object* which must be a constraint (=have a *References* property). All references must be of the same type which is than returned as a string. A type can be "Vertex", "Edge", "Face" or "Solid". :param fem_doc_object: A constraint object with a *References* property. :returns: A string representing the shape type ("Vertex", "Edge", "Face" or "Solid"). If *fem_doc_object* isn't a constraint ``""`` is returned. :note: Undefined behaviour if the type of the references of one object are not all the same. :note: Undefined behaviour if constraint contains no references (empty list). """ from femtools.geomtools import get_element if hasattr(fem_doc_object, "References") and fem_doc_object.References: first_ref_obj = fem_doc_object.References[0] first_ref_shape = get_element(first_ref_obj[0], first_ref_obj[1][0]) st = first_ref_shape.ShapeType FreeCAD.Console.PrintLog( f"References: {st} in {fem_doc_object.Name}, {fem_doc_object.Label}\n" ) return st else: FreeCAD.Console.PrintLog( f"References: empty in {fem_doc_object.Name}, {fem_doc_object.Label}\n" ) return "" def pydecode(bytestring): """Return *bytestring* as a unicode string""" return bytestring.decode("utf-8") def startProgramInfo(code): """starts a program under Windows minimized, hidden or normal""" if system() == "Windows": info = subprocess.STARTUPINFO() if code == "hide": SW_HIDE = 0 info.wShowWindow = SW_HIDE elif code == "minimize": SW_MINIMIZE = 6 info.wShowWindow = SW_MINIMIZE elif code == "normal": SW_DEFAULT = 10 info.wShowWindow = SW_DEFAULT info.dwFlags = subprocess.STARTF_USESHOWWINDOW return info else: return None def expandParentObject(): """expands parent and selected obj in tree view""" trees = FreeCADGui.getMainWindow().findChildren(QtGui.QTreeWidget) for tree in trees: items = tree.selectedItems() if items == []: continue for item in items: tree.expandItem(item) def getOutputWinColor(type): """ type: 'Error', 'Warning', 'Logging', 'Text' """ col_int = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/OutputWindow").GetUnsigned( "color" + type ) return f"#{col_int:08X}"[:-2]