From bd2b95562b74b8f58ecd5e3f4172efc7e649ccc8 Mon Sep 17 00:00:00 2001 From: Uwe Date: Sun, 26 Mar 2023 08:47:04 +0200 Subject: [PATCH] [FEM] proper support for transient analyses - for the first time ever you get now for every time step a result in FreeCAD - this way also change output filename prefix to "FreeCAD" to avoid we depend on the default name Elmer gives and that was already changed in the past and to distinguish the *.vtu files from those created e.g. directly by ElmerGui - also remove an unnecessary output to the case.sif file --- src/Mod/Fem/femsolver/elmer/solver.py | 8 ++ src/Mod/Fem/femsolver/elmer/tasks.py | 94 ++++++++++++++++++- src/Mod/Fem/femsolver/elmer/writer.py | 11 ++- .../femtest/data/elmer/box_static_0_mm.sif | 3 +- .../elmer/ccxcantilever_faceload_0_mm.sif | 3 +- .../elmer/ccxcantilever_faceload_1_si.sif | 3 +- .../elmer/ccxcantilever_nodeload_0_mm.sif | 3 +- ...cantilever_prescribeddisplacement_0_mm.sif | 3 +- 8 files changed, 118 insertions(+), 10 deletions(-) diff --git a/src/Mod/Fem/femsolver/elmer/solver.py b/src/Mod/Fem/femsolver/elmer/solver.py index 10725c7d3a..c50a950c37 100644 --- a/src/Mod/Fem/femsolver/elmer/solver.py +++ b/src/Mod/Fem/femsolver/elmer/solver.py @@ -165,6 +165,14 @@ class Proxy(solverbase.Proxy): 4 | 8 ) + obj.addProperty( + "App::PropertyLinkList", + "ElmerTimeResults", + "Base", + "", + 4 | 8 + ) + obj.addProperty( "App::PropertyLink", "ElmerOutput", diff --git a/src/Mod/Fem/femsolver/elmer/tasks.py b/src/Mod/Fem/femsolver/elmer/tasks.py index b87f39d399..46000d800e 100644 --- a/src/Mod/Fem/femsolver/elmer/tasks.py +++ b/src/Mod/Fem/femsolver/elmer/tasks.py @@ -244,6 +244,12 @@ class Solve(run.Solve): class Results(run.Results): def run(self): + if self.solver.SimulationType == "Steady State": + self._handleStedyStateResult() + else: + self._handleTransientResults() + + def _handleStedyStateResult(self): if self.solver.ElmerResult is None: self._createResults() postPath = self._getResultFile() @@ -253,7 +259,7 @@ class Results(run.Results): return self.solver.ElmerResult.read(postPath) # at the moment we scale the mesh back using Elmer - # this might be changed in future, therefore leave this + # this might be changed in future, this commented code is left as info # self.solver.ElmerResult.scale(1000) # for eigen analyses the resulting values are by a factor 1000 to high @@ -269,20 +275,100 @@ class Results(run.Results): def _createResults(self): self.solver.ElmerResult = self.analysis.Document.addObject( "Fem::FemPostPipeline", self.solver.Name + "Result") - self.solver.ElmerResult.Label = self.solver.Label + "Result" + self.solver.ElmerResult.Label = self.solver.ElmerResult.Name self.solver.ElmerResult.ViewObject.SelectionStyle = "BoundBox" self.analysis.addObject(self.solver.ElmerResult) # to assure the user sees something, set the default to Surface self.solver.ElmerResult.ViewObject.DisplayMode = "Surface" + def _handleTransientResults(self): + # for transient results we must create a result pipeline for every time + # the connection between result files and and their time is in the FreeCAD.pvd file + # therefore first open FreeCAD.pvd + pvdFilePath = os.path.join(self.directory, "FreeCAD.pvd") + if not os.path.exists(pvdFilePath): + self.pushStatus("\nNo result file was created.\n") + self.fail() + return + pvdFile = open(pvdFilePath, "r") + # read all lines + pvdContent = pvdFile.readlines() + # skip header and footer line and evaluate all lines + # a line has the form like this: + # + # so .split("\"") gives as 2nd the time and as 7th the filename + for i in range(0, len(pvdContent) - 2): + # get time + lineArray = pvdContent[i+1].split("\"") + time = float(lineArray[1]) + filename = os.path.join(self.directory, lineArray[7]) + if os.path.isfile(filename): + self._createTimeResults(time, i+1) + self.solver.ElmerTimeResults[i].read(filename) + + # for eigen analyses the resulting values are by a factor 1000 to high + # therefore scale all *EigenMode results + self.solver.ElmerTimeResults[i].ViewObject.transformField( + "displacement EigenMode1", 0.001 + ) + + self.solver.ElmerTimeResults[i].recomputeChildren() + # recompute() will update the result mesh data + # but not the shape and bar coloring + self.solver.ElmerTimeResults[i].ViewObject.updateColorBars() + else: + self.pushStatus("\nResult file for time {} is missing.\n".format(time)) + self.fail() + return + self.solver.Document.recompute() + + def _createTimeResults(self, time, counter): + # if self.solver.ElmerTimeResults[counter] exists, but time is different + # recreate, other wise append + # FreeCAD would replaces dots in object names with underscores, thus do the same + newName = self.solver.Name + "_" + str(time).replace(".", "_") + "_" + "Result" + if counter > len(self.solver.ElmerTimeResults): + pipeline = self.analysis.Document.addObject( + "Fem::FemPostPipeline", newName + ) + # App::PropertyLinkList does not support append + # thus we have to use a temporary list to append + tmplist = self.solver.ElmerTimeResults + tmplist.append(pipeline) + self.solver.ElmerTimeResults = tmplist + self._finishTimeResults(time, counter-1) + else: + # recreate if time is not equal + if self.solver.ElmerTimeResults[counter-1].Name != newName: + # store current list before removing object since object removal will automatically + # remove entry from self.solver.ElmerTimeResults + tmplist = self.solver.ElmerTimeResults + self.analysis.Document.removeObject( + self.solver.ElmerTimeResults[counter-1].Name + ) + tmplist[counter-1] = self.analysis.Document.addObject( + "Fem::FemPostPipeline", newName + ) + self.solver.ElmerTimeResults = tmplist + self._finishTimeResults(time, counter-1) + + def _finishTimeResults(self, time, counter): + # we purposely use the decimal dot in the label + self.solver.ElmerTimeResults[counter].Label\ + = self.solver.Name + "_" + str(time) + "_" + "Result" + self.solver.ElmerTimeResults[counter].ViewObject.OnTopWhenSelected = True + self.analysis.addObject(self.solver.ElmerTimeResults[counter]) + # to assure the user sees something, set the default to Surface + self.solver.ElmerTimeResults[counter].ViewObject.DisplayMode = "Surface" + def _getResultFile(self): postPath = None # elmer post file path changed with version x.x # see https://forum.freecadweb.org/viewtopic.php?f=18&t=42732 # workaround possible_post_file_old = os.path.join(self.directory, "case0001.vtu") - possible_post_file_single = os.path.join(self.directory, "case_t0001.vtu") - possible_post_file_multi = os.path.join(self.directory, "case_t0001.pvtu") + possible_post_file_single = os.path.join(self.directory, "FreeCAD_t0001.vtu") + possible_post_file_multi = os.path.join(self.directory, "FreeCAD_t0001.pvtu") # depending on the currently set number of cores we try to load either # the multi-thread result or the single result if settings.get_cores("ElmerSolver") > 1: diff --git a/src/Mod/Fem/femsolver/elmer/writer.py b/src/Mod/Fem/femsolver/elmer/writer.py index 98a8ffc9a7..dfa4af460e 100644 --- a/src/Mod/Fem/femsolver/elmer/writer.py +++ b/src/Mod/Fem/femsolver/elmer/writer.py @@ -331,7 +331,6 @@ class Writer(object): # Elmer uses SI base units, but our mesh is in mm, therefore we must tell # the solver that we have another scale self._simulation("Coordinate Scaling", 0.001) - self._simulation("Output Intervals", 1) self._simulation("Simulation Type", self.solver.SimulationType) if self.solver.SimulationType == "Steady State": self._simulation( @@ -372,6 +371,14 @@ class Writer(object): "Order of time stepping method 'BDF'" ) solver.BDFOrder = (2, 1, 5, 1) + if not hasattr(self.solver, "ElmerTimeResults"): + solver.addProperty( + "App::PropertyLinkList", + "ElmerTimeResults", + "Base", + "", + 4 | 8 + ) if not hasattr(self.solver, "OutputIntervals"): solver.addProperty( "App::PropertyIntegerList", @@ -824,7 +831,9 @@ class Writer(object): else: s["Exec Solver"] = "After simulation" s["Procedure"] = sifio.FileAttr("ResultOutputSolve/ResultOutputSolver") + s["Output File Name"] = sifio.FileAttr("FreeCAD") s["Vtu Format"] = True + s["Vtu Time Collection"] = True if self.unit_schema == Units.Scheme.SI2: s["Coordinate Scaling Revert"] = True Console.PrintMessage( diff --git a/src/Mod/Fem/femtest/data/elmer/box_static_0_mm.sif b/src/Mod/Fem/femtest/data/elmer/box_static_0_mm.sif index cb83b85380..8d95069df6 100644 --- a/src/Mod/Fem/femtest/data/elmer/box_static_0_mm.sif +++ b/src/Mod/Fem/femtest/data/elmer/box_static_0_mm.sif @@ -27,7 +27,6 @@ Simulation Coordinate Mapping(3) = Integer 1 2 3 Coordinate Scaling = Real 0.001 Coordinate System = String "Cartesian" - Output Intervals = Integer 1 Simulation Type = String "Steady State" Steady State Max Iterations = Integer 1 Steady State Min Iterations = Integer 0 @@ -57,8 +56,10 @@ Solver 2 Coordinate Scaling Revert = Logical True Equation = String "ResultOutput" Exec Solver = String "After simulation" + Output File Name = File "FreeCAD" Procedure = File "ResultOutputSolve" "ResultOutputSolver" Vtu Format = Logical True + Vtu Time Collection = Logical True End Boundary Condition 1 diff --git a/src/Mod/Fem/femtest/data/elmer/ccxcantilever_faceload_0_mm.sif b/src/Mod/Fem/femtest/data/elmer/ccxcantilever_faceload_0_mm.sif index 9bf6ddd2dc..32d5a1fed1 100644 --- a/src/Mod/Fem/femtest/data/elmer/ccxcantilever_faceload_0_mm.sif +++ b/src/Mod/Fem/femtest/data/elmer/ccxcantilever_faceload_0_mm.sif @@ -27,7 +27,6 @@ Simulation Coordinate Mapping(3) = Integer 1 2 3 Coordinate Scaling = Real 0.001 Coordinate System = String "Cartesian" - Output Intervals = Integer 1 Simulation Type = String "Steady State" Steady State Max Iterations = Integer 1 Steady State Min Iterations = Integer 0 @@ -57,8 +56,10 @@ Solver 2 Coordinate Scaling Revert = Logical True Equation = String "ResultOutput" Exec Solver = String "After simulation" + Output File Name = File "FreeCAD" Procedure = File "ResultOutputSolve" "ResultOutputSolver" Vtu Format = Logical True + Vtu Time Collection = Logical True End Boundary Condition 1 diff --git a/src/Mod/Fem/femtest/data/elmer/ccxcantilever_faceload_1_si.sif b/src/Mod/Fem/femtest/data/elmer/ccxcantilever_faceload_1_si.sif index 9bf6ddd2dc..32d5a1fed1 100644 --- a/src/Mod/Fem/femtest/data/elmer/ccxcantilever_faceload_1_si.sif +++ b/src/Mod/Fem/femtest/data/elmer/ccxcantilever_faceload_1_si.sif @@ -27,7 +27,6 @@ Simulation Coordinate Mapping(3) = Integer 1 2 3 Coordinate Scaling = Real 0.001 Coordinate System = String "Cartesian" - Output Intervals = Integer 1 Simulation Type = String "Steady State" Steady State Max Iterations = Integer 1 Steady State Min Iterations = Integer 0 @@ -57,8 +56,10 @@ Solver 2 Coordinate Scaling Revert = Logical True Equation = String "ResultOutput" Exec Solver = String "After simulation" + Output File Name = File "FreeCAD" Procedure = File "ResultOutputSolve" "ResultOutputSolver" Vtu Format = Logical True + Vtu Time Collection = Logical True End Boundary Condition 1 diff --git a/src/Mod/Fem/femtest/data/elmer/ccxcantilever_nodeload_0_mm.sif b/src/Mod/Fem/femtest/data/elmer/ccxcantilever_nodeload_0_mm.sif index dd84946954..95811e42d5 100644 --- a/src/Mod/Fem/femtest/data/elmer/ccxcantilever_nodeload_0_mm.sif +++ b/src/Mod/Fem/femtest/data/elmer/ccxcantilever_nodeload_0_mm.sif @@ -27,7 +27,6 @@ Simulation Coordinate Mapping(3) = Integer 1 2 3 Coordinate Scaling = Real 0.001 Coordinate System = String "Cartesian" - Output Intervals = Integer 1 Simulation Type = String "Steady State" Steady State Max Iterations = Integer 1 Steady State Min Iterations = Integer 0 @@ -57,8 +56,10 @@ Solver 2 Coordinate Scaling Revert = Logical True Equation = String "ResultOutput" Exec Solver = String "After simulation" + Output File Name = File "FreeCAD" Procedure = File "ResultOutputSolve" "ResultOutputSolver" Vtu Format = Logical True + Vtu Time Collection = Logical True End Boundary Condition 1 diff --git a/src/Mod/Fem/femtest/data/elmer/ccxcantilever_prescribeddisplacement_0_mm.sif b/src/Mod/Fem/femtest/data/elmer/ccxcantilever_prescribeddisplacement_0_mm.sif index 329099baad..7ccd3daa7a 100644 --- a/src/Mod/Fem/femtest/data/elmer/ccxcantilever_prescribeddisplacement_0_mm.sif +++ b/src/Mod/Fem/femtest/data/elmer/ccxcantilever_prescribeddisplacement_0_mm.sif @@ -27,7 +27,6 @@ Simulation Coordinate Mapping(3) = Integer 1 2 3 Coordinate Scaling = Real 0.001 Coordinate System = String "Cartesian" - Output Intervals = Integer 1 Simulation Type = String "Steady State" Steady State Max Iterations = Integer 1 Steady State Min Iterations = Integer 0 @@ -57,8 +56,10 @@ Solver 2 Coordinate Scaling Revert = Logical True Equation = String "ResultOutput" Exec Solver = String "After simulation" + Output File Name = File "FreeCAD" Procedure = File "ResultOutputSolve" "ResultOutputSolver" Vtu Format = Logical True + Vtu Time Collection = Logical True End Boundary Condition 1