From c74e2a6378aaa64f7615482ef5335e26df6cda1d Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Wed, 3 Jun 2020 23:48:34 +0200 Subject: [PATCH] FEM: move task panel in separate module, result mechanical --- src/Mod/Fem/CMakeLists.txt | 1 + .../femtaskpanels/task_result_mechanical.py | 676 ++++++++++++++++++ .../femviewprovider/view_result_mechanical.py | 644 +---------------- 3 files changed, 679 insertions(+), 642 deletions(-) create mode 100644 src/Mod/Fem/femtaskpanels/task_result_mechanical.py diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 461f65c18e..e75e19427e 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -349,6 +349,7 @@ SET(FemGuiObjects_SRCS SET(FemGuiTaskPanels_SRCS femtaskpanels/__init__.py + femtaskpanels/task_result_mechanical.py ) SET(FemGuiTests_SRCS diff --git a/src/Mod/Fem/femtaskpanels/task_result_mechanical.py b/src/Mod/Fem/femtaskpanels/task_result_mechanical.py new file mode 100644 index 0000000000..2b645e479a --- /dev/null +++ b/src/Mod/Fem/femtaskpanels/task_result_mechanical.py @@ -0,0 +1,676 @@ +# *************************************************************************** +# * Copyright (c) 2015 Qingfeng Xia * +# * Copyright (c) 2016 Bernd Hahnebach * +# * * +# * 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 * +# * * +# *************************************************************************** + +__title__ = "FreeCAD result mechanical task panel" +__author__ = "Qingfeng Xia, Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## @package view_result_mechanical +# \ingroup FEM +# \brief task panel for mechanical ResultObjectPython + +import matplotlib.pyplot as plt +import numpy as np + +from PySide import QtCore +from PySide import QtGui +from PySide.QtCore import Qt +from PySide.QtGui import QApplication + +import FreeCAD +import FreeCADGui + +import femresult.resulttools as resulttools + + +class _TaskPanel: + """ + The task panel for the post-processing + """ + + def __init__(self, obj): + self.result_obj = obj + self.mesh_obj = self.result_obj.Mesh + # task panel should be started by use of setEdit of view provider + # in view provider checks: Mesh, active analysis and + # if Mesh and result are in active analysis + # activate the result mesh object + self.mesh_obj.ViewObject.show() + + ui_path = FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/" + self.result_widget = FreeCADGui.PySideUic.loadUi(ui_path + "ResultShow.ui") + self.info_widget = FreeCADGui.PySideUic.loadUi(ui_path + "ResultHints.ui") + self.form = [self.result_widget, self.info_widget] + + self.fem_prefs = FreeCAD.ParamGet( + "User parameter:BaseApp/Preferences/Mod/Fem/General" + ) + self.restore_result_settings_in_dialog = self.fem_prefs.GetBool( + "RestoreResultDialog", True + ) + + # Connect Signals and Slots + # result type radio buttons + # TODO: move to combo box, to be independent from result types and result types count + QtCore.QObject.connect( + self.result_widget.rb_none, QtCore.SIGNAL("toggled(bool)"), + self.none_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_abs_displacement, + QtCore.SIGNAL("toggled(bool)"), + self.abs_displacement_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_x_displacement, + QtCore.SIGNAL("toggled(bool)"), + self.x_displacement_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_y_displacement, + QtCore.SIGNAL("toggled(bool)"), + self.y_displacement_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_z_displacement, + QtCore.SIGNAL("toggled(bool)"), + self.z_displacement_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_temperature, + QtCore.SIGNAL("toggled(bool)"), + self.temperature_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_vm_stress, + QtCore.SIGNAL("toggled(bool)"), + self.vm_stress_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_maxprin, + QtCore.SIGNAL("toggled(bool)"), + self.max_prin_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_minprin, + QtCore.SIGNAL("toggled(bool)"), + self.min_prin_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_max_shear_stress, + QtCore.SIGNAL("toggled(bool)"), + self.max_shear_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_massflowrate, + QtCore.SIGNAL("toggled(bool)"), + self.massflowrate_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_networkpressure, + QtCore.SIGNAL("toggled(bool)"), + self.networkpressure_selected + ) + QtCore.QObject.connect( + self.result_widget.rb_peeq, + QtCore.SIGNAL("toggled(bool)"), + self.peeq_selected + ) + + # stats + self.result_widget.show_histogram.clicked.connect( + self.show_histogram_clicked + ) + + # displacement + QtCore.QObject.connect( + self.result_widget.cb_show_displacement, + QtCore.SIGNAL("clicked(bool)"), + self.show_displacement + ) + QtCore.QObject.connect( + self.result_widget.hsb_displacement_factor, + QtCore.SIGNAL("valueChanged(int)"), + self.hsb_disp_factor_changed + ) + + self.result_widget.sb_displacement_factor.valueChanged.connect( + self.sb_disp_factor_changed + ) + self.result_widget.sb_displacement_factor_max.valueChanged.connect( + self.sb_disp_factor_max_changed + ) + + # user defined equation + self.result_widget.user_def_eq.textChanged.connect( + self.user_defined_text + ) + QtCore.QObject.connect( + self.result_widget.calculate, + QtCore.SIGNAL("clicked()"), + self.calculate + ) + + self.update() + if self.restore_result_settings_in_dialog: + self.restore_result_dialog() + else: + self.restore_initial_result_dialog() + # initialize scale factor for show displacement + scale_factor = get_displacement_scale_factor(self.result_obj) + self.result_widget.sb_displacement_factor_max.setValue(10. * scale_factor) + self.result_widget.sb_displacement_factor.setValue(scale_factor) + + def restore_result_dialog(self): + try: + rt = FreeCAD.FEM_dialog["results_type"] + if rt == "None": + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + elif rt == "Uabs": + self.result_widget.rb_abs_displacement.setChecked(True) + self.abs_displacement_selected(True) + elif rt == "U1": + self.result_widget.rb_x_displacement.setChecked(True) + self.x_displacement_selected(True) + elif rt == "U2": + self.result_widget.rb_y_displacement.setChecked(True) + self.y_displacement_selected(True) + elif rt == "U3": + self.result_widget.rb_z_displacement.setChecked(True) + self.z_displacement_selected(True) + elif rt == "Temp": + self.result_widget.rb_temperature.setChecked(True) + self.temperature_selected(True) + elif rt == "Sabs": + self.result_widget.rb_vm_stress.setChecked(True) + self.vm_stress_selected(True) + elif rt == "MaxPrin": + self.result_widget.rb_maxprin.setChecked(True) + self.max_prin_selected(True) + elif rt == "MinPrin": + self.result_widget.rb_minprin.setChecked(True) + self.min_prin_selected(True) + elif rt == "MaxShear": + self.result_widget.rb_max_shear_stress.setChecked(True) + self.max_shear_selected(True) + elif rt == "MFlow": + self.result_widget.rb_massflowrate.setChecked(True) + self.massflowrate_selected(True) + elif rt == "NPress": + self.result_widget.rb_networkpressure.setChecked(True) + self.networkpressure_selected(True) + elif rt == "Peeq": + self.result_widget.rb_peeq.setChecked(True) + self.peeq_selected(True) + + sd = FreeCAD.FEM_dialog["show_disp"] + self.result_widget.cb_show_displacement.setChecked(sd) + self.show_displacement(sd) + + df = FreeCAD.FEM_dialog["disp_factor"] + dfm = FreeCAD.FEM_dialog["disp_factor_max"] + # self.result_widget.hsb_displacement_factor.setMaximum(dfm) + # self.result_widget.hsb_displacement_factor.setValue(df) + self.result_widget.sb_displacement_factor_max.setValue(dfm) + self.result_widget.sb_displacement_factor.setValue(df) + except: + self.restore_initial_result_dialog() + + def restore_initial_result_dialog(self): + # initialize FreeCAD.FEM_dialog and set standard values + # the FEM result mechanical task panel restore values + # are saved in a dictionary which is an attribute of FreeCAD + # the name is FEM_dialog + # in python console after result task panel has been opened once + # FreeCAD.FEM_dialog or FreeCAD.__dir__() + # This is not smart at all IMHO (Bernd) + # It was added with commit 3a7772d + # https://github.com/FreeCAD/FreeCAD/commit/3a7772d + FreeCAD.FEM_dialog = { + "results_type": "None", + "show_disp": False, + "disp_factor": 0., + "disp_factor_max": 100. + } + self.result_widget.sb_displacement_factor_max.setValue(100.) # init non standard values + + def getStandardButtons(self): + return int(QtGui.QDialogButtonBox.Close) + + def get_result_stats(self, type_name): + return resulttools.get_stats(self.result_obj, type_name) + + def none_selected(self, state): + FreeCAD.FEM_dialog["results_type"] = "None" + self.set_result_stats("mm", 0.0, 0.0) + self.reset_mesh_color() + if len(plt.get_fignums()) > 0: + plt.close() + + # if an analysis has different result types and one has + # stress and the other not the restore result dialog + # could trigger stress selected for a result object + # which has not stress + # see https://forum.freecadweb.org/viewtopic.php?f=18&t=39162 + # check if the results len is not 0 on any selected method + + def abs_displacement_selected(self, state): + if len(self.result_obj.DisplacementLengths) > 0: + self.result_selected("Uabs", self.result_obj.DisplacementLengths, "mm") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def x_displacement_selected(self, state): + if len(self.result_obj.DisplacementVectors) > 0: + res_disp_u1 = self.get_scalar_disp_list( + self.result_obj.DisplacementVectors, 0 + ) + self.result_selected("U1", res_disp_u1, "mm") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def y_displacement_selected(self, state): + if len(self.result_obj.DisplacementVectors) > 0: + res_disp_u2 = self.get_scalar_disp_list( + self.result_obj.DisplacementVectors, 1 + ) + self.result_selected("U2", res_disp_u2, "mm") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def z_displacement_selected(self, state): + if len(self.result_obj.DisplacementVectors) > 0: + res_disp_u3 = self.get_scalar_disp_list( + self.result_obj.DisplacementVectors, 2 + ) + self.result_selected("U3", res_disp_u3, "mm") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def vm_stress_selected(self, state): + if len(self.result_obj.vonMises) > 0: + self.result_selected("Sabs", self.result_obj.vonMises, "MPa") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def max_shear_selected(self, state): + if len(self.result_obj.MaxShear) > 0: + self.result_selected("MaxShear", self.result_obj.MaxShear, "MPa") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def max_prin_selected(self, state): + if len(self.result_obj.PrincipalMax) > 0: + self.result_selected("MaxPrin", self.result_obj.PrincipalMax, "MPa") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def temperature_selected(self, state): + if len(self.result_obj.Temperature) > 0: + self.result_selected("Temp", self.result_obj.Temperature, "K") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def massflowrate_selected(self, state): + if len(self.result_obj.MassFlowRate) > 0: + self.result_selected("MFlow", self.result_obj.MassFlowRate, "kg/s") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def networkpressure_selected(self, state): + if len(self.result_obj.NetworkPressure) > 0: + self.result_selected("NPress", self.result_obj.NetworkPressure, "MPa") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def min_prin_selected(self, state): + if len(self.result_obj.PrincipalMin) > 0: + self.result_selected("MinPrin", self.result_obj.PrincipalMin, "MPa") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def peeq_selected(self, state): + if len(self.result_obj.Peeq) > 0: + self.result_selected("Peeq", self.result_obj.Peeq, "") + else: + self.result_widget.rb_none.setChecked(True) + self.none_selected(True) + + def show_histogram_clicked(self): + if len(plt.get_fignums()) > 0: + plt.show() + else: + QtGui.QMessageBox.information( + None, + self.result_obj.Label + " - Information", + "No histogram available.\nPlease select a result type first." + ) + + def user_defined_text(self, equation): + FreeCAD.FEM_dialog["results_type"] = "user" + self.result_widget.user_def_eq.toPlainText() + + def calculate(self): + + # Convert existing result values to numpy array + # scalars + P1 = np.array(self.result_obj.PrincipalMax) + P2 = np.array(self.result_obj.PrincipalMed) + P3 = np.array(self.result_obj.PrincipalMin) + vM = np.array(self.result_obj.vonMises) + Peeq = np.array(self.result_obj.Peeq) + T = np.array(self.result_obj.Temperature) + MF = np.array(self.result_obj.MassFlowRate) + NP = np.array(self.result_obj.NetworkPressure) + sxx = np.array(self.result_obj.NodeStressXX) + syy = np.array(self.result_obj.NodeStressYY) + szz = np.array(self.result_obj.NodeStressZZ) + sxy = np.array(self.result_obj.NodeStressXY) + sxz = np.array(self.result_obj.NodeStressXZ) + syz = np.array(self.result_obj.NodeStressYZ) + exx = np.array(self.result_obj.NodeStrainXX) + eyy = np.array(self.result_obj.NodeStrainYY) + ezz = np.array(self.result_obj.NodeStrainZZ) + exy = np.array(self.result_obj.NodeStrainXY) + exz = np.array(self.result_obj.NodeStrainXZ) + eyz = np.array(self.result_obj.NodeStrainYZ) + rx = np.array(self.result_obj.ReinforcementRatio_x) + ry = np.array(self.result_obj.ReinforcementRatio_y) + rz = np.array(self.result_obj.ReinforcementRatio_z) + mc = np.array(self.result_obj.MohrCoulomb) + # vectors + dispvectors = np.array(self.result_obj.DisplacementVectors) + x = np.array(dispvectors[:, 0]) + y = np.array(dispvectors[:, 1]) + z = np.array(dispvectors[:, 2]) + s1x, s1y, s1z = np.array([]), np.array([]), np.array([]) + s2x, s2y, s2z = np.array([]), np.array([]), np.array([]) + s3x, s3y, s3z = np.array([]), np.array([]), np.array([]) + # If PSxVector is empty all UserDefined equation does not work + if self.result_obj.PS1Vector: + ps1vector = np.array(self.result_obj.PS1Vector) + s1x = np.array(ps1vector[:, 0]) + s1y = np.array(ps1vector[:, 1]) + s1z = np.array(ps1vector[:, 2]) + if self.result_obj.PS2Vector: + ps2vector = np.array(self.result_obj.PS2Vector) + s2x = np.array(ps2vector[:, 0]) + s2y = np.array(ps2vector[:, 1]) + s2z = np.array(ps2vector[:, 2]) + if self.result_obj.PS3Vector: + ps3vector = np.array(self.result_obj.PS3Vector) + s3x = np.array(ps3vector[:, 0]) + s3y = np.array(ps3vector[:, 1]) + s3z = np.array(ps3vector[:, 2]) + + FreeCAD.FEM_dialog["results_type"] = "None" + self.update() + self.restore_result_dialog() + userdefined_eq = self.result_widget.user_def_eq.toPlainText() # Get equation to be used + + # https://forum.freecadweb.org/viewtopic.php?f=18&t=42425&start=10#p368774 ff + # https://github.com/FreeCAD/FreeCAD/pull/3020 + from ply import lex + from ply import yacc + import femtools.tokrules as tokrules + identifiers = [ + "x", "y", "z", "T", "vM", "Peeq", "P1", "P2", "P3", + "sxx", "syy", "szz", "sxy", "sxz", "syz", + "exx", "eyy", "ezz", "exy", "exz", "eyz", + "MF", "NP", "rx", "ry", "rz", "mc", + "s1x", "s1y", "s1z", "s2x", "s2y", "s2z", "s3x", "s3y", "s3z" + ] + tokrules.names = {} + for i in identifiers: + tokrules.names[i] = locals()[i] + + lexer = lex.lex(module=tokrules) + yacc.parse(input="UserDefinedFormula={0}".format(userdefined_eq), lexer=lexer) + UserDefinedFormula = tokrules.names["UserDefinedFormula"].tolist() + tokrules.names = {} + # UserDefinedFormula = eval(userdefined_eq).tolist() + + if UserDefinedFormula: + self.result_obj.UserDefined = UserDefinedFormula + minm = min(UserDefinedFormula) + maxm = max(UserDefinedFormula) + self.update_colors_stats(UserDefinedFormula, "", minm, maxm) + + # Dummy use of the variables to get around flake8 error + del x, y, z, T, vM, Peeq, P1, P2, P3 + del sxx, syy, szz, sxy, sxz, syz + del exx, eyy, ezz, exy, exz, eyz + del MF, NP, rx, ry, rz, mc + del s1x, s1y, s1z, s2x, s2y, s2z, s3x, s3y, s3z + + def get_scalar_disp_list(self, vector_list, axis): + # list is needed, as zib-object is not subscriptable in py3 + d = list(zip(*self.result_obj.DisplacementVectors)) + scalar_list = list(d[axis]) + return scalar_list + + def result_selected(self, res_type, res_values, res_unit): + FreeCAD.FEM_dialog["results_type"] = res_type + (minm, maxm) = self.get_result_stats(res_type) + self.update_colors_stats(res_values, res_unit, minm, maxm) + + if len(plt.get_fignums()) > 0: + plt.close() + plt.hist(res_values, bins=50, alpha=0.5, facecolor="blue") + plt.xlabel(res_unit) + plt.title("Histogram of {}".format(res_type)) + plt.ylabel("Nodes") + plt.grid(True) + fig_manager = plt.get_current_fig_manager() + fig_manager.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) # stay ontop + + def update_colors_stats(self, res_values, res_unit, minm, maxm): + QApplication.setOverrideCursor(Qt.WaitCursor) + if self.suitable_results: + self.mesh_obj.ViewObject.setNodeColorByScalars( + self.result_obj.NodeNumbers, + res_values + ) + self.set_result_stats(res_unit, minm, maxm) + QtGui.QApplication.restoreOverrideCursor() + + def set_result_stats(self, unit, minm, maxm): + self.result_widget.le_min.setProperty("unit", unit) + self.result_widget.le_min.setProperty("rawText", "{:.6} {}".format(minm, unit)) + self.result_widget.le_max.setProperty("unit", unit) + self.result_widget.le_max.setProperty("rawText", "{:.6} {}".format(maxm, unit)) + + def update_displacement(self, factor=None): + if factor is None: + if FreeCAD.FEM_dialog["show_disp"]: + factor = self.result_widget.sb_displacement_factor.value() + else: + factor = 0.0 + self.mesh_obj.ViewObject.applyDisplacement(factor) + + def show_displacement(self, checked): + QApplication.setOverrideCursor(Qt.WaitCursor) + FreeCAD.FEM_dialog["show_disp"] = checked + if "result_obj" in FreeCAD.FEM_dialog: + if FreeCAD.FEM_dialog["result_obj"] != self.result_obj: + self.update_displacement() + FreeCAD.FEM_dialog["result_obj"] = self.result_obj + if self.suitable_results: + self.mesh_obj.ViewObject.setNodeDisplacementByVectors( + self.result_obj.NodeNumbers, + self.result_obj.DisplacementVectors + ) + self.update_displacement() + QtGui.QApplication.restoreOverrideCursor() + + def hsb_disp_factor_changed(self, value): + self.result_widget.sb_displacement_factor.setValue( + value / 100. * self.result_widget.sb_displacement_factor_max.value() + ) + self.update_displacement() + + def sb_disp_factor_max_changed(self, value): + FreeCAD.FEM_dialog["disp_factor_max"] = value + if value < self.result_widget.sb_displacement_factor.value(): + self.result_widget.sb_displacement_factor.setValue(value) + if value == 0.: + self.result_widget.hsb_displacement_factor.setValue(0) + else: + self.result_widget.hsb_displacement_factor.setValue( + round(self.result_widget.sb_displacement_factor.value() / value * 100.) + ) + + def sb_disp_factor_changed(self, value): + FreeCAD.FEM_dialog["disp_factor"] = value + if value > self.result_widget.sb_displacement_factor_max.value(): + self.result_widget.sb_displacement_factor.setValue( + self.result_widget.sb_displacement_factor_max.value() + ) + if self.result_widget.sb_displacement_factor_max.value() == 0.: + self.result_widget.hsb_displacement_factor.setValue(0.) + else: + self.result_widget.hsb_displacement_factor.setValue( + round(value / self.result_widget.sb_displacement_factor_max.value() * 100.) + ) + + def disable_empty_result_buttons(self): + """ disable radio buttons if result does not exists in result object""" + """assignments + DisplacementLengths --> rb_abs_displacement + DisplacementVectors --> rb_x_displacement, rb_y_displacement, rb_z_displacement + Temperature --> rb_temperature + vonMises --> rb_vm_stress + PrincipalMax --> rb_maxprin + PrincipalMin --> rb_minprin + MaxShear --> rb_max_shear_stress + MassFlowRate --> rb_massflowrate + NetworkPressure --> rb_networkpressure + Peeq --> rb_peeq""" + if len(self.result_obj.DisplacementLengths) == 0: + self.result_widget.rb_abs_displacement.setEnabled(0) + if len(self.result_obj.DisplacementVectors) == 0: + self.result_widget.rb_x_displacement.setEnabled(0) + self.result_widget.rb_y_displacement.setEnabled(0) + self.result_widget.rb_z_displacement.setEnabled(0) + if len(self.result_obj.Temperature) == 0: + self.result_widget.rb_temperature.setEnabled(0) + if len(self.result_obj.vonMises) == 0: + self.result_widget.rb_vm_stress.setEnabled(0) + if len(self.result_obj.PrincipalMax) == 0: + self.result_widget.rb_maxprin.setEnabled(0) + if len(self.result_obj.PrincipalMin) == 0: + self.result_widget.rb_minprin.setEnabled(0) + if len(self.result_obj.MaxShear) == 0: + self.result_widget.rb_max_shear_stress.setEnabled(0) + if len(self.result_obj.MassFlowRate) == 0: + self.result_widget.rb_massflowrate.setEnabled(0) + if len(self.result_obj.NetworkPressure) == 0: + self.result_widget.rb_networkpressure.setEnabled(0) + if len(self.result_obj.Peeq) == 0: + self.result_widget.rb_peeq.setEnabled(0) + + def update(self): + self.reset_result_mesh() + self.suitable_results = False + self.disable_empty_result_buttons() + if (self.mesh_obj.FemMesh.NodeCount == len(self.result_obj.NodeNumbers)): + self.suitable_results = True + hide_parts_constraints() + else: + if not self.mesh_obj.FemMesh.VolumeCount: + error_message = ( + "FEM: Graphical bending stress output " + "for beam or shell FEM Meshes not yet supported.\n" + ) + FreeCAD.Console.PrintError(error_message) + QtGui.QMessageBox.critical(None, "No result object", error_message) + else: + error_message = "FEM: Result node numbers are not equal to FEM Mesh NodeCount.\n" + FreeCAD.Console.PrintError(error_message) + QtGui.QMessageBox.critical(None, "No result object", error_message) + + def reset_mesh_color(self): + self.mesh_obj.ViewObject.NodeColor = {} + self.mesh_obj.ViewObject.ElementColor = {} + self.mesh_obj.ViewObject.resetNodeColor() + + def reset_result_mesh(self): + self.mesh_obj.ViewObject.resetNodeDisplacement() + self.reset_mesh_color() + + def reject(self): + self.reset_result_mesh() + plt.close() + # if the tasks panel is called from Command obj is not in edit mode + # thus reset edit does not close the dialog, maybe don't call but set in edit instead + FreeCADGui.Control.closeDialog() + FreeCADGui.ActiveDocument.resetEdit() + + +# helper +def hide_parts_constraints(): + from FemGui import getActiveAnalysis + fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General") + hide_constraints = fem_prefs.GetBool("HideConstraint", False) + if hide_constraints: + for o in FreeCAD.ActiveDocument.Objects: + if o.isDerivedFrom("Fem::FemAnalysis"): + for acnstrmesh in getActiveAnalysis().Group: + if "Constraint" in acnstrmesh.TypeId: + acnstrmesh.ViewObject.Visibility = False + break + + +def get_displacement_scale_factor(res_obj): + node_items = res_obj.Mesh.FemMesh.Nodes.items() + displacements = res_obj.DisplacementVectors + x_max, y_max, z_max = map(max, zip(*displacements)) + positions = [] # list of node vectors + for k, v in node_items: + positions.append(v) + p_x_max, p_y_max, p_z_max = map(max, zip(*positions)) + p_x_min, p_y_min, p_z_min = map(min, zip(*positions)) + x_span = abs(p_x_max - p_x_min) + y_span = abs(p_y_max - p_y_min) + z_span = abs(p_z_max - p_z_min) + span = max(x_span, y_span, z_span) + max_disp = max(x_max, y_max, z_max) + if max_disp == 0.0: + return 0.0 # avoid float division by zero + # FIXME - add max_allowed_disp to Preferences + max_allowed_disp = 0.01 * span + scale = max_allowed_disp / max_disp + return scale diff --git a/src/Mod/Fem/femviewprovider/view_result_mechanical.py b/src/Mod/Fem/femviewprovider/view_result_mechanical.py index aa4fdf22ca..902e06d855 100644 --- a/src/Mod/Fem/femviewprovider/view_result_mechanical.py +++ b/src/Mod/Fem/femviewprovider/view_result_mechanical.py @@ -30,19 +30,11 @@ __url__ = "http://www.freecadweb.org" # \ingroup FEM # \brief view provider for mechanical ResultObjectPython -import matplotlib.pyplot as plt -import numpy as np - -from PySide import QtCore -from PySide import QtGui -from PySide.QtCore import Qt -from PySide.QtGui import QApplication - import FreeCAD import FreeCADGui +from femtaskpanels import task_result_mechanical from . import view_base_femconstraint -import femresult.resulttools as resulttools class VPResultMechanical(view_base_femconstraint.VPBaseFemConstraint): @@ -55,7 +47,7 @@ class VPResultMechanical(view_base_femconstraint.VPBaseFemConstraint): self, vobj, mode, - _TaskPanel, + task_result_mechanical._TaskPanel, ) # overwrite unsetEdit, hide result mesh object on task panel exit @@ -77,635 +69,3 @@ class VPResultMechanical(view_base_femconstraint.VPBaseFemConstraint): except Exception as err: FreeCAD.Console.PrintError("Error in onDelete: {0} \n".format(err)) return True - - -class _TaskPanel: - """ - The task panel for the post-processing - """ - - def __init__(self, obj): - self.result_obj = obj - self.mesh_obj = self.result_obj.Mesh - # task panel should be started by use of setEdit of view provider - # in view provider checks: Mesh, active analysis and - # if Mesh and result are in active analysis - # activate the result mesh object - self.mesh_obj.ViewObject.show() - - ui_path = FreeCAD.getHomePath() + "Mod/Fem/Resources/ui/" - self.result_widget = FreeCADGui.PySideUic.loadUi(ui_path + "ResultShow.ui") - self.info_widget = FreeCADGui.PySideUic.loadUi(ui_path + "ResultHints.ui") - self.form = [self.result_widget, self.info_widget] - - self.fem_prefs = FreeCAD.ParamGet( - "User parameter:BaseApp/Preferences/Mod/Fem/General" - ) - self.restore_result_settings_in_dialog = self.fem_prefs.GetBool( - "RestoreResultDialog", True - ) - - # Connect Signals and Slots - # result type radio buttons - # TODO: move to combo box, to be independent from result types and result types count - QtCore.QObject.connect( - self.result_widget.rb_none, QtCore.SIGNAL("toggled(bool)"), - self.none_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_abs_displacement, - QtCore.SIGNAL("toggled(bool)"), - self.abs_displacement_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_x_displacement, - QtCore.SIGNAL("toggled(bool)"), - self.x_displacement_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_y_displacement, - QtCore.SIGNAL("toggled(bool)"), - self.y_displacement_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_z_displacement, - QtCore.SIGNAL("toggled(bool)"), - self.z_displacement_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_temperature, - QtCore.SIGNAL("toggled(bool)"), - self.temperature_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_vm_stress, - QtCore.SIGNAL("toggled(bool)"), - self.vm_stress_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_maxprin, - QtCore.SIGNAL("toggled(bool)"), - self.max_prin_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_minprin, - QtCore.SIGNAL("toggled(bool)"), - self.min_prin_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_max_shear_stress, - QtCore.SIGNAL("toggled(bool)"), - self.max_shear_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_massflowrate, - QtCore.SIGNAL("toggled(bool)"), - self.massflowrate_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_networkpressure, - QtCore.SIGNAL("toggled(bool)"), - self.networkpressure_selected - ) - QtCore.QObject.connect( - self.result_widget.rb_peeq, - QtCore.SIGNAL("toggled(bool)"), - self.peeq_selected - ) - - # stats - self.result_widget.show_histogram.clicked.connect( - self.show_histogram_clicked - ) - - # displacement - QtCore.QObject.connect( - self.result_widget.cb_show_displacement, - QtCore.SIGNAL("clicked(bool)"), - self.show_displacement - ) - QtCore.QObject.connect( - self.result_widget.hsb_displacement_factor, - QtCore.SIGNAL("valueChanged(int)"), - self.hsb_disp_factor_changed - ) - - self.result_widget.sb_displacement_factor.valueChanged.connect( - self.sb_disp_factor_changed - ) - self.result_widget.sb_displacement_factor_max.valueChanged.connect( - self.sb_disp_factor_max_changed - ) - - # user defined equation - self.result_widget.user_def_eq.textChanged.connect( - self.user_defined_text - ) - QtCore.QObject.connect( - self.result_widget.calculate, - QtCore.SIGNAL("clicked()"), - self.calculate - ) - - self.update() - if self.restore_result_settings_in_dialog: - self.restore_result_dialog() - else: - self.restore_initial_result_dialog() - # initialize scale factor for show displacement - scale_factor = get_displacement_scale_factor(self.result_obj) - self.result_widget.sb_displacement_factor_max.setValue(10. * scale_factor) - self.result_widget.sb_displacement_factor.setValue(scale_factor) - - def restore_result_dialog(self): - try: - rt = FreeCAD.FEM_dialog["results_type"] - if rt == "None": - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - elif rt == "Uabs": - self.result_widget.rb_abs_displacement.setChecked(True) - self.abs_displacement_selected(True) - elif rt == "U1": - self.result_widget.rb_x_displacement.setChecked(True) - self.x_displacement_selected(True) - elif rt == "U2": - self.result_widget.rb_y_displacement.setChecked(True) - self.y_displacement_selected(True) - elif rt == "U3": - self.result_widget.rb_z_displacement.setChecked(True) - self.z_displacement_selected(True) - elif rt == "Temp": - self.result_widget.rb_temperature.setChecked(True) - self.temperature_selected(True) - elif rt == "Sabs": - self.result_widget.rb_vm_stress.setChecked(True) - self.vm_stress_selected(True) - elif rt == "MaxPrin": - self.result_widget.rb_maxprin.setChecked(True) - self.max_prin_selected(True) - elif rt == "MinPrin": - self.result_widget.rb_minprin.setChecked(True) - self.min_prin_selected(True) - elif rt == "MaxShear": - self.result_widget.rb_max_shear_stress.setChecked(True) - self.max_shear_selected(True) - elif rt == "MFlow": - self.result_widget.rb_massflowrate.setChecked(True) - self.massflowrate_selected(True) - elif rt == "NPress": - self.result_widget.rb_networkpressure.setChecked(True) - self.networkpressure_selected(True) - elif rt == "Peeq": - self.result_widget.rb_peeq.setChecked(True) - self.peeq_selected(True) - - sd = FreeCAD.FEM_dialog["show_disp"] - self.result_widget.cb_show_displacement.setChecked(sd) - self.show_displacement(sd) - - df = FreeCAD.FEM_dialog["disp_factor"] - dfm = FreeCAD.FEM_dialog["disp_factor_max"] - # self.result_widget.hsb_displacement_factor.setMaximum(dfm) - # self.result_widget.hsb_displacement_factor.setValue(df) - self.result_widget.sb_displacement_factor_max.setValue(dfm) - self.result_widget.sb_displacement_factor.setValue(df) - except: - self.restore_initial_result_dialog() - - def restore_initial_result_dialog(self): - # initialize FreeCAD.FEM_dialog and set standard values - # the FEM result mechanical task panel restore values - # are saved in a dictionary which is an attribute of FreeCAD - # the name is FEM_dialog - # in python console after result task panel has been opened once - # FreeCAD.FEM_dialog or FreeCAD.__dir__() - # This is not smart at all IMHO (Bernd) - # It was added with commit 3a7772d - # https://github.com/FreeCAD/FreeCAD/commit/3a7772d - FreeCAD.FEM_dialog = { - "results_type": "None", - "show_disp": False, - "disp_factor": 0., - "disp_factor_max": 100. - } - self.result_widget.sb_displacement_factor_max.setValue(100.) # init non standard values - - def getStandardButtons(self): - return int(QtGui.QDialogButtonBox.Close) - - def get_result_stats(self, type_name): - return resulttools.get_stats(self.result_obj, type_name) - - def none_selected(self, state): - FreeCAD.FEM_dialog["results_type"] = "None" - self.set_result_stats("mm", 0.0, 0.0) - self.reset_mesh_color() - if len(plt.get_fignums()) > 0: - plt.close() - - # if an analysis has different result types and one has - # stress and the other not the restore result dialog - # could trigger stress selected for a result object - # which has not stress - # see https://forum.freecadweb.org/viewtopic.php?f=18&t=39162 - # check if the results len is not 0 on any selected method - - def abs_displacement_selected(self, state): - if len(self.result_obj.DisplacementLengths) > 0: - self.result_selected("Uabs", self.result_obj.DisplacementLengths, "mm") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def x_displacement_selected(self, state): - if len(self.result_obj.DisplacementVectors) > 0: - res_disp_u1 = self.get_scalar_disp_list( - self.result_obj.DisplacementVectors, 0 - ) - self.result_selected("U1", res_disp_u1, "mm") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def y_displacement_selected(self, state): - if len(self.result_obj.DisplacementVectors) > 0: - res_disp_u2 = self.get_scalar_disp_list( - self.result_obj.DisplacementVectors, 1 - ) - self.result_selected("U2", res_disp_u2, "mm") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def z_displacement_selected(self, state): - if len(self.result_obj.DisplacementVectors) > 0: - res_disp_u3 = self.get_scalar_disp_list( - self.result_obj.DisplacementVectors, 2 - ) - self.result_selected("U3", res_disp_u3, "mm") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def vm_stress_selected(self, state): - if len(self.result_obj.vonMises) > 0: - self.result_selected("Sabs", self.result_obj.vonMises, "MPa") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def max_shear_selected(self, state): - if len(self.result_obj.MaxShear) > 0: - self.result_selected("MaxShear", self.result_obj.MaxShear, "MPa") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def max_prin_selected(self, state): - if len(self.result_obj.PrincipalMax) > 0: - self.result_selected("MaxPrin", self.result_obj.PrincipalMax, "MPa") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def temperature_selected(self, state): - if len(self.result_obj.Temperature) > 0: - self.result_selected("Temp", self.result_obj.Temperature, "K") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def massflowrate_selected(self, state): - if len(self.result_obj.MassFlowRate) > 0: - self.result_selected("MFlow", self.result_obj.MassFlowRate, "kg/s") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def networkpressure_selected(self, state): - if len(self.result_obj.NetworkPressure) > 0: - self.result_selected("NPress", self.result_obj.NetworkPressure, "MPa") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def min_prin_selected(self, state): - if len(self.result_obj.PrincipalMin) > 0: - self.result_selected("MinPrin", self.result_obj.PrincipalMin, "MPa") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def peeq_selected(self, state): - if len(self.result_obj.Peeq) > 0: - self.result_selected("Peeq", self.result_obj.Peeq, "") - else: - self.result_widget.rb_none.setChecked(True) - self.none_selected(True) - - def show_histogram_clicked(self): - if len(plt.get_fignums()) > 0: - plt.show() - else: - QtGui.QMessageBox.information( - None, - self.result_obj.Label + " - Information", - "No histogram available.\nPlease select a result type first." - ) - - def user_defined_text(self, equation): - FreeCAD.FEM_dialog["results_type"] = "user" - self.result_widget.user_def_eq.toPlainText() - - def calculate(self): - - # Convert existing result values to numpy array - # scalars - P1 = np.array(self.result_obj.PrincipalMax) - P2 = np.array(self.result_obj.PrincipalMed) - P3 = np.array(self.result_obj.PrincipalMin) - vM = np.array(self.result_obj.vonMises) - Peeq = np.array(self.result_obj.Peeq) - T = np.array(self.result_obj.Temperature) - MF = np.array(self.result_obj.MassFlowRate) - NP = np.array(self.result_obj.NetworkPressure) - sxx = np.array(self.result_obj.NodeStressXX) - syy = np.array(self.result_obj.NodeStressYY) - szz = np.array(self.result_obj.NodeStressZZ) - sxy = np.array(self.result_obj.NodeStressXY) - sxz = np.array(self.result_obj.NodeStressXZ) - syz = np.array(self.result_obj.NodeStressYZ) - exx = np.array(self.result_obj.NodeStrainXX) - eyy = np.array(self.result_obj.NodeStrainYY) - ezz = np.array(self.result_obj.NodeStrainZZ) - exy = np.array(self.result_obj.NodeStrainXY) - exz = np.array(self.result_obj.NodeStrainXZ) - eyz = np.array(self.result_obj.NodeStrainYZ) - rx = np.array(self.result_obj.ReinforcementRatio_x) - ry = np.array(self.result_obj.ReinforcementRatio_y) - rz = np.array(self.result_obj.ReinforcementRatio_z) - mc = np.array(self.result_obj.MohrCoulomb) - # vectors - dispvectors = np.array(self.result_obj.DisplacementVectors) - x = np.array(dispvectors[:, 0]) - y = np.array(dispvectors[:, 1]) - z = np.array(dispvectors[:, 2]) - s1x, s1y, s1z = np.array([]), np.array([]), np.array([]) - s2x, s2y, s2z = np.array([]), np.array([]), np.array([]) - s3x, s3y, s3z = np.array([]), np.array([]), np.array([]) - # If PSxVector is empty all UserDefined equation does not work - if self.result_obj.PS1Vector: - ps1vector = np.array(self.result_obj.PS1Vector) - s1x = np.array(ps1vector[:, 0]) - s1y = np.array(ps1vector[:, 1]) - s1z = np.array(ps1vector[:, 2]) - if self.result_obj.PS2Vector: - ps2vector = np.array(self.result_obj.PS2Vector) - s2x = np.array(ps2vector[:, 0]) - s2y = np.array(ps2vector[:, 1]) - s2z = np.array(ps2vector[:, 2]) - if self.result_obj.PS3Vector: - ps3vector = np.array(self.result_obj.PS3Vector) - s3x = np.array(ps3vector[:, 0]) - s3y = np.array(ps3vector[:, 1]) - s3z = np.array(ps3vector[:, 2]) - - FreeCAD.FEM_dialog["results_type"] = "None" - self.update() - self.restore_result_dialog() - userdefined_eq = self.result_widget.user_def_eq.toPlainText() # Get equation to be used - - # https://forum.freecadweb.org/viewtopic.php?f=18&t=42425&start=10#p368774 ff - # https://github.com/FreeCAD/FreeCAD/pull/3020 - from ply import lex - from ply import yacc - import femtools.tokrules as tokrules - identifiers = [ - "x", "y", "z", "T", "vM", "Peeq", "P1", "P2", "P3", - "sxx", "syy", "szz", "sxy", "sxz", "syz", - "exx", "eyy", "ezz", "exy", "exz", "eyz", - "MF", "NP", "rx", "ry", "rz", "mc", - "s1x", "s1y", "s1z", "s2x", "s2y", "s2z", "s3x", "s3y", "s3z" - ] - tokrules.names = {} - for i in identifiers: - tokrules.names[i] = locals()[i] - - lexer = lex.lex(module=tokrules) - yacc.parse(input="UserDefinedFormula={0}".format(userdefined_eq), lexer=lexer) - UserDefinedFormula = tokrules.names["UserDefinedFormula"].tolist() - tokrules.names = {} - # UserDefinedFormula = eval(userdefined_eq).tolist() - - if UserDefinedFormula: - self.result_obj.UserDefined = UserDefinedFormula - minm = min(UserDefinedFormula) - maxm = max(UserDefinedFormula) - self.update_colors_stats(UserDefinedFormula, "", minm, maxm) - - # Dummy use of the variables to get around flake8 error - del x, y, z, T, vM, Peeq, P1, P2, P3 - del sxx, syy, szz, sxy, sxz, syz - del exx, eyy, ezz, exy, exz, eyz - del MF, NP, rx, ry, rz, mc - del s1x, s1y, s1z, s2x, s2y, s2z, s3x, s3y, s3z - - def get_scalar_disp_list(self, vector_list, axis): - # list is needed, as zib-object is not subscriptable in py3 - d = list(zip(*self.result_obj.DisplacementVectors)) - scalar_list = list(d[axis]) - return scalar_list - - def result_selected(self, res_type, res_values, res_unit): - FreeCAD.FEM_dialog["results_type"] = res_type - (minm, maxm) = self.get_result_stats(res_type) - self.update_colors_stats(res_values, res_unit, minm, maxm) - - if len(plt.get_fignums()) > 0: - plt.close() - plt.hist(res_values, bins=50, alpha=0.5, facecolor="blue") - plt.xlabel(res_unit) - plt.title("Histogram of {}".format(res_type)) - plt.ylabel("Nodes") - plt.grid(True) - fig_manager = plt.get_current_fig_manager() - fig_manager.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint) # stay ontop - - def update_colors_stats(self, res_values, res_unit, minm, maxm): - QApplication.setOverrideCursor(Qt.WaitCursor) - if self.suitable_results: - self.mesh_obj.ViewObject.setNodeColorByScalars( - self.result_obj.NodeNumbers, - res_values - ) - self.set_result_stats(res_unit, minm, maxm) - QtGui.QApplication.restoreOverrideCursor() - - def set_result_stats(self, unit, minm, maxm): - self.result_widget.le_min.setProperty("unit", unit) - self.result_widget.le_min.setProperty("rawText", "{:.6} {}".format(minm, unit)) - self.result_widget.le_max.setProperty("unit", unit) - self.result_widget.le_max.setProperty("rawText", "{:.6} {}".format(maxm, unit)) - - def update_displacement(self, factor=None): - if factor is None: - if FreeCAD.FEM_dialog["show_disp"]: - factor = self.result_widget.sb_displacement_factor.value() - else: - factor = 0.0 - self.mesh_obj.ViewObject.applyDisplacement(factor) - - def show_displacement(self, checked): - QApplication.setOverrideCursor(Qt.WaitCursor) - FreeCAD.FEM_dialog["show_disp"] = checked - if "result_obj" in FreeCAD.FEM_dialog: - if FreeCAD.FEM_dialog["result_obj"] != self.result_obj: - self.update_displacement() - FreeCAD.FEM_dialog["result_obj"] = self.result_obj - if self.suitable_results: - self.mesh_obj.ViewObject.setNodeDisplacementByVectors( - self.result_obj.NodeNumbers, - self.result_obj.DisplacementVectors - ) - self.update_displacement() - QtGui.QApplication.restoreOverrideCursor() - - def hsb_disp_factor_changed(self, value): - self.result_widget.sb_displacement_factor.setValue( - value / 100. * self.result_widget.sb_displacement_factor_max.value() - ) - self.update_displacement() - - def sb_disp_factor_max_changed(self, value): - FreeCAD.FEM_dialog["disp_factor_max"] = value - if value < self.result_widget.sb_displacement_factor.value(): - self.result_widget.sb_displacement_factor.setValue(value) - if value == 0.: - self.result_widget.hsb_displacement_factor.setValue(0) - else: - self.result_widget.hsb_displacement_factor.setValue( - round(self.result_widget.sb_displacement_factor.value() / value * 100.) - ) - - def sb_disp_factor_changed(self, value): - FreeCAD.FEM_dialog["disp_factor"] = value - if value > self.result_widget.sb_displacement_factor_max.value(): - self.result_widget.sb_displacement_factor.setValue( - self.result_widget.sb_displacement_factor_max.value() - ) - if self.result_widget.sb_displacement_factor_max.value() == 0.: - self.result_widget.hsb_displacement_factor.setValue(0.) - else: - self.result_widget.hsb_displacement_factor.setValue( - round(value / self.result_widget.sb_displacement_factor_max.value() * 100.) - ) - - def disable_empty_result_buttons(self): - """ disable radio buttons if result does not exists in result object""" - """assignments - DisplacementLengths --> rb_abs_displacement - DisplacementVectors --> rb_x_displacement, rb_y_displacement, rb_z_displacement - Temperature --> rb_temperature - vonMises --> rb_vm_stress - PrincipalMax --> rb_maxprin - PrincipalMin --> rb_minprin - MaxShear --> rb_max_shear_stress - MassFlowRate --> rb_massflowrate - NetworkPressure --> rb_networkpressure - Peeq --> rb_peeq""" - if len(self.result_obj.DisplacementLengths) == 0: - self.result_widget.rb_abs_displacement.setEnabled(0) - if len(self.result_obj.DisplacementVectors) == 0: - self.result_widget.rb_x_displacement.setEnabled(0) - self.result_widget.rb_y_displacement.setEnabled(0) - self.result_widget.rb_z_displacement.setEnabled(0) - if len(self.result_obj.Temperature) == 0: - self.result_widget.rb_temperature.setEnabled(0) - if len(self.result_obj.vonMises) == 0: - self.result_widget.rb_vm_stress.setEnabled(0) - if len(self.result_obj.PrincipalMax) == 0: - self.result_widget.rb_maxprin.setEnabled(0) - if len(self.result_obj.PrincipalMin) == 0: - self.result_widget.rb_minprin.setEnabled(0) - if len(self.result_obj.MaxShear) == 0: - self.result_widget.rb_max_shear_stress.setEnabled(0) - if len(self.result_obj.MassFlowRate) == 0: - self.result_widget.rb_massflowrate.setEnabled(0) - if len(self.result_obj.NetworkPressure) == 0: - self.result_widget.rb_networkpressure.setEnabled(0) - if len(self.result_obj.Peeq) == 0: - self.result_widget.rb_peeq.setEnabled(0) - - def update(self): - self.reset_result_mesh() - self.suitable_results = False - self.disable_empty_result_buttons() - if (self.mesh_obj.FemMesh.NodeCount == len(self.result_obj.NodeNumbers)): - self.suitable_results = True - hide_parts_constraints() - else: - if not self.mesh_obj.FemMesh.VolumeCount: - error_message = ( - "FEM: Graphical bending stress output " - "for beam or shell FEM Meshes not yet supported.\n" - ) - FreeCAD.Console.PrintError(error_message) - QtGui.QMessageBox.critical(None, "No result object", error_message) - else: - error_message = "FEM: Result node numbers are not equal to FEM Mesh NodeCount.\n" - FreeCAD.Console.PrintError(error_message) - QtGui.QMessageBox.critical(None, "No result object", error_message) - - def reset_mesh_color(self): - self.mesh_obj.ViewObject.NodeColor = {} - self.mesh_obj.ViewObject.ElementColor = {} - self.mesh_obj.ViewObject.resetNodeColor() - - def reset_result_mesh(self): - self.mesh_obj.ViewObject.resetNodeDisplacement() - self.reset_mesh_color() - - def reject(self): - self.reset_result_mesh() - plt.close() - # if the tasks panel is called from Command obj is not in edit mode - # thus reset edit does not close the dialog, maybe don't call but set in edit instead - FreeCADGui.Control.closeDialog() - FreeCADGui.ActiveDocument.resetEdit() - - -# helper -def hide_parts_constraints(): - from FemGui import getActiveAnalysis - fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General") - hide_constraints = fem_prefs.GetBool("HideConstraint", False) - if hide_constraints: - for o in FreeCAD.ActiveDocument.Objects: - if o.isDerivedFrom("Fem::FemAnalysis"): - for acnstrmesh in getActiveAnalysis().Group: - if "Constraint" in acnstrmesh.TypeId: - acnstrmesh.ViewObject.Visibility = False - break - - -def get_displacement_scale_factor(res_obj): - node_items = res_obj.Mesh.FemMesh.Nodes.items() - displacements = res_obj.DisplacementVectors - x_max, y_max, z_max = map(max, zip(*displacements)) - positions = [] # list of node vectors - for k, v in node_items: - positions.append(v) - p_x_max, p_y_max, p_z_max = map(max, zip(*positions)) - p_x_min, p_y_min, p_z_min = map(min, zip(*positions)) - x_span = abs(p_x_max - p_x_min) - y_span = abs(p_y_max - p_y_min) - z_span = abs(p_z_max - p_z_min) - span = max(x_span, y_span, z_span) - max_disp = max(x_max, y_max, z_max) - if max_disp == 0.0: - return 0.0 # avoid float division by zero - # FIXME - add max_allowed_disp to Preferences - max_allowed_disp = 0.01 * span - scale = max_allowed_disp / max_disp - return scale