FEM: since result object does not depend on analysis mesh, make analysis tools independed from result object

This commit is contained in:
Bernd Hahnebach
2017-08-30 09:36:57 +02:00
committed by wmayer
parent 01519924d3
commit ea84ed71c0
6 changed files with 179 additions and 161 deletions

View File

@@ -90,6 +90,7 @@ SET(FemScripts_SRCS
FemInputWriterZ88.py
FemMesh2Mesh.py
FemMeshTools.py
FemResultTools.py
FemSelectionObserver.py
FemTools.py
FemToolsCcx.py

View File

@@ -38,6 +38,7 @@ INSTALL(
FemInputWriterZ88.py
FemMesh2Mesh.py
FemMeshTools.py
FemResultTools.py
FemSelectionObserver.py
FemTools.py
FemToolsCcx.py

View File

@@ -0,0 +1,156 @@
# ***************************************************************************
# * *
# * Copyright (c) 2017 - Bernd Hahnebach <bernd@bimstatik.org> *
# * *
# * 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 *
# * *
# ***************************************************************************
__title__ = "Fem Tools for results"
__author__ = "Bernd Hahnebach"
__url__ = "http://www.freecadweb.org"
## \addtogroup FEM
# @{
import FreeCAD
## Removes all result objects from an analysis group
# @param analysis
def purge_results(analysis):
for m in analysis.Member:
if (m.isDerivedFrom('Fem::FemResultObject')):
if m.Mesh and hasattr(m.Mesh, "Proxy") and m.Mesh.Proxy.Type == "FemMeshResult":
analysis.Document.removeObject(m.Mesh.Name)
analysis.Document.removeObject(m.Name)
FreeCAD.ActiveDocument.recompute()
## Resets result mesh deformation
# @param result object
def reset_mesh_deformation(resultobj):
if FreeCAD.GuiUp:
if resultobj.Mesh:
resultobj.Mesh.ViewObject.applyDisplacement(0.0)
## Resets reslut mesh color
# @param result object
def reset_mesh_color(resultobj):
if FreeCAD.GuiUp:
if resultobj.Mesh:
resultobj.Mesh.ViewObject.NodeColor = {}
resultobj.Mesh.ViewObject.ElementColor = {}
node_numbers = resultobj.Mesh.FemMesh.Nodes.keys()
zero_values = [0] * len(node_numbers)
resultobj.Mesh.ViewObject.setNodeColorByScalars(node_numbers, zero_values)
def show_displacement(resultobj, displacement_factor=0.0):
if FreeCAD.GuiUp:
if resultobj.Mesh.ViewObject.Visibility is False:
resultobj.Mesh.ViewObject.Visibility = True
resultobj.Mesh.ViewObject.setNodeDisplacementByVectors(resultobj.NodeNumbers, resultobj.DisplacementVectors)
resultobj.Mesh.ViewObject.applyDisplacement(displacement_factor)
## Sets mesh color using selected type of results (Sabs by default)
# @param self The python object self
# @param result_type Type of FEM result, allowed are:
# - U1, U2, U3 - deformation
# - Uabs - absolute deformation
# - Sabs - Von Mises stress
# @param limit cutoff value. All values over the limit are treated as equal to the limit. Useful for filtering out hot spots.
def show_result(resultobj, result_type="Sabs", limit=None):
if result_type == "None":
reset_mesh_color(resultobj.Mesh)
return
if resultobj:
if result_type == "Sabs":
values = resultobj.StressValues
elif result_type == "Uabs":
values = resultobj.DisplacementLengths
# TODO the result object does have more result types to show, implement them
else:
match = {"U1": 0, "U2": 1, "U3": 2}
d = zip(*resultobj.DisplacementVectors)
values = list(d[match[result_type]])
show_color_by_scalar_with_cutoff(resultobj, values, limit)
else:
print('Error, No result object given.')
## Sets mesh color using list of values. Internally used by show_result function.
# @param self The python object self
# @param values list of values
# @param limit cutoff value. All values over the limit are treated as equel to the limit. Useful for filtering out hot spots.
def show_color_by_scalar_with_cutoff(resultobj, values, limit=None):
if limit:
filtered_values = []
for v in values:
if v > limit:
filtered_values.append(limit)
else:
filtered_values.append(v)
else:
filtered_values = values
if FreeCAD.GuiUp:
if resultobj.Mesh.ViewObject.Visibility is False:
resultobj.Mesh.ViewObject.Visibility = True
resultobj.Mesh.ViewObject.setNodeColorByScalars(resultobj.NodeNumbers, filtered_values)
## Returns minimum, average and maximum value for provided result type
# @param result object
# @param result_type Type of FEM result, allowed are:
# - U1, U2, U3 - deformation
# - Uabs - absolute deformation
# - Sabs - Von Mises stress
# - Prin1 - Principal stress 1
# - Prin2 - Principal stress 2
# - Prin3 - Principal stress 3
# - MaxSear - maximum shear stress
# - Peeq - peeq strain
# - Temp - Temperature
# - MFlow - MassFlowRate
# - NPress - NetworkPressure
# - None - always return (0.0, 0.0, 0.0)
def get_stats(resultobj, result_type):
m = resultobj
stats = (0.0, 0.0, 0.0)
match_table = {
"U1": (m.Stats[0], m.Stats[1], m.Stats[2]),
"U2": (m.Stats[3], m.Stats[4], m.Stats[5]),
"U3": (m.Stats[6], m.Stats[7], m.Stats[8]),
"Uabs": (m.Stats[9], m.Stats[10], m.Stats[11]),
"Sabs": (m.Stats[12], m.Stats[13], m.Stats[14]),
"MaxPrin": (m.Stats[15], m.Stats[16], m.Stats[17]),
"MidPrin": (m.Stats[18], m.Stats[19], m.Stats[20]),
"MinPrin": (m.Stats[21], m.Stats[22], m.Stats[23]),
"MaxShear": (m.Stats[24], m.Stats[25], m.Stats[26]),
"Peeq": (m.Stats[27], m.Stats[28], m.Stats[29]),
"Temp": (m.Stats[30], m.Stats[31], m.Stats[32]),
"MFlow": (m.Stats[33], m.Stats[34], m.Stats[35]),
"NPress": (m.Stats[36], m.Stats[37], m.Stats[38]),
"None": (0.0, 0.0, 0.0)
}
stats = match_table[result_type]
return stats
## @}

View File

@@ -56,8 +56,6 @@ class FemTools(QtCore.QRunnable, QtCore.QObject):
self.solver = None
if self.analysis:
self.update_objects()
self.results_present = False
self.result_object = None
else:
raise Exception('FEM: No active analysis found!')
@@ -69,27 +67,8 @@ class FemTools(QtCore.QRunnable, QtCore.QObject):
if m.Mesh and hasattr(m.Mesh, "Proxy") and m.Mesh.Proxy.Type == "FemMeshResult":
self.analysis.Document.removeObject(m.Mesh.Name)
self.analysis.Document.removeObject(m.Name)
self.results_present = False
FreeCAD.ActiveDocument.recompute()
## Resets mesh deformation
# @param self The python object self
def reset_mesh_deformation(self):
if FreeCAD.GuiUp:
if self.mesh:
self.mesh.ViewObject.applyDisplacement(0.0)
## Resets mesh color
# @param self The python object self
def reset_mesh_color(self):
if FreeCAD.GuiUp:
if self.mesh:
self.mesh.ViewObject.NodeColor = {}
self.mesh.ViewObject.ElementColor = {}
node_numbers = self.mesh.FemMesh.Nodes.keys()
zero_values = [0] * len(node_numbers)
self.mesh.ViewObject.setNodeColorByScalars(node_numbers, zero_values)
## Resets mesh color, deformation and removes all result objects if preferences to keep them is not set
# @param self The python object self
def reset_mesh_purge_results_checked(self):
@@ -97,65 +76,11 @@ class FemTools(QtCore.QRunnable, QtCore.QObject):
keep_results_on_rerun = self.fem_prefs.GetBool("KeepResultsOnReRun", False)
if not keep_results_on_rerun:
self.purge_results()
if FreeCAD.GuiUp:
self.reset_mesh_color()
self.reset_mesh_deformation()
## Resets mesh color, deformation and removes all result objects
# @param self The python object self
def reset_all(self):
self.purge_results()
if FreeCAD.GuiUp:
self.reset_mesh_color()
self.reset_mesh_deformation()
## Sets mesh color using selected type of results (Sabs by default)
# @param self The python object self
# @param result_type Type of FEM result, allowed are:
# - U1, U2, U3 - deformation
# - Uabs - absolute deformation
# - Sabs - Von Mises stress
# @param limit cutoff value. All values over the limit are treated as equal to the limit. Useful for filtering out hot spots.
def show_result(self, result_type="Sabs", limit=None):
self.update_objects()
if result_type == "None":
self.reset_mesh_color()
return
if self.result_object:
if FreeCAD.GuiUp:
if self.result_object.Mesh.ViewObject.Visibility is False:
self.result_object.Mesh.ViewObject.Visibility = True
if result_type == "Sabs":
values = self.result_object.StressValues
elif result_type == "Uabs":
values = self.result_object.DisplacementLengths
else:
match = {"U1": 0, "U2": 1, "U3": 2}
d = zip(*self.result_object.DisplacementVectors)
values = list(d[match[result_type]])
self.show_color_by_scalar_with_cutoff(values, limit)
## Sets mesh color using list of values. Internally used by show_result function.
# @param self The python object self
# @param values list of values
# @param limit cutoff value. All values over the limit are treated as equel to the limit. Useful for filtering out hot spots.
def show_color_by_scalar_with_cutoff(self, values, limit=None):
if limit:
filtered_values = []
for v in values:
if v > limit:
filtered_values.append(limit)
else:
filtered_values.append(v)
else:
filtered_values = values
self.mesh.ViewObject.setNodeColorByScalars(self.result_object.NodeNumbers, filtered_values)
def show_displacement(self, displacement_factor=0.0):
if FreeCAD.GuiUp:
self.mesh.ViewObject.setNodeDisplacementByVectors(self.result_object.NodeNumbers,
self.result_object.DisplacementVectors)
self.mesh.ViewObject.applyDisplacement(displacement_factor)
def update_objects(self):
# [{'Object':materials_linear}, {}, ...]
@@ -597,70 +522,6 @@ class FemTools(QtCore.QRunnable, QtCore.QObject):
# Update inp file name
self.set_inp_file_name()
## Set the analysis result object
# if no result object is provided, check if the analysis has result objects
# if the analysis has exact one result object use this result object
# @param self The python object self
# @param result object name
def use_results(self, results_name=None):
self.result_object = None
if results_name is not None:
for m in self.analysis.Member:
if m.isDerivedFrom("Fem::FemResultObject") and m.Name == results_name:
self.result_object = m
break
if not self.result_object:
raise Exception("{} doesn't exist".format(results_name))
else:
has_results = False
for m in self.analysis.Member:
if m.isDerivedFrom("Fem::FemResultObject"):
self.result_object = m
if has_results is True:
self.result_object = None
raise Exception("No result name was provided, but more than one result objects in the analysis.")
has_results = True
if not self.result_object:
raise Exception("No result object found in the analysis")
## Returns minimum, average and maximum value for provided result type
# @param self The python object self
# @param result_type Type of FEM result, allowed are:
# - U1, U2, U3 - deformation
# - Uabs - absolute deformation
# - Sabs - Von Mises stress
# - Prin1 - Principal stress 1
# - Prin2 - Principal stress 2
# - Prin3 - Principal stress 3
# - MaxSear - maximum shear stress
# - Peeq - peeq strain
# - Temp - Temperature
# - MFlow - MassFlowRate
# - NPress - NetworkPressure
# - None - always return (0.0, 0.0, 0.0)
def get_stats(self, result_type, results_name=None):
self.use_results(results_name)
m = self.result_object
stats = (0.0, 0.0, 0.0)
match_table = {
"U1": (m.Stats[0], m.Stats[1], m.Stats[2]),
"U2": (m.Stats[3], m.Stats[4], m.Stats[5]),
"U3": (m.Stats[6], m.Stats[7], m.Stats[8]),
"Uabs": (m.Stats[9], m.Stats[10], m.Stats[11]),
"Sabs": (m.Stats[12], m.Stats[13], m.Stats[14]),
"MaxPrin": (m.Stats[15], m.Stats[16], m.Stats[17]),
"MidPrin": (m.Stats[18], m.Stats[19], m.Stats[20]),
"MinPrin": (m.Stats[21], m.Stats[22], m.Stats[23]),
"MaxShear": (m.Stats[24], m.Stats[25], m.Stats[26]),
"Peeq": (m.Stats[27], m.Stats[28], m.Stats[29]),
"Temp": (m.Stats[30], m.Stats[31], m.Stats[32]),
"MFlow": (m.Stats[33], m.Stats[34], m.Stats[35]),
"NPress": (m.Stats[36], m.Stats[37], m.Stats[38]),
"None": (0.0, 0.0, 0.0)
}
stats = match_table[result_type]
return stats
# helper
def get_refshape_type(fem_doc_object):

View File

@@ -26,6 +26,7 @@
import Fem
import FemToolsCcx
import FemResultTools
import FreeCAD
import ObjectsFem
import tempfile
@@ -507,7 +508,7 @@ class FemCcxAnalysisTest(unittest.TestCase):
self.assertTrue(fea.results_present, "Cannot read results from {}.frd frd file".format(fea.base_name))
fcc_print('Reading stats from result object for static analysis...')
ret = compare_stats(fea, static_expected_values)
ret = compare_stats(fea, static_expected_values, 'CalculiX_static_results')
self.assertFalse(ret, "Invalid results read from .frd file")
fcc_print('Save FreeCAD file for static analysis to {}...'.format(static_save_fc_file))
@@ -557,7 +558,7 @@ class FemCcxAnalysisTest(unittest.TestCase):
self.assertTrue(fea.results_present, "Cannot read results from {}.frd frd file".format(fea.base_name))
fcc_print('Reading stats from result object for frequency analysis...')
ret = compare_stats(fea, frequency_expected_values)
ret = compare_stats(fea, frequency_expected_values, 'CalculiX_frequency_mode_1_results')
self.assertFalse(ret, "Invalid results read from .frd file")
fcc_print('Save FreeCAD file for frequency analysis to {}...'.format(frequency_save_fc_file))
@@ -685,7 +686,7 @@ class FemCcxAnalysisTest(unittest.TestCase):
self.assertTrue(fea.results_present, "Cannot read results from {}.frd frd file".format(fea.base_name))
fcc_print('Reading stats from result object for thermomech analysis...')
ret = compare_stats(fea, thermomech_expected_values)
ret = compare_stats(fea, thermomech_expected_values, 'CalculiX_thermomech_results')
self.assertFalse(ret, "Invalid results read from .frd file")
fcc_print('Save FreeCAD file for thermomech analysis to {}...'.format(thermomech_save_fc_file))
@@ -1013,9 +1014,10 @@ def compare_stats(fea, stat_file=None, loc_stat_types=None, res_obj_name=None):
stats = []
for s in loc_stat_types:
if res_obj_name:
statval = fea.get_stats(s, res_obj_name)
statval = FemResultTools.get_stats(FreeCAD.ActiveDocument.getObject(res_obj_name), s)
else:
statval = fea.get_stats(s)
print('No result object name given')
return False
stats.append("{0}: ({1:.14g}, {2:.14g}, {3:.14g})\n".format(s, statval[0], statval[1], statval[2]))
if sf_content != stats:
fcc_print("Expected stats from {}".format(stat_file))
@@ -1048,7 +1050,7 @@ def runTestFem():
def create_test_results():
# run FEM unit tests
print("run FEM unit tests")
runTestFem()
import shutil
@@ -1059,20 +1061,19 @@ def create_test_results():
FemGui.setActiveAnalysis(FreeCAD.ActiveDocument.Analysis)
fea = FemToolsCcx.FemToolsCcx()
# static
print("create static result files")
fea.reset_all()
fea.run()
fea.load_results()
stats_static = [] # we only have one result object so we are fine
stats_static = []
for s in stat_types:
statval = fea.get_stats(s)
statval = FemResultTools.get_stats(FreeCAD.ActiveDocument.getObject('CalculiX_static_results'), s)
stats_static.append("{0}: ({1:.14g}, {2:.14g}, {3:.14g})\n".format(s, statval[0], statval[1], statval[2]))
static_expected_values_file = static_analysis_dir + 'cube_static_expected_values'
f = open(static_expected_values_file, 'w')
for s in stats_static:
f.write(s)
f.close()
# could be added in FemToolsCcx to the self object as an Attribut
frd_result_file = os.path.splitext(fea.inp_file_name)[0] + '.frd'
dat_result_file = os.path.splitext(fea.inp_file_name)[0] + '.dat'
frd_static_test_result_file = static_analysis_dir + 'cube_static.frd'
@@ -1080,15 +1081,15 @@ def create_test_results():
shutil.copyfile(frd_result_file, frd_static_test_result_file)
shutil.copyfile(dat_result_file, dat_static_test_result_file)
# frequency
print("create frequency result files")
fea.reset_all()
fea.set_analysis_type('frequency')
fea.solver.EigenmodesCount = 1 # we should only have one result object
fea.run()
fea.load_results()
stats_frequency = [] # since we set eigenmodeno. we only have one result object so we are fine
stats_frequency = []
for s in stat_types:
statval = fea.get_stats(s)
statval = FemResultTools.get_stats(FreeCAD.ActiveDocument.getObject('CalculiX_static_mode_1_results'), s) # FIXME for some reason result obj name has static
stats_frequency.append("{0}: ({1:.14g}, {2:.14g}, {3:.14g})\n".format(s, statval[0], statval[1], statval[2]))
frequency_expected_values_file = frequency_analysis_dir + 'cube_frequency_expected_values'
f = open(frequency_expected_values_file, 'w')
@@ -1100,23 +1101,22 @@ def create_test_results():
shutil.copyfile(frd_result_file, frd_frequency_test_result_file)
shutil.copyfile(dat_result_file, dat_frequency_test_result_file)
# thermomech
print("create thermomech result files")
FreeCAD.open(thermomech_save_fc_file)
FemGui.setActiveAnalysis(FreeCAD.ActiveDocument.Analysis)
fea = FemToolsCcx.FemToolsCcx()
fea.reset_all()
fea.run()
fea.load_results()
stats_thermomech = [] # we only have one result object so we are fine
stats_thermomech = []
for s in stat_types:
statval = fea.get_stats(s)
statval = FemResultTools.get_stats(FreeCAD.ActiveDocument.getObject('CalculiX_thermomech_results'), s)
stats_thermomech.append("{0}: ({1:.14g}, {2:.14g}, {3:.14g})\n".format(s, statval[0], statval[1], statval[2]))
thermomech_expected_values_file = thermomech_analysis_dir + 'spine_thermomech_expected_values'
f = open(thermomech_expected_values_file, 'w')
for s in stats_thermomech:
f.write(s)
f.close()
# could be added in FemToolsCcx to the self object as an Attribut
frd_result_file = os.path.splitext(fea.inp_file_name)[0] + '.frd'
dat_result_file = os.path.splitext(fea.inp_file_name)[0] + '.dat'
frd_thermomech_test_result_file = thermomech_analysis_dir + 'spine_thermomech.frd'
@@ -1125,23 +1125,22 @@ def create_test_results():
shutil.copyfile(dat_result_file, dat_thermomech_test_result_file)
print('Results copied to the appropriate FEM test dirs in: ' + temp_dir)
# Flow1D
print("create Flow1D result files")
FreeCAD.open(Flow1D_thermomech_save_fc_file)
FemGui.setActiveAnalysis(FreeCAD.ActiveDocument.Analysis)
fea = FemToolsCcx.FemToolsCcx()
fea.reset_all()
fea.run()
fea.load_results()
stats_flow1D = [] # be carefule if we have more than one result object !
stats_flow1D = []
for s in stat_types:
statval = fea.get_stats(s, 'CalculiX_thermomech_time_1_0_results')
statval = FemResultTools.get_stats(FreeCAD.ActiveDocument.getObject('CalculiX_thermomech_time_1_0_results'), s)
stats_flow1D.append("{0}: ({1:.14g}, {2:.14g}, {3:.14g})\n".format(s, statval[0], statval[1], statval[2]))
Flow1D_thermomech_expected_values_file = Flow1D_thermomech_analysis_dir + 'Flow1D_thermomech_expected_values'
f = open(Flow1D_thermomech_expected_values_file, 'w')
for s in stats_flow1D:
f.write(s)
f.close()
# could be added in FemToolsCcx to the self object as an Attribut
frd_result_file = os.path.splitext(fea.inp_file_name)[0] + '.frd'
dat_result_file = os.path.splitext(fea.inp_file_name)[0] + '.dat'
frd_Flow1D_thermomech_test_result_file = Flow1D_thermomech_analysis_dir + 'Flow1D_thermomech.frd'

View File

@@ -395,7 +395,7 @@ def fill_femresult_mechanical(results, result_set, span):
temp_min, temp_avg, temp_max,
mflow_min, mflow_avg, mflow_max,
npress_min, npress_avg, npress_max]
# do not forget to adapt the def get_stats in FemTools and _TaskPanelFemResultShow module as well as the TestFem module
# do not forget to adapt the def get_stats in FemResultTools and _TaskPanelFemResultShow module as well as the TestFem module
# stat_types = ["U1", "U2", "U3", "Uabs", "Sabs", "MaxPrin", "MidPrin", "MinPrin", "MaxShear", "Peeq", "Temp", "MFlow", "NPress"]
# TODO a dictionary would be far robust than a list, but needs adapten in VTK too because of VTK result import