diff --git a/src/Mod/Fem/femtools/femutils.py b/src/Mod/Fem/femtools/femutils.py index c1304990c0..f55afb8dc2 100644 --- a/src/Mod/Fem/femtools/femutils.py +++ b/src/Mod/Fem/femtools/femutils.py @@ -20,6 +20,13 @@ # * USA * # * * # *************************************************************************** +""" Collection of functions for the Fem module. + +This module contains function for managing a analysis and all the differnet +types of objects it contains, helper for executing a simulation, function for +extracting relevant parts of geometry and a few unrelated function useful at +various places in the Fem module. +""" __title__ = "FEM Utilities" @@ -38,8 +45,23 @@ if FreeCAD.GuiUp: from PySide import QtGui -# analysis and its members def createObject(doc, name, proxy, viewProxy): + """ 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: @@ -48,6 +70,14 @@ def createObject(doc, name, proxy, viewProxy): def findAnalysisOfMember(member): + """ Find Analysis the *member* belongs to. + + :param member: a document object + + :returns: + If a analysis that contains *member* can be found a reference is returned. + If no such object exists in the document of *member*, ``None`` is returned. + """ if member is None: raise ValueError("Member must not be None") for obj in member.Document.Objects: @@ -69,6 +99,22 @@ def _searchGroups(member, objs): def get_member(analysis, t): + """ Return list of all members of *analysis* of type *t*. + + Search *analysis* for members of type *t*. This method checks the custom + python typesytem (BaseType class property) used by the Fem module if + possible. If the object doens't use the python typesystem the usual + isDerivedFrom from the C++ dynamic type system is used. + + :param analysis: only objects part of this analysis are considered + :param t: only objects of this type are returned + + :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.freecadweb.org/viewtopic.php?f=10&t=32625 + """ if analysis is None: raise ValueError("Analysis must not be None") matching = [] @@ -81,12 +127,58 @@ def get_member(analysis, t): def get_single_member(analysis, t): + """ Return one object of type *t* and part of *analysis*. + + Search *analysis* for members of type *t* and return the first one that's + found. This method checks the custom python typesytem (BaseType class + property) used by the Fem module if possible. If the object doesn't use the + python typesystem the usual isDerivedFrom from the C++ dynamic type system + is used. + + :param analysis: only objects part of this analysis are considered + :param t: only a object of this type is returned + + :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.freecadweb.org/viewtopic.php?f=10&t=32625 + """ objs = get_member(analysis, t) return objs[0] if objs else None -# collect analysis members used in CalculiX and Z88 def get_several_member(analysis, t): + """ Get members and pack them for Calculix/Z88. + + Collect members by calling :py:func:`get_member` and pack them into a + data structure that can be consumed by calculix and Z88 solver modules. + + :param analysis: see :py:func:`get_member` + :param t: see :py:func:`get_member` + + :returns: + A list containing one dict per member. Each dict has two entries: + ``"Object"`` and ``"RefShapeType"``. ``dict["Object"]`` contains the + member document object. ``dict["RefShapeType"]`` contains the shape type + of the *References* property of the member (used by constraints) as a + string ("Vertex", "Edge", "Face" or "Solid"). If the member doesn't have a + *References* property ``dict["RefShapeType"]`` is the empty string ``""``. + + :note: + Undefined behaviour if one of the members has a *References* property + which is empty. + + :note: + Undefined behaviour if the type of the references of one object arn't all + the same. + + :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.freecadweb.org/viewtopic.php?f=10&t=32625 + """ # if no member is found, an empty list is returned objs = get_member(analysis, t) members = [] @@ -99,6 +191,14 @@ def get_several_member(analysis, t): def get_mesh_to_solve(analysis): + """ Find one and only mesh object of *analysis*. + + :returns: + A tuple ``(object, message)``. If and only if the analysis contains + exactely one mesh object the first value of the tuple is the mesh document + object. Otherwise the first value is ``None`` and the second value is a + error message indicating what went wrong. + """ mesh_to_solve = None for m in analysis.Group: if m.isDerivedFrom("Fem::FemMeshObject") and not is_of_type(m, "Fem::FemMeshResult"): @@ -114,27 +214,46 @@ def get_mesh_to_solve(analysis): # typeID and object type defs def type_of_obj(obj): - """returns objects TypeId (C++ objects) or Proxy.Type (Python objects)""" + """ 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): - """returns True if an object is of - a given TypeId (C++ objects) or Proxy.Type (Python Features)""" - # only returns true if the exact TypeId is given. - # For FeaturPythons the Proxy.Type has to be given. - # Keep in mind the TypeId for them is the TypeId from the C++ father class + """ 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 + exactely: 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): - """returns True if an object or its inheritance chain is of a - given TypeId (C++ objects) or Proxy.Type (Python objects)""" - # returns true for all FEM objects if given t == "App::DocumentObject" - # since this is a father of the given object - # see https://forum.freecadweb.org/viewtopic.php?f=10&t=32625 + """ Check if *obj* is derived from *t* honoring Fems typesytem. + + 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.freecadweb.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) @@ -143,8 +262,16 @@ def is_derived_from(obj, t): # ************************************************************************************************ # working dir def get_pref_working_dir(solver_obj): - # _dirTypes from run are not used - # be aware beside could get an error if the document has not been saved + """ Return working directory for solver honoring user settings. + + :throws femsolver.run.MustSaveError: + If user setting is set to BESIDE and the document isn't saved. + + :note: + Not working correctely for most cases because this circumvents directory + caching of the solver framework. For solver use getMachine from run.py + instead. + """ dir_setting = settings.get_dir_setting() if dir_setting == settings.TEMPORARY: setting_working_dir = get_temp_dir(solver_obj) @@ -255,6 +382,13 @@ def get_part_to_mesh(mesh_obj): 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``. + """ overalboundbox = None for o in doc.Objects: # netgen mesh obj has an attribute Shape which is an Document obj, which has no BB @@ -271,6 +405,16 @@ def getBoundBoxOfAllDocumentShapes(doc): def getSelectedFace(selectionex): + """ Return selected face if exactly one face is selected. + + :returns: + The selcted 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: @@ -290,14 +434,27 @@ def getSelectedFace(selectionex): def get_refshape_type(fem_doc_object): - # returns the reference shape type - # for force object: - # in GUI defined frc_obj all frc_obj have at least one ref_shape - # and ref_shape have all the same shape type - # for material object: - # in GUI defined material_obj could have no RefShape and RefShapes could be different type - # we're going to need the RefShapes to be the same type inside one fem_doc_object - # TODO: check if all RefShapes inside the object really have the same type + """ 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 contraint ``""`` is returned. + + :note: + Undefined behaviour if the type of the references of one object arn't all + the same. + + :note: + Undefined behaviour if constraint contains no references (empty list). + """ import femmesh.meshtools as FemMeshTools if hasattr(fem_doc_object, "References") and fem_doc_object.References: first_ref_obj = fem_doc_object.References[0] @@ -315,6 +472,12 @@ def get_refshape_type(fem_doc_object): def pydecode(bytestring): + """ Return *bytestring* as a unicode string for python 2 and 3. + + For python 2 *bytestring* is converted to a string of type ``unicode``. For + python 3 it is returned as is because it uses unicode for it's ``str`` type + already. + """ if sys.version_info.major < 3: return bytestring else: