FEM: Animation of Results (#18496)

This commit is contained in:
mac-the-bike
2025-02-17 16:24:09 +00:00
committed by GitHub
parent 4698042deb
commit 6fcbafb121
5 changed files with 592 additions and 43 deletions

View File

@@ -1,6 +1,7 @@
# ***************************************************************************
# * Copyright (c) 2015 Qingfeng Xia <qingfeng.xia()eng.ox.ac.uk> *
# * Copyright (c) 2016 Bernd Hahnebach <bernd@bimstatik.org> *
# * Copyright (c) 2024 PMcB *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
@@ -30,6 +31,9 @@ __url__ = "https://www.freecad.org"
# \ingroup FEM
# \brief task panel for mechanical ResultObjectPython
import CreateLabels
import inspect, sys
try:
import matplotlib
@@ -39,6 +43,7 @@ except Exception:
import matplotlib.pyplot as plt
import numpy as np
import time, math
from PySide import QtCore
from PySide import QtGui
@@ -71,6 +76,12 @@ class _TaskPanel:
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.results_name = "No Contour Data"
self.animate_inc = 1
self.startAnimate = False
self.animateText = []
self.slider_max = False
self.recurlim = min(200, sys.getrecursionlimit() / 2)
self.fem_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/General")
self.restore_result_settings_in_dialog = self.fem_prefs.GetBool("RestoreResultDialog", True)
@@ -79,7 +90,9 @@ class _TaskPanel:
# 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
self.result_widget.rb_none,
QtCore.SIGNAL("toggled(bool)"),
self.none_selected,
)
QtCore.QObject.connect(
self.result_widget.rb_abs_displacement,
@@ -107,13 +120,19 @@ class _TaskPanel:
self.temperature_selected,
)
QtCore.QObject.connect(
self.result_widget.rb_vm_stress, QtCore.SIGNAL("toggled(bool)"), self.vm_stress_selected
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
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
self.result_widget.rb_minprin,
QtCore.SIGNAL("toggled(bool)"),
self.min_prin_selected,
)
QtCore.QObject.connect(
self.result_widget.rb_max_shear_stress,
@@ -131,11 +150,29 @@ class _TaskPanel:
self.networkpressure_selected,
)
QtCore.QObject.connect(
self.result_widget.rb_peeq, QtCore.SIGNAL("toggled(bool)"), self.peeq_selected
self.result_widget.rb_peeq,
QtCore.SIGNAL("toggled(bool)"),
self.peeq_selected,
)
# stats
self.result_widget.show_histogram.clicked.connect(self.show_histogram_clicked)
# animate
QtCore.QObject.connect(
self.result_widget.hsb_displacement_factor,
QtCore.SIGNAL("valueChanged(int)"),
lambda dummy="", name="scale": self.value_changed(self, dummy, name),
)
QtCore.QObject.connect(
self.result_widget.sb_displacement_factor,
QtCore.SIGNAL("valueChanged(double)"),
lambda dummy="", name="factor": self.value_changed(self, dummy, name),
)
QtCore.QObject.connect(
self.result_widget.startButton,
QtCore.SIGNAL("clicked()"),
lambda dummy="", name="startButton": self.value_changed(self, dummy, name),
)
# displacement
QtCore.QObject.connect(
@@ -223,6 +260,12 @@ class _TaskPanel:
# 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)
# animate
self.startAnimate = False
if FreeCAD.FEM_dialog["animate"][0] != -1:
self.result_widget.steps.setValue(FreeCAD.FEM_dialog["animate"][0])
self.result_widget.loops.setValue(FreeCAD.FEM_dialog["animate"][1])
self.result_widget.framerate.setValue(FreeCAD.FEM_dialog["animate"][2])
except Exception:
self.restore_initial_result_dialog()
@@ -238,9 +281,10 @@ class _TaskPanel:
# https://github.com/FreeCAD/FreeCAD/commit/3a7772d
FreeCAD.FEM_dialog = {
"results_type": "None",
"show_disp": False,
"disp_factor": 0.0,
"show_disp": True, # False,
"disp_factor": 5.0,
"disp_factor_max": 100.0,
"animate": [-1, -1, -1, -1], # steps, loops, rate, indicator (not used)
}
self.result_widget.sb_displacement_factor_max.setValue(100.0) # init non standard values
@@ -251,6 +295,7 @@ class _TaskPanel:
return resulttools.get_stats(self.result_obj, type_name)
def none_selected(self, state):
self.set_label(self.result_obj.Label, "No Contours")
FreeCAD.FEM_dialog["results_type"] = "None"
self.set_result_stats("mm", 0.0, 0.0)
self.reset_mesh_color()
@@ -303,7 +348,10 @@ class _TaskPanel:
def vm_stress_selected(self, state):
if len(self.result_obj.vonMises) > 0:
self.result_selected(
"Sabs", self.result_obj.vonMises, "MPa", translate("FEM", "von Mises Stress")
"Sabs",
self.result_obj.vonMises,
"MPa",
translate("FEM", "von Mises Stress"),
)
else:
self.result_widget.rb_none.setChecked(True)
@@ -312,7 +360,10 @@ class _TaskPanel:
def max_shear_selected(self, state):
if len(self.result_obj.MaxShear) > 0:
self.result_selected(
"MaxShear", self.result_obj.MaxShear, "MPa", translate("FEM", "Max Shear Stress")
"MaxShear",
self.result_obj.MaxShear,
"MPa",
translate("FEM", "Max Shear Stress"),
)
else:
self.result_widget.rb_none.setChecked(True)
@@ -333,7 +384,10 @@ class _TaskPanel:
def temperature_selected(self, state):
if len(self.result_obj.Temperature) > 0:
self.result_selected(
"Temp", self.result_obj.Temperature, "K", translate("FEM", "Temperature")
"Temp",
self.result_obj.Temperature,
"K",
translate("FEM", "Temperature"),
)
else:
self.result_widget.rb_none.setChecked(True)
@@ -342,7 +396,10 @@ class _TaskPanel:
def massflowrate_selected(self, state):
if len(self.result_obj.MassFlowRate) > 0:
self.result_selected(
"MFlow", self.result_obj.MassFlowRate, "kg/s", translate("FEM", "Mass Flow Rate")
"MFlow",
self.result_obj.MassFlowRate,
"kg/s",
translate("FEM", "Mass Flow Rate"),
)
else:
self.result_widget.rb_none.setChecked(True)
@@ -375,7 +432,10 @@ class _TaskPanel:
def peeq_selected(self, state):
if len(self.result_obj.Peeq) > 0:
self.result_selected(
"Peeq", self.result_obj.Peeq, "", translate("FEM", "Equivalent Plastic Strain")
"Peeq",
self.result_obj.Peeq,
"",
translate("FEM", "Equivalent Plastic Strain"),
)
else:
self.result_widget.rb_none.setChecked(True)
@@ -396,7 +456,10 @@ class _TaskPanel:
QtGui.QMessageBox.information(
None,
self.result_obj.Label + " - " + translate("FEM", "Information"),
translate("FEM", "No histogram available.\nPlease select a result type first."),
translate(
"FEM",
"No histogram available.\nPlease select a result type first.",
),
)
def user_defined_text(self, equation):
@@ -404,7 +467,6 @@ class _TaskPanel:
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)
@@ -461,6 +523,7 @@ class _TaskPanel:
self.update()
self.restore_result_dialog()
userdefined_eq = self.result_widget.user_def_eq.toPlainText() # Get equation to be used
self.results_name = "User Defined: " + userdefined_eq
# https://forum.freecad.org/viewtopic.php?f=18&t=42425&start=10#p368774 ff
# https://github.com/FreeCAD/FreeCAD/pull/3020
@@ -533,6 +596,7 @@ class _TaskPanel:
return scalar_list
def result_selected(self, res_type, res_values, res_unit, res_title):
self.results_name = res_title
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)
@@ -555,6 +619,7 @@ class _TaskPanel:
fig_manager.window.setWindowFlag(QtCore.Qt.Tool)
def update_colors_stats(self, res_values, res_unit, minm, maxm):
self.set_label(self.result_obj.Label, self.results_name)
QApplication.setOverrideCursor(Qt.WaitCursor)
if self.suitable_results:
self.mesh_obj.ViewObject.setNodeColorByScalars(self.result_obj.NodeNumbers, res_values)
@@ -596,6 +661,7 @@ class _TaskPanel:
self.update_displacement()
def sb_disp_factor_max_changed(self, value):
self.slider_max = True
FreeCAD.FEM_dialog["disp_factor_max"] = value
if value < self.result_widget.sb_displacement_factor.value():
self.result_widget.sb_displacement_factor.setValue(value)
@@ -605,19 +671,24 @@ class _TaskPanel:
self.result_widget.hsb_displacement_factor.setValue(
round(self.result_widget.sb_displacement_factor.value() / value * 100.0)
)
self.slider_max = False
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.0:
self.result_widget.hsb_displacement_factor.setValue(0.0)
else:
self.result_widget.hsb_displacement_factor.setValue(
round(value / self.result_widget.sb_displacement_factor_max.value() * 100.0)
)
# this bit of code causes:
# RecursionError: maximum recursion depth exceeded
# so check on the depth and don't exceed recurlim
if len(inspect.stack(0)) < self.recurlim:
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.0:
self.result_widget.hsb_displacement_factor.setValue(0.0)
else:
self.result_widget.hsb_displacement_factor.setValue(
round(value / self.result_widget.sb_displacement_factor_max.value() * 100.0)
)
def disable_empty_result_buttons(self):
"""disable radio buttons if result does not exists in result object"""
@@ -706,6 +777,99 @@ class _TaskPanel:
# thus reset edit does not close the dialog, maybe don't call but set in edit instead
FreeCADGui.Control.closeDialog()
FreeCADGui.ActiveDocument.resetEdit()
if len(self.animateText) > 0:
for a in self.animateText:
a.hide()
self.animateText = []
self.startAnimate = False
FreeCAD.FEM_dialog["animate"][0] = self.result_widget.steps.value()
FreeCAD.FEM_dialog["animate"][1] = self.result_widget.loops.value()
FreeCAD.FEM_dialog["animate"][2] = self.result_widget.framerate.value()
# animation start
def animate_displacement(self):
if "result_obj" in FreeCAD.FEM_dialog:
if FreeCAD.FEM_dialog["result_obj"] != self.result_obj:
self.update_displacement()
self.result_widget.cb_show_displacement.setChecked(True)
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.result_widget.startButton.setText("Stop Animation")
frame_rate = 10
self.hsb_displacement_factor = self.result_widget.sb_displacement_factor.value()
frame_rate = self.result_widget.framerate.value()
steps_per_cycle = int(self.result_widget.steps.value())
number_cycles = int(self.result_widget.loops.value())
inc = math.pi / steps_per_cycle * 2.0
self.set_label(self.result_obj.Label, self.results_name)
done = False
for lo in range(0, number_cycles):
for st in range(0, steps_per_cycle):
self.mesh_obj.ViewObject.applyDisplacement(
math.sin(st * inc) * self.hsb_displacement_factor
)
FreeCADGui.updateGui()
if not self.startAnimate:
done = True
break
time.sleep(1.0 / frame_rate) # modify the time here
if done:
break
try:
self.result_widget.startButton.setText("Start Animation")
except:
pass
QtGui.QApplication.restoreOverrideCursor()
self.startAnimate = False
def value_changed(self, dummy, value, myType):
# the only actions are:
if myType == "startButton":
if not self.startAnimate:
self.startAnimate = True
self.animate_displacement()
else:
self.startAnimate = False
# # this is taken care of in the "ui"
# # set the scale - scroll bar - Show
# elif myType == "scale" and not self.slider_max:
# if self.animate_inc == 0:
# if self.result_widget.hsb_displacement_factor.value() > 1:
# self.result_widget.sb_displacement_factor.setValue(
# self.result_widget.hsb_displacement_factor.value()
# )
# self.animate_inc = 1 - self.animate_inc
# # set the factor - spin - Factor
# elif myType == "factor" and not self.slider_max:
# if self.animate_inc == 0:
# self.result_widget.hsb_displacement_factor.setValue(
# int(self.result_widget.sb_displacement_factor.value())
# )
# self.animate_inc = 1 - self.animate_inc
else:
pass
try:
self.hsb_displacement_factor = self.result_widget.sb_displacement_factor.value()
except:
pass
return
def set_label(self, result_name, mesh_data):
if len(self.animateText) == 0:
self.animateText.append(CreateLabels.createLabel((-0.98, 0.90, 0), result_name))
self.animateText.append(CreateLabels.createLabel((-0.98, 0.70, 0), mesh_data))
else:
self.animateText[1].set_text(mesh_data)
pass
# animation end
# helper