374 lines
13 KiB
Python
374 lines
13 KiB
Python
# ***************************************************************************
|
|
# * Copyright (c) 2017 Markus Hovorka <m.hovorka@live.de> *
|
|
# * Copyright (c) 2018 Bernd Hahnebach <bernd@bimstatik.org> *
|
|
# * *
|
|
# * 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"
|
|
__url__ = "https://www.freecadweb.org"
|
|
|
|
|
|
import os
|
|
import sys
|
|
|
|
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
|
|
``<Class>.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 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)
|
|
|
|
|
|
# ************************************************************************************************
|
|
# 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("{}\n".format(error_message))
|
|
if FreeCAD.GuiUp:
|
|
QtGui.QMessageBox.critical(
|
|
FreeCADGui.getMainWindow(),
|
|
"Can't start Solver or Mesh creation besides FC file.",
|
|
error_message
|
|
)
|
|
|
|
# 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("{}\n".format(error_message))
|
|
if FreeCAD.GuiUp:
|
|
QtGui.QMessageBox.critical(
|
|
FreeCADGui.getMainWindow(),
|
|
"Can't start Solver or Mesh creation.",
|
|
error_message
|
|
)
|
|
# 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("{}\n".format(error_message))
|
|
return new_path
|
|
return specific_path
|
|
|
|
|
|
# ************************************************************************************************
|
|
# other
|
|
def get_part_to_mesh(mesh_obj):
|
|
"""
|
|
gmsh mesh object: the Attribute is Part
|
|
netgen mesh object: the Attribute is Shape
|
|
other mesh objects: do not have a Attribute which holds the part to mesh
|
|
"""
|
|
if is_derived_from(mesh_obj, "Fem::FemMeshGmsh"):
|
|
return mesh_obj.Part
|
|
elif is_derived_from(mesh_obj, "Fem::FemMeshShapeNetgenObject"):
|
|
return mesh_obj.Shape
|
|
else:
|
|
return None
|
|
# TODO: the Attributes should be named with the same name
|
|
# should it be Shape or Part?
|
|
# IMHO Part since the Attributes references the document object and not a Shape
|
|
|
|
|
|
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
|
|
# 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.freecadweb.org/viewtopic.php?f=18&t=52920
|
|
for o in doc.Objects:
|
|
|
|
bb = None
|
|
if hasattr(o, "Shape") and hasattr(o.Shape, "BoundBox"):
|
|
try:
|
|
bb = o.Shape.BoundBox
|
|
except Exception:
|
|
pass
|
|
elif hasattr(o, "FemMesh") and hasattr(o.FemMesh, "BoundBox"):
|
|
try:
|
|
bb = o.FemMesh.BoundBox
|
|
except Exception:
|
|
pass
|
|
if bb.isValid():
|
|
if not overalboundbox:
|
|
overalboundbox = bb
|
|
else:
|
|
overalboundbox.add(bb)
|
|
return overalboundbox
|
|
|
|
|
|
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.PrintMessage(
|
|
"References: {} in {}, {}\n". format(st, fem_doc_object.Name, fem_doc_object.Label)
|
|
)
|
|
return st
|
|
else:
|
|
FreeCAD.Console.PrintMessage(
|
|
"References: empty in {}, {}\n". format(fem_doc_object.Name, fem_doc_object.Label)
|
|
)
|
|
return ""
|
|
|
|
|
|
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:
|
|
return bytestring.decode("utf-8")
|