From f54869760326729fa71e2d0658791a922a9e4be0 Mon Sep 17 00:00:00 2001 From: mac-the-bike <57401002+mac-the-bike@users.noreply.github.com> Date: Wed, 7 Jan 2026 08:22:07 +0000 Subject: [PATCH] FEM: Addition of "half cycle" animation (#24129) * Animation of Results - addition of half cycle * Delete src/Mod/Fem/Gui/Resources/ui/ResultShow.ui * Delete src/Mod/Fem/femtaskpanels/task_result_mechanical.py * Delete src/Mod/Fem/femviewprovider/view_result_mechanical.py * Add files via upload * Add files via upload * Add files via upload * Update view_result_mechanical.py * Update task_result_mechanical.py * Update task_result_mechanical.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Delete user_guide_animate.txt * Delete results.png --------- Co-authored-by: mac-the-bike Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- src/Mod/Fem/Gui/Resources/ui/ResultShow.ui | 35 +++++++++- .../femtaskpanels/task_result_mechanical.py | 70 ++++++++++++++----- .../femviewprovider/view_result_mechanical.py | 10 +-- 3 files changed, 90 insertions(+), 25 deletions(-) diff --git a/src/Mod/Fem/Gui/Resources/ui/ResultShow.ui b/src/Mod/Fem/Gui/Resources/ui/ResultShow.ui index 57b92ff9b3..3367664ff0 100644 --- a/src/Mod/Fem/Gui/Resources/ui/ResultShow.ui +++ b/src/Mod/Fem/Gui/Resources/ui/ResultShow.ui @@ -60,7 +60,7 @@ - von Mises Stress + von Mises stress @@ -495,9 +495,9 @@ - 10 + 230 130 - 435 + 200 25 @@ -508,6 +508,35 @@ Start Animation + + + + 10 + 130 + 91 + 23 + + + + Full cycle + + + true + + + + + + 120 + 130 + 101 + 23 + + + + Half cycle + + diff --git a/src/Mod/Fem/femtaskpanels/task_result_mechanical.py b/src/Mod/Fem/femtaskpanels/task_result_mechanical.py index 9785510af9..ed604d1bff 100644 --- a/src/Mod/Fem/femtaskpanels/task_result_mechanical.py +++ b/src/Mod/Fem/femtaskpanels/task_result_mechanical.py @@ -2,6 +2,7 @@ # * Copyright (c) 2015 Qingfeng Xia * # * Copyright (c) 2016 Bernd Hahnebach * # * Copyright (c) 2024 PMcB * +# * Copyright (c) 2025 PMcB * # * * # * This file is part of the FreeCAD CAx development system. * # * * @@ -266,6 +267,11 @@ class _TaskPanel: 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]) + if FreeCAD.FEM_dialog["animate"][3]: + self.result_widget.rb_full_cycle.setChecked(True) + else: + self.result_widget.rb_half_cycle.setChecked(True) + self.result_widget.sb_displacement_factor.setValue(FreeCAD.FEM_dialog["animate"][4]) except Exception: self.restore_initial_result_dialog() @@ -284,7 +290,7 @@ class _TaskPanel: "show_disp": True, # False, "disp_factor": 5.0, "disp_factor_max": 100.0, - "animate": [-1, -1, -1, -1], # steps, loops, rate, indicator (not used) + "animate": [-1, -1, -1, -1, -1, -1, -1], # steps, loops, rate, cycle type } self.result_widget.sb_displacement_factor_max.setValue(100.0) # init non standard values @@ -315,7 +321,7 @@ class _TaskPanel: "Uabs", self.result_obj.DisplacementLengths, "mm", - translate("FEM", "Displacement Magnitude"), + translate("FEM", "Displacement magnitude"), ) else: self.result_widget.rb_none.setChecked(True) @@ -351,7 +357,7 @@ class _TaskPanel: "Sabs", self.result_obj.vonMises, "MPa", - translate("FEM", "von Mises Stress"), + translate("FEM", "von Mises stress"), ) else: self.result_widget.rb_none.setChecked(True) @@ -363,7 +369,7 @@ class _TaskPanel: "MaxShear", self.result_obj.MaxShear, "MPa", - translate("FEM", "Max Shear Stress"), + translate("FEM", "Maximum shear stress (Tresca)"), ) else: self.result_widget.rb_none.setChecked(True) @@ -375,7 +381,7 @@ class _TaskPanel: "MaxPrin", self.result_obj.PrincipalMax, "MPa", - translate("FEM", "Max Principal Stress"), + translate("FEM", "Maximum principal stress"), ) else: self.result_widget.rb_none.setChecked(True) @@ -399,7 +405,7 @@ class _TaskPanel: "MFlow", self.result_obj.MassFlowRate, "kg/s", - translate("FEM", "Mass Flow Rate"), + translate("FEM", "Mass flow rate"), ) else: self.result_widget.rb_none.setChecked(True) @@ -411,7 +417,7 @@ class _TaskPanel: "NPress", self.result_obj.NetworkPressure, "MPa", - translate("FEM", "Network Pressure"), + translate("FEM", "Network pressure"), ) else: self.result_widget.rb_none.setChecked(True) @@ -423,7 +429,7 @@ class _TaskPanel: "MinPrin", self.result_obj.PrincipalMin, "MPa", - translate("FEM", "Min Principal Stress"), + translate("FEM", "Minimum principal stress"), ) else: self.result_widget.rb_none.setChecked(True) @@ -435,7 +441,7 @@ class _TaskPanel: "Peeq", self.result_obj.Peeq, "", - translate("FEM", "Equivalent Plastic Strain"), + translate("FEM", "Equivalent plastic strain"), ) else: self.result_widget.rb_none.setChecked(True) @@ -787,8 +793,14 @@ class _TaskPanel: 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() + FreeCAD.FEM_dialog["animate"][3] = self.result_widget.rb_full_cycle.isChecked() + try: + FreeCAD.FEM_dialog["animate"][4] = self.result_widget.sb_displacement_factor.value() + except: + FreeCAD.FEM_dialog["animate"][4] = 1 # animation start + def animate_displacement(self): if "result_obj" in FreeCAD.FEM_dialog: if FreeCAD.FEM_dialog["result_obj"] != self.result_obj: @@ -806,22 +818,44 @@ class _TaskPanel: 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 + sinc = 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): + # for lo in range(0, number_cycles): + loops = max(1, number_cycles) * steps_per_cycle + 1 + st = 0 + for loop in range(0, loops): + if self.result_widget.rb_half_cycle.isChecked(): + if number_cycles > 0: # 0 -> 1 -> 0, repeated + inc = sinc / 2 + else: + inc = sinc / 4 + # full cycle + if self.result_widget.rb_full_cycle.isChecked(): 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: + elif self.result_widget.rb_half_cycle.isChecked(): + # half cycle + if number_cycles > 0: # 0 -> 1 -> 0, repeated + self.mesh_obj.ViewObject.applyDisplacement( + abs(math.sin(st * inc)) * self.hsb_displacement_factor + ) + elif number_cycles <= 0: # 0 -> 1, once + self.mesh_obj.ViewObject.applyDisplacement( + abs(math.sin(st * inc)) * self.hsb_displacement_factor + ) + else: + print("No cycle type selected") + FreeCADGui.updateGui() + if not self.startAnimate: + done = True break + time.sleep(1.0 / frame_rate) # modify the time here + st += 1 + # if done: + # break try: self.result_widget.startButton.setText("Start Animation") except: diff --git a/src/Mod/Fem/femviewprovider/view_result_mechanical.py b/src/Mod/Fem/femviewprovider/view_result_mechanical.py index a431ac512b..fd782eb422 100644 --- a/src/Mod/Fem/femviewprovider/view_result_mechanical.py +++ b/src/Mod/Fem/femviewprovider/view_result_mechanical.py @@ -2,6 +2,7 @@ # * Copyright (c) 2015 Qingfeng Xia * # * Copyright (c) 2016 Bernd Hahnebach * # * Copyright (c) 2024 PMcB * +# * Copyright (c) 2025 PMcB * # * * # * This file is part of the FreeCAD CAx development system. * # * * @@ -44,6 +45,8 @@ class VPResultMechanical(view_base_femconstraint.VPBaseFemConstraint): """ def setEdit(self, vobj, mode=0): + # is mesh visible + self.visibility = self.Object.Mesh.ViewObject.Visibility return view_base_femconstraint.VPBaseFemConstraint.setEdit( self, vobj, @@ -51,12 +54,11 @@ class VPResultMechanical(view_base_femconstraint.VPBaseFemConstraint): task_result_mechanical._TaskPanel, ) - # overwrite unsetEdit, hide result mesh object on task panel exit def unsetEdit(self, vobj, mode=0): FreeCADGui.Control.closeDialog() - # hide the mesh after result viewing is finished, but do not reset the coloring - # change this to not hide - PMcB - # self.Object.Mesh.ViewObject.hide() + # hide the mesh if it was not visible + if not self.visibility: + self.Object.Mesh.ViewObject.hide() return True def claimChildren(self):