/
femutils.py
373 lines (311 loc) · 13.4 KB
/
femutils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# ***************************************************************************
# * 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")