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