From cdcd271b4c97a27431dc28d852f6ed7e171dba67 Mon Sep 17 00:00:00 2001 From: Bernd Hahnebach Date: Fri, 30 Jul 2021 13:07:57 +0200 Subject: [PATCH] FEM: Mystran solver, add solver, writer, tasks and constraint force and fixed writing --- src/Mod/Fem/CMakeLists.txt | 16 ++ src/Mod/Fem/ObjectsFem.py | 11 ++ src/Mod/Fem/femsolver/mystran/__init__.py | 0 .../Fem/femsolver/mystran/add_con_fixed.py | 72 +++++++ .../Fem/femsolver/mystran/add_con_force.py | 81 ++++++++ .../mystran/add_femelement_geometry.py | 86 +++++++++ .../mystran/add_femelement_material.py | 60 ++++++ src/Mod/Fem/femsolver/mystran/add_mesh.py | 68 +++++++ .../femsolver/mystran/add_solver_control.py | 72 +++++++ src/Mod/Fem/femsolver/mystran/solver.py | 95 ++++++++++ src/Mod/Fem/femsolver/mystran/tasks.py | 179 ++++++++++++++++++ src/Mod/Fem/femsolver/mystran/writer.py | 133 +++++++++++++ src/Mod/Fem/femsolver/settings.py | 6 + src/Mod/Fem/femtest/app/test_object.py | 33 ++++ 14 files changed, 912 insertions(+) create mode 100644 src/Mod/Fem/femsolver/mystran/__init__.py create mode 100644 src/Mod/Fem/femsolver/mystran/add_con_fixed.py create mode 100644 src/Mod/Fem/femsolver/mystran/add_con_force.py create mode 100644 src/Mod/Fem/femsolver/mystran/add_femelement_geometry.py create mode 100644 src/Mod/Fem/femsolver/mystran/add_femelement_material.py create mode 100644 src/Mod/Fem/femsolver/mystran/add_mesh.py create mode 100644 src/Mod/Fem/femsolver/mystran/add_solver_control.py create mode 100644 src/Mod/Fem/femsolver/mystran/solver.py create mode 100644 src/Mod/Fem/femsolver/mystran/tasks.py create mode 100644 src/Mod/Fem/femsolver/mystran/writer.py diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 71a9c3d8de..ba99ae67a9 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -255,6 +255,19 @@ SET(FemSolverFenics_SRCS femsolver/fenics/fenics_tools.py ) +SET(FemSolverMystran_SRCS + femsolver/mystran/__init__.py + femsolver/mystran/add_con_fixed.py + femsolver/mystran/add_con_force.py + femsolver/mystran/add_femelement_geometry.py + femsolver/mystran/add_femelement_material.py + femsolver/mystran/add_mesh.py + femsolver/mystran/add_solver_control.py + femsolver/mystran/solver.py + femsolver/mystran/tasks.py + femsolver/mystran/writer.py +) + SET(FemSolverZ88_SRCS femsolver/z88/__init__.py femsolver/z88/solver.py @@ -281,6 +294,7 @@ SET(FemTestsApp_SRCS femtest/app/test_result.py femtest/app/test_solver_calculix.py femtest/app/test_solver_elmer.py + femtest/app/test_solver_mystran.py femtest/app/test_solver_z88.py ) @@ -452,6 +466,7 @@ SET(FemAllScripts ${FemSolverElmer_SRCS} ${FemSolverElmerEquations_SRCS} ${FemSolverFenics_SRCS} + ${FemSolverMystran_SRCS} ${FemSolverZ88_SRCS} ${FemTests_SRCS} ${FemTestsApp_SRCS} @@ -489,6 +504,7 @@ INSTALL(FILES ${FemSolverCalculix_SRCS} DESTINATION Mod/Fem/femsolver/calculix) INSTALL(FILES ${FemSolverElmer_SRCS} DESTINATION Mod/Fem/femsolver/elmer) INSTALL(FILES ${FemSolverElmerEquations_SRCS} DESTINATION Mod/Fem/femsolver/elmer/equations) INSTALL(FILES ${FemSolverFenics_SRCS} DESTINATION Mod/Fem/femsolver/fenics) +INSTALL(FILES ${FemSolverMystran_SRCS} DESTINATION Mod/Fem/femsolver/mystran) INSTALL(FILES ${FemSolverZ88_SRCS} DESTINATION Mod/Fem/femsolver/z88) INSTALL(FILES ${FemTests_SRCS} DESTINATION Mod/Fem/femtest) INSTALL(FILES ${FemTestsApp_SRCS} DESTINATION Mod/Fem/femtest/app) diff --git a/src/Mod/Fem/ObjectsFem.py b/src/Mod/Fem/ObjectsFem.py index fd7c380540..bc99add23a 100644 --- a/src/Mod/Fem/ObjectsFem.py +++ b/src/Mod/Fem/ObjectsFem.py @@ -799,6 +799,17 @@ def makeSolverElmer( return obj +def makeSolverMystran( + doc, + name="SolverMystran" +): + """makeSolverMystran(document, [name]): + makes a Mystran solver object""" + import femsolver.mystran.solver + obj = femsolver.mystran.solver.create(doc, name) + return obj + + def makeSolverZ88( doc, name="SolverZ88" diff --git a/src/Mod/Fem/femsolver/mystran/__init__.py b/src/Mod/Fem/femsolver/mystran/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Fem/femsolver/mystran/add_con_fixed.py b/src/Mod/Fem/femsolver/mystran/add_con_fixed.py new file mode 100644 index 0000000000..81be959d58 --- /dev/null +++ b/src/Mod/Fem/femsolver/mystran/add_con_fixed.py @@ -0,0 +1,72 @@ +# *************************************************************************** +# * Copyright (c) 2021 Bernd Hahnebach * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "Mystran add fixed constraint" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## \addtogroup FEM +# @{ + + +def add_con_fixed(f, model, mystran_writer): + + # generate pyNastran code + # spc1 card + spc_ids = [] + fixed_code = "# spc1 card, Defines a set of single-point constraints\n" + for i, femobj in enumerate(mystran_writer.fixed_objects): + + conid = i + 2 # 1 will be the conid of the spcadd card + spc_ids.append(conid) + fixed_obj = femobj["Object"] + # print(fixed_obj.Name) + fixed_code += "# {}\n".format(fixed_obj.Name) + # node set + fixed_code += "nodes_{} = {}\n".format(fixed_obj.Name, femobj["Nodes"]) + # all nodes in one line may be to long ... FIXME + fixed_code += ( + "model.add_spc1(conid={}, components={}, nodes=nodes_{})\n\n" + .format(conid, "123456", fixed_obj.Name) + ) + + # spcadd card + spcadd_code = "# spcadd card, Single-Point Constraint Set Combination from SPC or SPC1 cards\n" + spcadd_code += ( + "model.add_spcadd(conid=1, sets={})\n\n".format(spc_ids) + ) + + pynas_code = "{}\n{}".format(fixed_code, spcadd_code) + # print(pynas_code) + + # write the pyNastran code + f.write(pynas_code) + + # execute pyNastran code to add data to the model + # print(model.get_bdf_stats()) + exec(pynas_code) + # print(model.get_bdf_stats()) + return model + + +## @} diff --git a/src/Mod/Fem/femsolver/mystran/add_con_force.py b/src/Mod/Fem/femsolver/mystran/add_con_force.py new file mode 100644 index 0000000000..db40356c04 --- /dev/null +++ b/src/Mod/Fem/femsolver/mystran/add_con_force.py @@ -0,0 +1,81 @@ +# *************************************************************************** +# * Copyright (c) 2021 Bernd Hahnebach * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "Mystran add force constraint" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## \addtogroup FEM +# @{ + + +def add_con_force(f, model, mystran_writer): + + # generate pyNastran code + # force card + scale_factors = [] + load_ids = [] + force_code = "# force cards, mesh node loads\n" + for i, femobj in enumerate(mystran_writer.force_objects): + + sid = i + 2 # 1 will be the id of the load card + scale_factors.append(1.0) + load_ids.append(sid) + force_obj = femobj["Object"] + # print(force_obj.Name) + + force_code += "# {}\n".format(force_obj.Name) + dirvec = femobj["Object"].DirectionVector + print(femobj["NodeLoadTable"]) + for ref_shape in femobj["NodeLoadTable"]: + force_code += "# {}\n".format(ref_shape[0]) + for n in sorted(ref_shape[1]): + node_load = ref_shape[1][n] + force_code += ( + "model.add_force(sid={}, node={}, mag={}, xyz=({}, {}, {}))\n" + .format(sid, n, node_load, dirvec.x, dirvec.y, dirvec.z) + ) + force_code += "\n" + + # generate calce factors lists + # load card, static load combinations + load_code = ( + "model.add_load(sid=1, scale=1.0, scale_factors={}, load_ids={})\n\n\n" + .format(scale_factors, load_ids) + ) + + pynas_code = "{}\n{}".format(force_code, load_code) + # print(pynas_code) + + # write the pyNastran code + f.write(pynas_code) + + # execute pyNastran code to add data to the model + # print(model.get_bdf_stats()) + exec(pynas_code) + # print(model.get_bdf_stats()) + + return model + + +## @} diff --git a/src/Mod/Fem/femsolver/mystran/add_femelement_geometry.py b/src/Mod/Fem/femsolver/mystran/add_femelement_geometry.py new file mode 100644 index 0000000000..8e5fda86c5 --- /dev/null +++ b/src/Mod/Fem/femsolver/mystran/add_femelement_geometry.py @@ -0,0 +1,86 @@ +# *************************************************************************** +# * Copyright (c) 2021 Bernd Hahnebach * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "Mystran add femelement geometry" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## \addtogroup FEM +# @{ + + +def add_femelement_geometry(f, model, mystran_writer): + + # generate pyNastran code + # HACK, the if statemant needs improvement, see calculix solver + if mystran_writer.member.geos_beamsection: + beamsec_obj = mystran_writer.member.geos_beamsection[0]["Object"] + if beamsec_obj.SectionType == "Rectangular": + height = beamsec_obj.RectHeight.getValueAs("mm").Value + width = beamsec_obj.RectWidth.getValueAs("mm").Value + pynas_code = "# pbarl card, properties of a simple beam element (CBAR entry)\n" + pynas_code += "# defined by cross-sectional dimensions\n" + pynas_code += ( + "dim = [{}, {}]\n" + .format(width, height) + ) + pynas_code += ( + "model.add_pbarl(pid=1, mid=1, Type={}, dim=dim, nsm=0.0)\n" + .format('"BAR"') + ) + pynas_code += "# pbarl.validate()\n\n\n" + else: + return + elif mystran_writer.member.geos_shellthickness: + # only use the first shellthickness object + shellth_obj = mystran_writer.member.geos_shellthickness[0]["Object"] + thickness = shellth_obj.Thickness.getValueAs("mm").Value + pynas_code = "# pshell card, thin shell element properties\n" + pynas_code += ( + "model.add_pshell(pid=1, mid1=1, t={}, mid2=1, mid3=1)\n\n\n" + .format(thickness) + ) + else: + pynas_code = "# psolid card, defines solid element\n" + pynas_code += "model.add_psolid(pid=1, mid=1)\n\n\n" + + # write the pyNastran code + f.write(pynas_code) + + # execute pyNastran code to add data to the model + # print(model.get_bdf_stats()) + exec(pynas_code) + # print(model.get_bdf_stats()) + + return model + + +pynas_code = """ +# pshell card, thin shell element properties +model.add_pshell(1, mid1=1, t=0.3, mid2=1, mid3=1) + + +""" + + +## @} diff --git a/src/Mod/Fem/femsolver/mystran/add_femelement_material.py b/src/Mod/Fem/femsolver/mystran/add_femelement_material.py new file mode 100644 index 0000000000..a5c4c95a0d --- /dev/null +++ b/src/Mod/Fem/femsolver/mystran/add_femelement_material.py @@ -0,0 +1,60 @@ +# *************************************************************************** +# * Copyright (c) 2021 Bernd Hahnebach * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "Mystran add femelement materials" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## \addtogroup FEM +# @{ + + +from FreeCAD import Units + + +def add_femelement_material(f, model, mystran_writer): + + # generate pyNastran code + # only use the first material object + mat_obj = mystran_writer.member.mats_linear[0]["Object"] + YM = Units.Quantity(mat_obj.Material["YoungsModulus"]) + YM_in_MPa = YM.getValueAs("MPa").Value + PR = float(mat_obj.Material["PoissonRatio"]) + pynas_code = "# mat1 card, material properties for linear isotropic material\n" + pynas_code += ( + "mat = model.add_mat1(mid=1, E={:.1f}, G=None, nu={})\n\n\n" + .format(YM_in_MPa, PR) + ) + + # write the pyNastran code + f.write(pynas_code) + + # execute pyNastran code to add data to the model + # print(model.get_bdf_stats()) + exec(pynas_code) + # print(model.get_bdf_stats()) + + return model + + +## @} diff --git a/src/Mod/Fem/femsolver/mystran/add_mesh.py b/src/Mod/Fem/femsolver/mystran/add_mesh.py new file mode 100644 index 0000000000..43cea22233 --- /dev/null +++ b/src/Mod/Fem/femsolver/mystran/add_mesh.py @@ -0,0 +1,68 @@ +# *************************************************************************** +# * Copyright (c) 2021 Bernd Hahnebach * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "Mystran add fem mesh" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## \addtogroup FEM +# @{ + +from feminout import exportNastranMesh +from femmesh import meshtools + + +def add_mesh(f, model, mystran_writer): + + # needed basic data + if not mystran_writer.femnodes_mesh: + mystran_writer.femnodes_mesh = mystran_writer.femmesh.Nodes + if not mystran_writer.femelement_table: + mystran_writer.femelement_table = meshtools.get_femelement_table( + mystran_writer.femmesh + ) + mesh_eletype = exportNastranMesh.get_export_element_type( + mystran_writer.femmesh, + mystran_writer.femelement_table + ) + + # get the pynas code + mesh_pynas_code = exportNastranMesh.get_pynastran_mesh( + mystran_writer.femnodes_mesh, + mystran_writer.femelement_table, + mesh_eletype + ) + # print(mesh_pynas_code) + + # write the pyNastran code + f.write(mesh_pynas_code) + + # execute pyNastran code to add grid to the model + # print(model.get_bdf_stats()) + exec(mesh_pynas_code) + # print(model.get_bdf_stats()) + + return model + + +## @} diff --git a/src/Mod/Fem/femsolver/mystran/add_solver_control.py b/src/Mod/Fem/femsolver/mystran/add_solver_control.py new file mode 100644 index 0000000000..824e593fe5 --- /dev/null +++ b/src/Mod/Fem/femsolver/mystran/add_solver_control.py @@ -0,0 +1,72 @@ +# *************************************************************************** +# * Copyright (c) 2021 Bernd Hahnebach * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "Mystran add solver control" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## \addtogroup FEM +# @{ + + +def add_solver_control(f, model, mystran_writer): + + # write the pyNastran code which will be executed into the file + f.write(pynas_code) + + # print(model.get_bdf_stats()) + exec(pynas_code) + # print(model.get_bdf_stats()) + + return model + + +pynas_code = """ +# executive control +model.sol = 101 + + +# params cards +model.add_param(key="POST", values=-1) +# model.add_param(key="PRTMAXIM", values="YES") # not recognized by Mystran + + +# case control +from pyNastran.bdf.bdf import CaseControlDeck +cc = CaseControlDeck([ + "ECHO = NONE", + "TITLE = pyNastran for generating solverinput for for Mystran", + "SUBCASE 1", + " SUBTITLE = Default", + " LOAD = 1", + " SPC = 1", + " SPCFORCES(SORT1,REAL) = ALL", + " STRESS(SORT1,REAL,VONMISES,BILIN) = ALL", + " DISPLACEMENT(SORT1,REAL) = ALL", +]) +model.case_control_deck = cc +# model.validate() # creates an error +""" + + +## @} diff --git a/src/Mod/Fem/femsolver/mystran/solver.py b/src/Mod/Fem/femsolver/mystran/solver.py new file mode 100644 index 0000000000..9a438b0fd6 --- /dev/null +++ b/src/Mod/Fem/femsolver/mystran/solver.py @@ -0,0 +1,95 @@ +# *************************************************************************** +# * Copyright (c) 2021 Bernd Hahnebach * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "FreeCAD FEM solver object Mystran" +__author__ = "Bernd Hahnebach" +__url__ = "https://www.freecadweb.org" + +## @package SolverMystran +# \ingroup FEM + +import glob +import os + +import FreeCAD + +from . import tasks +from .. import run +from .. import solverbase +from femtools import femutils + +if FreeCAD.GuiUp: + import FemGui + +ANALYSIS_TYPES = ["static"] + + +def create(doc, name="SolverMystran"): + return femutils.createObject( + doc, name, Proxy, ViewProxy) + + +class Proxy(solverbase.Proxy): + """The Fem::FemSolver's Proxy python type, add solver specific properties + """ + + Type = "Fem::SolverMystran" + + def __init__(self, obj): + super(Proxy, self).__init__(obj) + obj.Proxy = self + + # mystran_prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Fem/Mystran") + + obj.addProperty("App::PropertyEnumeration", "AnalysisType", "Fem", "Type of the analysis") + obj.AnalysisType = ANALYSIS_TYPES + obj.AnalysisType = ANALYSIS_TYPES[0] + + def createMachine(self, obj, directory, testmode=False): + return run.Machine( + solver=obj, directory=directory, + check=tasks.Check(), + prepare=tasks.Prepare(), + solve=tasks.Solve(), + results=tasks.Results(), + testmode=testmode) + + def editSupported(self): + return True + + def edit(self, directory): + pattern = os.path.join(directory, "*.bdf") # TODO Mystran file ending + FreeCAD.Console.PrintMessage("{}\n".format(pattern)) + f = glob.glob(pattern)[0] + FemGui.open(f) + # see comment in oofem solver file + + def execute(self, obj): + return + + +class ViewProxy(solverbase.ViewProxy): + pass + + +## @} diff --git a/src/Mod/Fem/femsolver/mystran/tasks.py b/src/Mod/Fem/femsolver/mystran/tasks.py new file mode 100644 index 0000000000..72c2560bd0 --- /dev/null +++ b/src/Mod/Fem/femsolver/mystran/tasks.py @@ -0,0 +1,179 @@ +# *************************************************************************** +# * Copyright (c) 2021 Bernd Hahnebach * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "FreeCAD FEM solver Mystran tasks" +__author__ = "Bernd Hahnebach" +__url__ = "https://www.freecadweb.org" + +## \addtogroup FEM +# @{ + +import os +import os.path +import subprocess + +import FreeCAD + + +try: + import hfcMystranNeuIn + result_reading = True +except Exception: + FreeCAD.Console.PrintWarning("Module to read results not found.\n") + result_reading = False + + +from . import writer +from .. import run +from .. import settings +from femmesh import meshsetsgetter +from femtools import femutils +from femtools import membertools + + +_inputFileName = None + + +class Check(run.Check): + + def run(self): + self.pushStatus("Checking analysis...\n") + self.check_mesh_exists() + self.check_material_exists() + self.check_material_single() # no multiple material + self.check_geos_beamsection_single() # no multiple beamsection + self.check_geos_shellthickness_single() # no multiple shellsection + self.check_geos_beamsection_and_shellthickness() # either beams or shells + + +class Prepare(run.Prepare): + + def run(self): + global _inputFileName + self.pushStatus("Preparing input files...\n") + + mesh_obj = membertools.get_mesh_to_solve(self.analysis)[0] # pre check done already + + # get mesh set data + # TODO evaluate if it makes sense to add new task + # between check and prepare to the solver frame work + meshdatagetter = meshsetsgetter.MeshSetsGetter( + self.analysis, + self.solver, + mesh_obj, + membertools.AnalysisMember(self.analysis), + ) + meshdatagetter.get_mesh_sets() + + # write input file + w = writer.FemInputWriterMystran( + self.analysis, + self.solver, + mesh_obj, + meshdatagetter.member, + self.directory, + meshdatagetter.mat_geo_sets + ) + path = w.write_solver_input() + # report to user if task succeeded + if path != "": + self.pushStatus("Write completed!") + else: + self.pushStatus("Writing CalculiX input file failed!") + _inputFileName = os.path.splitext(os.path.basename(path))[0] + + +class Solve(run.Solve): + + def run(self): + # print(_inputFileName) + if not _inputFileName: + # TODO do not run solver, do not try to read results in a smarter way than an Exception + raise Exception("Error on writing Mystran input file.\n") + infile = _inputFileName + ".bdf" + + # TODO use solver framework status system + FreeCAD.Console.PrintMessage("Mystran: solver input file: {} \n\n".format(infile)) + + # get binary + self.pushStatus("Get solver...\n") + binary = settings.get_binary("Mystran") + # use preferences editor to add a group Mystran and the prefs: + # "UseStandardMystranLocation" --> bool, set to False + # "mystranBinaryPath, string" --> the binary path + if binary is None: + return # a print has been made in settings module + + # run solver + self.pushStatus("Executing solver...\n") + self._process = subprocess.Popen( + args=[binary, infile], # pass empty param fails! [binary, "", infile] + cwd=self.directory, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + self.signalAbort.add(self._process.terminate) + self._process.communicate() + self.signalAbort.remove(self._process.terminate) + + # for chatching the output see CalculiX or Elmer solver tasks module + + +class Results(run.Results): + + def run(self): + prefs = FreeCAD.ParamGet( + "User parameter:BaseApp/Preferences/Mod/Fem/General") + if not prefs.GetBool("KeepResultsOnReRun", False): + self.purge_results() + if result_reading is True: + self.load_results() # ToDo in all solvers generischer name + + def purge_results(self): + for m in membertools.get_member(self.analysis, "Fem::FemResultObject"): + if femutils.is_of_type(m.Mesh, "Fem::MeshResult"): + self.analysis.Document.removeObject(m.Mesh.Name) + self.analysis.Document.removeObject(m.Name) + self.analysis.Document.recompute() + # deletes all results from any solver + # TODO: delete only the mystran results, fix in all solver + + def load_results(self): + self.pushStatus("Import results...\n") + neu_result_file = os.path.join(self.directory, _inputFileName + ".NEU") + if os.path.isfile(neu_result_file): + hfcMystranNeuIn.import_neu(neu_result_file) + # Workaround to move result object into analysis + for o in self.analysis.Document.Objects: + if o.Name == "Displacement0": + self.analysis.addObject(o) + break + else: + # TODO: use solver framework error and status message system + FreeCAD.Console.PrintError( + "FEM: No results found at {}!\n".format(neu_result_file) + ) + return + + +## @} diff --git a/src/Mod/Fem/femsolver/mystran/writer.py b/src/Mod/Fem/femsolver/mystran/writer.py new file mode 100644 index 0000000000..f25047d168 --- /dev/null +++ b/src/Mod/Fem/femsolver/mystran/writer.py @@ -0,0 +1,133 @@ +# *************************************************************************** +# * Copyright (c) 2021 Bernd Hahnebach * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * This program is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with this program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "Mystran Writer" +__author__ = "Bernd Hahnebach" +__url__ = "http://www.freecadweb.org" + +## \addtogroup FEM +# @{ + +import time +from os.path import join + +import FreeCAD + +# we need to import FreeCAD before the non FreeCAD library because of the print +try: + from pyNastran.bdf.bdf import BDF +except Exception: + FreeCAD.Console.PrintError( + "Module pyNastran not found. Writing Mystran solver input will not be work.\n" + ) + +from . import add_mesh +from . import add_femelement_material +from . import add_femelement_geometry +from . import add_con_force +from . import add_con_fixed +from . import add_solver_control +from .. import writerbase + + +class FemInputWriterMystran(writerbase.FemInputWriter): + def __init__( + self, + analysis_obj, + solver_obj, + mesh_obj, + member, + dir_name=None, + mat_geo_sets=None + ): + writerbase.FemInputWriter.__init__( + self, + analysis_obj, + solver_obj, + mesh_obj, + member, + dir_name, + mat_geo_sets + ) + # basename (only for implementation purpose later delete this code + # the mesh should never be None for Calculix solver + # working dir and input file + if self.mesh_object is not None: + self.basename = self.mesh_object.Name + else: + self.basename = "Mesh" + self.solverinput_file = join(self.dir_name, self.basename + ".bdf") + self.pynasinput_file = join(self.dir_name, self.basename + ".py") + FreeCAD.Console.PrintLog( + "FemInputWriterMystran --> self.dir_name --> {}\n" + .format(self.dir_name) + ) + FreeCAD.Console.PrintMessage( + "FemInputWriterMystra --> self.solverinput_file --> {}\n" + .format(self.solverinput_file) + ) + FreeCAD.Console.PrintMessage( + "FemInputWriterMystra --> self.pynasf_name --> {}\n" + .format(self.pynasinput_file) + ) + + def write_solver_input(self): + + timestart = time.process_time() + + model = BDF() + + pynasf = open(self.pynasinput_file, "w") + + # comment and model init + pynasf.write("# written by FreeCAD\n\n") + pynasf.write("from pyNastran.bdf.bdf import BDF\n") + pynasf.write("model = BDF()\n\n") + + model = add_mesh.add_mesh(pynasf, model, self) + model = add_femelement_material.add_femelement_material(pynasf, model, self) + model = add_femelement_geometry.add_femelement_geometry(pynasf, model, self) + model = add_con_force.add_con_force(pynasf, model, self) + model = add_con_fixed.add_con_fixed(pynasf, model, self) + model = add_solver_control.add_solver_control(pynasf, model, self) + + pynasf.write( + "\n\nmodel.write_bdf('{}', enddata=True)\n" + .format(join(self.dir_name, self.basename + "_pyNas.bdf")) + ) + + pynasf.close() + + # print(model.get_bdf_stats()) + model.write_bdf(self.solverinput_file, enddata=True) + + writing_time_string = ( + "Writing time input file: {} seconds" + .format(round((time.process_time() - timestart), 2)) + ) + FreeCAD.Console.PrintMessage(writing_time_string + " \n\n") + + return self.solverinput_file + + +## @} diff --git a/src/Mod/Fem/femsolver/settings.py b/src/Mod/Fem/femsolver/settings.py index f01efa3f4f..7590b97a06 100644 --- a/src/Mod/Fem/femsolver/settings.py +++ b/src/Mod/Fem/femsolver/settings.py @@ -34,6 +34,7 @@ are supported: - Calculix - ElmerSolver + - Mystran - Z88 To query settings about those solver the solver name must be given exactly in @@ -239,6 +240,11 @@ _SOLVER_PARAM = { param_path=_PARAM_PATH + "Elmer", use_default="UseStandardGridLocation", custom_path="gridBinaryPath"), + "Mystran": _SolverDlg( + default="mystran", + param_path=_PARAM_PATH + "Mystran", + use_default="UseStandardMystranLocation", + custom_path="mystranBinaryPath"), "Z88": _SolverDlg( default="z88r", param_path=_PARAM_PATH + "Z88", diff --git a/src/Mod/Fem/femtest/app/test_object.py b/src/Mod/Fem/femtest/app/test_object.py index b3d710bc0f..89371f1714 100644 --- a/src/Mod/Fem/femtest/app/test_object.py +++ b/src/Mod/Fem/femtest/app/test_object.py @@ -322,6 +322,10 @@ class TestObjectType(unittest.TestCase): "Fem::SolverElmer", type_of_obj(solverelmer) ) + self.assertEqual( + "Fem::SolverMystran", + type_of_obj(ObjectsFem.makeSolverMystran(doc)) + ) self.assertEqual( "Fem::SolverZ88", type_of_obj(ObjectsFem.makeSolverZ88(doc)) @@ -537,6 +541,10 @@ class TestObjectType(unittest.TestCase): solverelmer, "Fem::SolverElmer" )) + self.assertTrue(is_of_type( + ObjectsFem.makeSolverMystran(doc), + "Fem::SolverMystran" + )) self.assertTrue(is_of_type( ObjectsFem.makeSolverZ88(doc), "Fem::SolverZ88" @@ -1217,6 +1225,25 @@ class TestObjectType(unittest.TestCase): "Fem::SolverElmer" )) + # SolverMystran + solver_mystran = ObjectsFem.makeSolverMystran(doc) + self.assertTrue(is_derived_from( + solver_mystran, + "App::DocumentObject" + )) + self.assertTrue(is_derived_from( + solver_mystran, + "Fem::FemSolverObject" + )) + self.assertTrue(is_derived_from( + solver_mystran, + "Fem::FemSolverObjectPython" + )) + self.assertTrue(is_derived_from( + solver_mystran, + "Fem::SolverMystran" + )) + # SolverZ88 solver_z88 = ObjectsFem.makeSolverZ88(doc) self.assertTrue(is_derived_from( @@ -1548,6 +1575,11 @@ class TestObjectType(unittest.TestCase): self.assertTrue( solverelmer.isDerivedFrom("Fem::FemSolverObjectPython") ) + self.assertTrue( + ObjectsFem.makeSolverMystran( + doc + ).isDerivedFrom("Fem::FemSolverObjectPython") + ) self.assertTrue( ObjectsFem.makeSolverZ88( doc @@ -1657,6 +1689,7 @@ def create_all_fem_objects_doc( analysis.addObject(ObjectsFem.makeSolverCalculixCcxTools(doc)) analysis.addObject(ObjectsFem.makeSolverCalculix(doc)) sol = analysis.addObject(ObjectsFem.makeSolverElmer(doc))[0] + analysis.addObject(ObjectsFem.makeSolverMystran(doc)) analysis.addObject(ObjectsFem.makeSolverZ88(doc)) ObjectsFem.makeEquationElasticity(doc, sol)