FEM: Animation of Results (#18496)
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user