diff --git a/src/Mod/Fem/App/CMakeLists.txt b/src/Mod/Fem/App/CMakeLists.txt index 054f0914a8..eb43721e65 100644 --- a/src/Mod/Fem/App/CMakeLists.txt +++ b/src/Mod/Fem/App/CMakeLists.txt @@ -120,6 +120,7 @@ SET(FemObjectsScripts_SRCS SET(FemSolver_SRCS femsolver/__init__.py femsolver/solverbase.py + femsolver/equationbase.py femsolver/report.py femsolver/reportdialog.py femsolver/settings.py @@ -128,6 +129,24 @@ SET(FemSolver_SRCS femsolver/signal.py ) +SET(FemElmer_SRCS + femsolver/elmer/__init__.py + femsolver/elmer/sifio.py + femsolver/elmer/solver.py + femsolver/elmer/tasks.py + femsolver/elmer/writer.py +) + +SET(FemEquationsElmer_SRCS + femsolver/elmer/equations/__init__.py + femsolver/elmer/equations/equation.py + femsolver/elmer/equations/linear.py + femsolver/elmer/equations/nonlinear.py + femsolver/elmer/equations/elasticity.py + femsolver/elmer/equations/heat.py + femsolver/elmer/equations/flow.py +) + SET(FemCalculix_SRCS femsolver/calculix/__init__.py femsolver/calculix/solver.py @@ -169,6 +188,8 @@ SET(FemGuiScripts_SRCS PyGui/_CommandFemResultShow.py PyGui/_CommandFemResultsPurge.py PyGui/_CommandFemSolverCalculix.py + PyGui/_CommandFemSolverElmer.py + PyGui/_CommandFemEquation.py PyGui/_CommandFemSolverControl.py PyGui/_CommandFemSolverRun.py PyGui/_CommandFemSolverZ88.py @@ -353,8 +374,10 @@ fc_target_copy_resource(Fem ${FemGuiScripts_SRCS} ${FemTests_SRCS} ${FemSolver_SRCS} + ${FemElmer_SRCS} ${FemCalculix_SRCS} ${FemZ88_SRCS} + ${FemEquationsElmer_SRCS} ) SET_BIN_DIR(Fem Fem /Mod/Fem) diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 029374a7a0..59fc866371 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -73,6 +73,7 @@ INSTALL( FILES femsolver/__init__.py femsolver/solverbase.py + femsolver/equationbase.py femsolver/report.py femsolver/reportdialog.py femsolver/settings.py @@ -83,6 +84,30 @@ INSTALL( Mod/Fem/femsolver ) +INSTALL( + FILES + femsolver/elmer/__init__.py + femsolver/elmer/sifio.py + femsolver/elmer/solver.py + femsolver/elmer/tasks.py + femsolver/elmer/writer.py + DESTINATION + Mod/Fem/femsolver/elmer +) + +INSTALL( + FILES + femsolver/elmer/equations/__init__.py + femsolver/elmer/equations/equation.py + femsolver/elmer/equations/linear.py + femsolver/elmer/equations/nonlinear.py + femsolver/elmer/equations/elasticity.py + femsolver/elmer/equations/heat.py + femsolver/elmer/equations/flow.py + DESTINATION + Mod/Fem/femsolver/elmer/equations +) + INSTALL( FILES femsolver/calculix/__init__.py @@ -109,6 +134,8 @@ INSTALL( PyGui/FemSelectionObserver.py PyGui/FemSelectionWidgets.py PyGui/__init__.py + PyGui/_CommandFemSolverElmer.py + PyGui/_CommandFemEquation.py PyGui/_CommandFemConstraintBodyHeatSource.py PyGui/_CommandFemConstraintFlowVelocity.py PyGui/_CommandFemConstraintInitialFlowVelocity.py diff --git a/src/Mod/Fem/Gui/AppFemGui.cpp b/src/Mod/Fem/Gui/AppFemGui.cpp index 72489097f3..3a2ddf268c 100644 --- a/src/Mod/Fem/Gui/AppFemGui.cpp +++ b/src/Mod/Fem/Gui/AppFemGui.cpp @@ -38,6 +38,7 @@ #include "DlgSettingsFemExportAbaqusImp.h" #include "DlgSettingsFemGmshImp.h" #include "DlgSettingsFemZ88Imp.h" +#include "DlgSettingsFemElmerImp.h" #include "ViewProviderFemMesh.h" #include "ViewProviderFemMeshShape.h" #include "ViewProviderFemMeshShapeNetgen.h" @@ -160,6 +161,7 @@ PyMOD_INIT_FUNC(FemGui) new Gui::PrefPageProducer (QT_TRANSLATE_NOOP("QObject","FEM")); new Gui::PrefPageProducer (QT_TRANSLATE_NOOP("QObject","FEM")); new Gui::PrefPageProducer (QT_TRANSLATE_NOOP("QObject","FEM")); + new Gui::PrefPageProducer (QT_TRANSLATE_NOOP("QObject","FEM")); // register preferences pages on Import-Export new Gui::PrefPageProducer (QT_TRANSLATE_NOOP("QObject","Import-Export")); diff --git a/src/Mod/Fem/Gui/CMakeLists.txt b/src/Mod/Fem/Gui/CMakeLists.txt index 64726ca288..5643818f6c 100755 --- a/src/Mod/Fem/Gui/CMakeLists.txt +++ b/src/Mod/Fem/Gui/CMakeLists.txt @@ -46,6 +46,7 @@ set(FemGui_MOC_HDRS DlgSettingsFemGeneralImp.h DlgSettingsFemGmshImp.h DlgSettingsFemZ88Imp.h + DlgSettingsFemElmerImp.h PropertyFemMeshItem.h TaskObjectName.h TaskCreateNodeSet.h @@ -87,6 +88,7 @@ set(FemGui_UIC_SRCS DlgSettingsFemGeneral.ui DlgSettingsFemGmsh.ui DlgSettingsFemZ88.ui + DlgSettingsFemElmer.ui TaskCreateNodeSet.ui TaskObjectName.ui TaskFemConstraint.ui @@ -143,6 +145,9 @@ SET(FemGui_DLG_SRCS DlgSettingsFemZ88.ui DlgSettingsFemZ88Imp.cpp DlgSettingsFemZ88Imp.h + DlgSettingsFemElmer.ui + DlgSettingsFemElmerImp.cpp + DlgSettingsFemElmerImp.h TaskFemConstraint.ui TaskFemConstraint.cpp TaskFemConstraint.h diff --git a/src/Mod/Fem/Gui/DlgSettingsFemElmer.ui b/src/Mod/Fem/Gui/DlgSettingsFemElmer.ui new file mode 100644 index 0000000000..bc52463b86 --- /dev/null +++ b/src/Mod/Fem/Gui/DlgSettingsFemElmer.ui @@ -0,0 +1,312 @@ + + + FemGui::DlgSettingsFemElmerImp + + + + 0 + 0 + 377 + 451 + + + + Elmer + + + + 6 + + + + + + 0 + 0 + + + + Binaries + + + + + + + + ElmerSolver: + + + + + + + false + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + Leave blank to use default Elmer elmer binary file + + + elmerBinaryPath + + + Mod/Fem/Elmer + + + + + + + use standard path + + + true + + + UseStandardElmerLocation + + + Mod/Fem/Elmer + + + + + + + ElmerGrid: + + + + + + + false + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 0 + 0 + + + + Leave blank to use default ElmerGrid binary file + + + gridBinaryPath + + + Mod/Fem/Grid + + + + + + + use standard path + + + true + + + UseStandardGridLocation + + + Mod/Fem/Grid + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 40 + + + + + + + + + + Gui::PrefCheckBox + QCheckBox +
Gui/PrefWidgets.h
+
+ + Gui::FileChooser + QWidget +
Gui/FileDialog.h
+
+ + Gui::PrefFileChooser + Gui::FileChooser +
Gui/PrefWidgets.h
+
+ + Gui::PrefRadioButton + QRadioButton +
Gui/PrefWidgets.h
+
+ + Gui::PrefLineEdit + QLineEdit +
Gui/PrefWidgets.h
+
+
+ + + + + + cb_elmer_binary_std + toggled(bool) + fc_elmer_binary_path + setEnabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + cb_elmer_binary_std + toggled(bool) + fc_elmer_binary_path + setDisabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + cb_grid_binary_std + toggled(bool) + fc_grid_binary_path + setEnabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + cb_grid_binary_std + toggled(bool) + fc_grid_binary_path + setDisabled(bool) + + + 20 + 20 + + + 20 + 20 + + + + + cb_wd_custom + toggled(bool) + le_wd_custom + setDisabled(bool) + + + 188 + 314 + + + 137 + 372 + + + + + cb_wd_custom + toggled(bool) + le_wd_custom + setEnabled(bool) + + + 188 + 314 + + + 137 + 372 + + + + +
diff --git a/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp b/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp new file mode 100644 index 0000000000..6ca22d7a7a --- /dev/null +++ b/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.cpp @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (c) 2015 FreeCAD Developers * + * Author: Bernd Hahnebach * + * Based on src/Mod/Fem/Gui/DlgSettingsFemCcxImp.cpp * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#include "Gui/Application.h" +#include "DlgSettingsFemElmerImp.h" +#include + +using namespace FemGui; + +DlgSettingsFemElmerImp::DlgSettingsFemElmerImp( QWidget* parent ) + : PreferencePage( parent ) +{ + this->setupUi(this); +} + +DlgSettingsFemElmerImp::~DlgSettingsFemElmerImp() +{ + // no need to delete child widgets, Qt does it all for us +} + +void DlgSettingsFemElmerImp::saveSettings() +{ + cb_elmer_binary_std->onSave(); + fc_elmer_binary_path->onSave(); + + cb_grid_binary_std->onSave(); + fc_grid_binary_path->onSave(); +} + +void DlgSettingsFemElmerImp::loadSettings() +{ + cb_elmer_binary_std->onRestore(); + fc_elmer_binary_path->onRestore(); + + cb_grid_binary_std->onRestore(); + fc_grid_binary_path->onRestore(); +} + +/** + * Sets the strings of the subwidgets using the current language. + */ +void DlgSettingsFemElmerImp::changeEvent(QEvent *e) +{ + if (e->type() == QEvent::LanguageChange) { + } + else { + QWidget::changeEvent(e); + } +} + +#include "moc_DlgSettingsFemElmerImp.cpp" diff --git a/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.h b/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.h new file mode 100644 index 0000000000..89000b48fe --- /dev/null +++ b/src/Mod/Fem/Gui/DlgSettingsFemElmerImp.h @@ -0,0 +1,50 @@ + /************************************************************************** + * Copyright (c) 2016 FreeCAD Developers * + * Author: Bernd Hahnebach * + * Based on src/Mod/Fem/Gui/DlgSettingsFemCcx.h * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef FEMGUI_DLGSETTINGSFEMELMERIMP_H +#define FEMGUI_DLGSETTINGSFEMELMERIMP_H + +#include "ui_DlgSettingsFemElmer.h" +#include + +namespace FemGui { + +class DlgSettingsFemElmerImp : public Gui::Dialog::PreferencePage, public Ui_DlgSettingsFemElmerImp +{ + Q_OBJECT + +public: + DlgSettingsFemElmerImp( QWidget* parent = 0 ); + ~DlgSettingsFemElmerImp(); + +protected: + void saveSettings(); + void loadSettings(); + void changeEvent(QEvent *e); +}; + +} // namespace FemGui + +#endif // FEMGUI_DLGSETTINGSFEMELMERIMP_H diff --git a/src/Mod/Fem/Gui/Resources/Fem.qrc b/src/Mod/Fem/Gui/Resources/Fem.qrc index af198f224f..0f774c2d76 100755 --- a/src/Mod/Fem/Gui/Resources/Fem.qrc +++ b/src/Mod/Fem/Gui/Resources/Fem.qrc @@ -34,6 +34,10 @@ icons/fem-cylinder.svg icons/fem-DataAlongLine.svg icons/fem-data.svg + icons/fem-elmer.png + icons/fem-equation-elasticity.svg + icons/fem-equation-flow.svg + icons/fem-equation-heat.svg icons/fem-femmesh-boundary-layer.svg icons/fem-femmesh-clear-mesh.svg icons/fem-femmesh-create-node-by-poly.svg diff --git a/src/Mod/Fem/Gui/Resources/icons/fem-elmer.png b/src/Mod/Fem/Gui/Resources/icons/fem-elmer.png new file mode 100755 index 0000000000..c7c51100a8 Binary files /dev/null and b/src/Mod/Fem/Gui/Resources/icons/fem-elmer.png differ diff --git a/src/Mod/Fem/Gui/Resources/icons/fem-equation-elasticity.svg b/src/Mod/Fem/Gui/Resources/icons/fem-equation-elasticity.svg new file mode 100644 index 0000000000..abe47b1976 --- /dev/null +++ b/src/Mod/Fem/Gui/Resources/icons/fem-equation-elasticity.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + image/svg+xml + + + + + [Alexander Gryson] + + + fem-warp + 2017-03-11 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/ + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + diff --git a/src/Mod/Fem/Gui/Resources/icons/fem-equation-flow.svg b/src/Mod/Fem/Gui/Resources/icons/fem-equation-flow.svg new file mode 100644 index 0000000000..0dd00532c6 --- /dev/null +++ b/src/Mod/Fem/Gui/Resources/icons/fem-equation-flow.svg @@ -0,0 +1,258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + [qingfengxia] + + + fem-constraint-fluid-boundary + 2016-08-10 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/ + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Fem/Gui/Resources/icons/fem-equation-heat.svg b/src/Mod/Fem/Gui/Resources/icons/fem-equation-heat.svg new file mode 100644 index 0000000000..c1092a91f5 --- /dev/null +++ b/src/Mod/Fem/Gui/Resources/icons/fem-equation-heat.svg @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + [vdwalts] + + + fem-constraint-temperature + 2016-08-01 + http://www.freecadweb.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/ + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Fem/Gui/Workbench.cpp b/src/Mod/Fem/Gui/Workbench.cpp index 7016384e41..e37e020d51 100755 --- a/src/Mod/Fem/Gui/Workbench.cpp +++ b/src/Mod/Fem/Gui/Workbench.cpp @@ -112,6 +112,11 @@ Gui::ToolBarItem* Workbench::setupToolBars() const Gui::ToolBarItem* solve = new Gui::ToolBarItem(root); solve->setCommand("Solve"); *solve << "FEM_SolverCalculix" + << "FEM_AddSolverElmer" + << "Separator" + << "FEM_AddEquationHeat" + << "FEM_AddEquationElasticity" + << "FEM_AddEquationFlow" << "Separator" << "FEM_SolverControl" << "FEM_SolverRun"; @@ -209,6 +214,11 @@ Gui::MenuItem* Workbench::setupMenuBar() const solve->setCommand("&Solve"); *solve << "FEM_SolverCalculix" << "FEM_SolverZ88" + << "FEM_AddSolverElmer" + << "Separator" + << "FEM_AddEquationHeat" + << "FEM_AddEquationElasticity" + << "FEM_AddEquationFlow" << "Separator" << "FEM_SolverControl" << "FEM_SolverRun"; diff --git a/src/Mod/Fem/InitGui.py b/src/Mod/Fem/InitGui.py index 96d713176d..88fa63d901 100644 --- a/src/Mod/Fem/InitGui.py +++ b/src/Mod/Fem/InitGui.py @@ -67,6 +67,8 @@ class FemWorkbench (Workbench): import PyGui._CommandFemResultShow import PyGui._CommandFemResultsPurge import PyGui._CommandFemSolverCalculix + import PyGui._CommandFemSolverElmer + import PyGui._CommandFemEquation import PyGui._CommandFemSolverControl import PyGui._CommandFemSolverRun import PyGui._CommandFemSolverZ88 diff --git a/src/Mod/Fem/ObjectsFem.py b/src/Mod/Fem/ObjectsFem.py index 02ab1811c5..449c30b8bf 100644 --- a/src/Mod/Fem/ObjectsFem.py +++ b/src/Mod/Fem/ObjectsFem.py @@ -361,6 +361,13 @@ def makeSolverCalculix(doc, name="SolverCalculiX"): return obj +def makeSolverElmer(doc, name="SolverElmer"): + '''makeSolverElmer(document, [name]): makes a Elmer solver object''' + import femsolver.elmer.solver + obj = femsolver.elmer.solver.create(doc, name) + return obj + + def makeSolverZ88(doc, name="SolverZ88"): '''makeSolverZ88(document, [name]): makes a Z88 solver object''' import femsolver.z88.solver diff --git a/src/Mod/Fem/PyGui/_CommandFemEquation.py b/src/Mod/Fem/PyGui/_CommandFemEquation.py new file mode 100644 index 0000000000..259b0b569f --- /dev/null +++ b/src/Mod/Fem/PyGui/_CommandFemEquation.py @@ -0,0 +1,102 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "_CommandFemEquation" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +from PySide import QtCore + +import FreeCAD as App +import FreeCADGui as Gui +import FemUtils + + +class _Base(QtCore.QObject): + + def getSpecifier(self): + raise NotImplementedError() + + def Activated(self): + s = Gui.Selection.getSelection() + if len(s) == 1 and FemUtils.isDerivedFrom(s[0], "Fem::FemSolverObject"): + App.ActiveDocument.openTransaction( + "Add %s equation to %s" + % (self.getSpecifier(), s[0].Label)) + Gui.doCommand( + "App.ActiveDocument.%(obj)s.Proxy.addEquation(" + "App.ActiveDocument.%(obj)s, '%(name)s')" + % {"obj": s[0].Name, "name": self.getSpecifier()}) + App.ActiveDocument.commitTransaction() + App.ActiveDocument.recompute() + + def IsActive(self): + s = Gui.Selection.getSelection() + if len(s) == 1 and FemUtils.isDerivedFrom(s[0], "Fem::FemSolverObject"): + return s[0].Proxy.isSupported(self.getSpecifier()) + return False + + +class Heat(_Base): + + def getSpecifier(self): + return "Heat" + + def GetResources(self): + return { + 'Pixmap': 'fem-equation-heat', + 'MenuText': "Heat Equation", + 'ToolTip': "Creates a FEM constraint body heat flux" + } + + +class Elasticity(_Base): + + def getSpecifier(self): + return "Elasticity" + + def GetResources(self): + return { + 'Pixmap': 'fem-equation-elasticity', + 'MenuText': "Elasticity Equation", + 'ToolTip': "Creates a FEM constraint for elasticity" + } + + +class Flow(_Base): + + def getSpecifier(self): + return "Flow" + + def GetResources(self): + return { + 'Pixmap': 'fem-equation-flow', + 'MenuText': "Flow Equation", + 'ToolTip': "Creates a FEM constraint body heat flux" + } + + +Gui.addCommand('FEM_AddEquationHeat', Heat()) +Gui.addCommand('FEM_AddEquationElasticity', Elasticity()) +Gui.addCommand('FEM_AddEquationFlow', Flow()) diff --git a/src/Mod/Fem/PyGui/_CommandFemSolverElmer.py b/src/Mod/Fem/PyGui/_CommandFemSolverElmer.py new file mode 100644 index 0000000000..38f274550d --- /dev/null +++ b/src/Mod/Fem/PyGui/_CommandFemSolverElmer.py @@ -0,0 +1,63 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "Elmer" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +from PySide import QtCore + +import FreeCAD as App +import FreeCADGui as Gui +import FemGui + + +class Command(QtCore.QObject): + + def Activated(self): + analysis = FemGui.getActiveAnalysis() + App.ActiveDocument.openTransaction("Create Elmer solver object") + Gui.addModule("femsolver.elmer.solver") + Gui.doCommand( + "App.ActiveDocument.%s.Member += " + "[femsolver.elmer.solver.create(App.ActiveDocument)]" + % analysis.Name) + App.ActiveDocument.commitTransaction() + App.ActiveDocument.recompute() + + def GetResources(self): + return { + 'Pixmap': 'fem-elmer', + 'MenuText': "Solver Elmer", + 'Accel': "S, E", + 'ToolTip': "Creates a FEM solver Elmer" + } + + def IsActive(self): + analysis = FemGui.getActiveAnalysis() + return (analysis is not None + and analysis.Document == App.ActiveDocument) + + +Gui.addCommand('FEM_AddSolverElmer', Command()) diff --git a/src/Mod/Fem/PyGui/_CommandFemSolverRun.py b/src/Mod/Fem/PyGui/_CommandFemSolverRun.py index 58b2f3b383..0c7d41f53d 100644 --- a/src/Mod/Fem/PyGui/_CommandFemSolverRun.py +++ b/src/Mod/Fem/PyGui/_CommandFemSolverRun.py @@ -54,6 +54,8 @@ class _CommandFemSolverRun(FemCommands): self.solver = FreeCADGui.Selection.getSelection()[0] # see 'with_solver' in FemCommands for selection check if FemUtils.isDerivedFrom(self.solver, "Fem::FemSolverObjectZ88"): self._newActivated() + elif FemUtils.isDerivedFrom(self.solver, "Fem::FemSolverObjectElmer"): + self._newActivated() elif FemUtils.isDerivedFrom(self.solver, "Fem::FemSolverObjectCalculix"): self._newActivated() elif self.solver.SolverType == "FemSolverCalculix": diff --git a/src/Mod/Fem/femsolver/elmer/__init__.py b/src/Mod/Fem/femsolver/elmer/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Fem/femsolver/elmer/equations/__init__.py b/src/Mod/Fem/femsolver/elmer/equations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/Mod/Fem/femsolver/elmer/equations/elasticity.py b/src/Mod/Fem/femsolver/elmer/equations/elasticity.py new file mode 100644 index 0000000000..a15c150c2d --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/equations/elasticity.py @@ -0,0 +1,68 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "Elasticity" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +import FemUtils +from ... import equationbase +from . import linear + + +def create(doc, name="Elasticity"): + return FemUtils.createObject( + doc, name, Proxy, ViewProxy) + + +class Proxy(linear.Proxy, equationbase.ElasticityProxy): + + Type = "Fem::FemEquationElmerElasticity" + + def __init__(self, obj): + super(Proxy, self).__init__(obj) + obj.addProperty( + "App::PropertyBool", "DoFrequencyAnalysis", + "Elasticity", "Select type of solver for linear system") + obj.addProperty( + "App::PropertyInteger", "EigenmodesCount", + "Elasticity", "Select type of solver for linear system") + obj.addProperty( + "App::PropertyBool", "CalculateStrains", + "Elasticity", "Select type of solver for linear system") + obj.addProperty( + "App::PropertyBool", "CalculateStresses", + "Elasticity", "Select type of solver for linear system") + obj.addProperty( + "App::PropertyBool", "CalculatePrincipal", + "Elasticity", "Select type of solver for linear system") + obj.addProperty( + "App::PropertyBool", "CalculatePangle", + "Elasticity", "Select type of solver for linear system") + obj.EigenmodesCount = 5 + obj.Priority = 10 + + +class ViewProxy(linear.ViewProxy, equationbase.ElasticityViewProxy): + pass diff --git a/src/Mod/Fem/femsolver/elmer/equations/equation.py b/src/Mod/Fem/femsolver/elmer/equations/equation.py new file mode 100644 index 0000000000..d88b4901f8 --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/equations/equation.py @@ -0,0 +1,111 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "Equation" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +import FreeCAD as App +from ... import equationbase +import FemUtils + +if App.GuiUp: + import FreeCADGui as Gui + import PyGui.FemSelectionWidgets as FemSelectionWidgets + + +class Proxy(equationbase.BaseProxy): + + def __init__(self, obj): + super(Proxy, self).__init__(obj) + obj.addProperty( + "App::PropertyInteger", "Priority", + "Base", "Select type of solver for linear system") + + +class ViewProxy(equationbase.BaseViewProxy): + + def setEdit(self, vobj, mode=0): + task = _TaskPanel(vobj.Object) + Gui.Control.showDialog(task) + + def unsetEdit(self, vobj, mode=0): + Gui.Control.closeDialog() + + def doubleClicked(self, vobj): + if Gui.Control.activeDialog(): + Gui.Control.closeDialog() + Gui.ActiveDocument.setEdit(vobj.Object.Name) + return True + + def getTaskWidget(self, vobj): + return None + + +class _TaskPanel(object): + + def __init__(self, obj): + self._obj = obj + self._refWidget = FemSelectionWidgets.SolidSelector() + self._refWidget.setReferences(obj.References) + propWidget = obj.ViewObject.Proxy.getTaskWidget( + obj.ViewObject) + if propWidget is None: + self.form = self._refWidget + else: + self.form = [self.refWidget, propWidget] + analysis = FemUtils.findAnalysisOfMember(obj) + self._mesh = FemUtils.getSingleMember(analysis, "Fem::FemMeshObject") + self._part = self._mesh.Part if self._mesh is not None else None + self._partVisible = None + self._meshVisible = None + + def open(self): + if self._mesh is not None and self._part is not None: + self._meshVisible = self._mesh.ViewObject.isVisible() + self._partVisible = self._part.ViewObject.isVisible() + self._mesh.ViewObject.hide() + self._part.ViewObject.show() + + def reject(self): + self._restoreVisibility() + return True + + def accept(self): + if self._obj.References != self._refWidget.references(): + self._obj.References = self._refWidget.references() + self._obj.Document.recompute() + self._restoreVisibility() + return True + + def _restoreVisibility(self): + if self._mesh is not None and self._part is not None: + if self._meshVisible: + self._mesh.ViewObject.show() + else: + self._mesh.ViewObject.hide() + if self._partVisible: + self._part.ViewObject.show() + else: + self._part.ViewObject.hide() diff --git a/src/Mod/Fem/femsolver/elmer/equations/flow.py b/src/Mod/Fem/femsolver/elmer/equations/flow.py new file mode 100644 index 0000000000..786474d586 --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/equations/flow.py @@ -0,0 +1,49 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "Flow" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +import FemUtils +from . import nonlinear +from ... import equationbase + + +def create(doc, name="Flow"): + return FemUtils.createObject( + doc, name, Proxy, ViewProxy) + + +class Proxy(nonlinear.Proxy, equationbase.FlowProxy): + + Type = "Fem::FemEquationElmerFlow" + + def __init__(self, obj): + super(Proxy, self).__init__(obj) + obj.Priority = 10 + + +class ViewProxy(nonlinear.ViewProxy, equationbase.FlowViewProxy): + pass diff --git a/src/Mod/Fem/femsolver/elmer/equations/heat.py b/src/Mod/Fem/femsolver/elmer/equations/heat.py new file mode 100644 index 0000000000..f992982ac7 --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/equations/heat.py @@ -0,0 +1,49 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "Heat" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +import FemUtils +from . import nonlinear +from ... import equationbase + + +def create(doc, name="Heat"): + return FemUtils.createObject( + doc, name, Proxy, ViewProxy) + + +class Proxy(nonlinear.Proxy, equationbase.HeatProxy): + + Type = "Fem::FemEquationElmerHeat" + + def __init__(self, obj): + super(Proxy, self).__init__(obj) + obj.Priority = 20 + + +class ViewProxy(nonlinear.ViewProxy, equationbase.HeatViewProxy): + pass diff --git a/src/Mod/Fem/femsolver/elmer/equations/linear.py b/src/Mod/Fem/femsolver/elmer/equations/linear.py new file mode 100644 index 0000000000..6181d4e7ee --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/equations/linear.py @@ -0,0 +1,103 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "_Linear" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +from . import equation + + +LINEAR_SOLVER = ["Direct", "Iterative"] +LINEAR_DIRECT = ["Banded", "umfpack"] +LINEAR_ITERATIVE = [ + "CG", + "CGS", + "BiCGStab", + "BiCGStabl", + "TFQMR", + "GMRES", + "GCR", +] +LINEAR_PRECONDITIONING = [ + "None", + "Diagonal", + "ILU0", + "ILU1", + "ILU2", + "ILU3", + "ILU4", +] + + +class Proxy(equation.Proxy): + + def __init__(self, obj): + super(Proxy, self).__init__(obj) + obj.addProperty( + "App::PropertyEnumeration", "LinearSolverType", + "Linear System", "Select type of solver for linear system") + obj.LinearSolverType = LINEAR_SOLVER + obj.LinearSolverType = "Iterative" + obj.addProperty( + "App::PropertyEnumeration", "LinearDirectMethod", + "Linear System", "Select type of solver for linear system") + obj.LinearDirectMethod = LINEAR_DIRECT + obj.addProperty( + "App::PropertyEnumeration", "LinearIterativeMethod", + "Linear System", "Select type of solver for linear system") + obj.LinearIterativeMethod = LINEAR_ITERATIVE + obj.LinearIterativeMethod = "BiCGStab" + obj.addProperty( + "App::PropertyInteger", "BiCGstablDegree", + "Linear System", "Select type of solver for linear system") + obj.addProperty( + "App::PropertyEnumeration", "LinearPreconditioning", + "Linear System", "Select type of solver for linear system") + obj.LinearPreconditioning = LINEAR_PRECONDITIONING + obj.LinearPreconditioning = "ILU0" + obj.addProperty( + "App::PropertyFloat", "LinearTolerance", + "Linear System", "Select type of solver for linear system") + obj.LinearTolerance = 1e-8 + obj.addProperty( + "App::PropertyInteger", "LinearIterations", + "Linear System", "Select type of solver for linear system") + obj.LinearIterations = 500 + obj.addProperty( + "App::PropertyFloat", "SteadyStateTolerance", + "Steady State", "Select type of solver for linear system") + obj.SteadyStateTolerance = 1e-5 + obj.addProperty( + "App::PropertyBool", "Stabilize", + "Base", "Select type of solver for linear system") + obj.Stabilize = True + obj.addProperty( + "App::PropertyBool", "Bubbles", + "Base", "Select type of solver for linear system") + obj.Bubbles = False + + +class ViewProxy(equation.ViewProxy): + pass diff --git a/src/Mod/Fem/femsolver/elmer/equations/nonlinear.py b/src/Mod/Fem/femsolver/elmer/equations/nonlinear.py new file mode 100644 index 0000000000..e527308583 --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/equations/nonlinear.py @@ -0,0 +1,59 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "_NonLinear" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +from . import linear + + +class Proxy(linear.Proxy): + + def __init__(self, obj): + super(Proxy, self).__init__(obj) + obj.addProperty( + "App::PropertyFloat", "NonlinearTolerance", + "Nonlinear System", "Select type of solver for linear system") + obj.addProperty( + "App::PropertyInteger", "NonlinearIterations", + "Nonlinear System", "Select type of solver for linear system") + obj.addProperty( + "App::PropertyFloat", "RelaxationFactor", + "Nonlinear System", "Select type of solver for linear system") + obj.addProperty( + "App::PropertyInteger", "NonlinearNewtonAfterIterations", + "Nonlinear System", "Select type of solver for linear system") + obj.addProperty( + "App::PropertyFloat", "NonlinearNewtonAfterTolerance", + "Nonlinear System", "Select type of solver for linear system") + obj.NonlinearTolerance = 1e-8 + obj.NonlinearIterations = 500 + obj.RelaxationFactor = 1 + obj.NonlinearNewtonAfterIterations = 3 + obj.NonlinearNewtonAfterTolerance = 1e-3 + + +class ViewProxy(linear.ViewProxy): + pass diff --git a/src/Mod/Fem/femsolver/elmer/sifio.py b/src/Mod/Fem/femsolver/elmer/sifio.py new file mode 100644 index 0000000000..2ce13d8e5f --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/sifio.py @@ -0,0 +1,429 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2016 - Markus Hovorka * +# * * +# * 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 * +# * * +# *************************************************************************** + + +import collections + + +SIMULATION = "Simulation" +CONSTANTS = "Constants" +BODY = "Body" +MATERIAL = "Material" +BODY_FORCE = "Body Force" +EQUATION = "Equation" +SOLVER = "Solver" +BOUNDARY_CONDITION = "Boundary Condition" +INITIAL_CONDITION = "Initial Condition" +COMPONENT = "Component" + + +_VALID_SECTIONS = ( + SIMULATION, + CONSTANTS, + BODY, + MATERIAL, + BODY_FORCE, + EQUATION, + SOLVER, + BOUNDARY_CONDITION, + INITIAL_CONDITION, + COMPONENT, +) + + +_NUMBERED_SECTIONS = ( + BODY, + MATERIAL, + BODY_FORCE, + EQUATION, + SOLVER, + BOUNDARY_CONDITION, + INITIAL_CONDITION, + COMPONENT, +) + + +_SECTION_DELIM = "End" +_WHITESPACE = " " +_INDENT = " " * 2 +_NEWLINE = "\n" + + +_TYPE_REAL = "Real" +_TYPE_INTEGER = "Integer" +_TYPE_LOGICAL = "Logical" +_TYPE_STRING = "String" +_TYPE_FILE = "File" + + +WARN = "Warn" +IGNORE = "Ignore" +ABORT = "Abort" +SILENT = "Silent" + + +def createSection(name): + section = Section(name) + if not isValid(section): + raise ValueError("Invalid section name: %s" % name) + return section + + +def writeSections(sections, stream): + ids = _IdManager() + _Writer(ids, sections, stream).write() + + +def isNumbered(section): + return section.name in _NUMBERED_SECTIONS + + +def isValid(section): + return section.name in _VALID_SECTIONS + + +class Builder(object): + + _ACTIVE_SOLVERS = "Active Solvers" + + def __init__(self): + self._customSections = set() + self._simulation = createSection(SIMULATION) + self._constants = createSection(CONSTANTS) + self._bodies = {} + self._boundaries = {} + + def getBoundaryNames(self): + return self._boundaries.keys() + + def getBodyNames(self): + return self._bodies.keys() + + def simulation(self, key, attr): + self._simulation[key] = attr + + def constant(self, key, attr): + self._constants[key] = attr + + def initial(self, body, key, attr): + section = self._getFromBody(body, INITIAL_CONDITION) + section[key] = attr + + def material(self, body, key, attr): + section = self._getFromBody(body, MATERIAL) + section[key] = attr + + def equation(self, body, key, attr): + section = self._getFromBody(body, EQUATION) + section[key] = attr + + def bodyForce(self, body, key, attr): + section = self._getFromBody(body, BODY_FORCE) + section[key] = attr + + def addSolver(self, body, solverSection): + section = self._getFromBody(body, EQUATION) + if self._ACTIVE_SOLVERS not in section: + section[self._ACTIVE_SOLVERS] = [] + section[self._ACTIVE_SOLVERS].append(solverSection) + + def boundary(self, boundary, key, attr): + if boundary not in self._boundaries: + self._boundaries[boundary] = createSection(BOUNDARY_CONDITION) + self._boundaries[boundary][key] = attr + + def addSection(self, section): + self._customSections.add(section) + + def _getFromBody(self, body, key): + if body not in self._bodies: + self._bodies[body] = createSection(BODY) + if key not in self._bodies[body]: + self._bodies[body][key] = createSection(key) + return self._bodies[body][key] + + def __iter__(self): + allSections = set(self._customSections) + allSections.add(self._simulation) + allSections.add(self._constants) + for name, section in self._bodies.iteritems(): + section["Name"] = name + allSections.add(section) + if MATERIAL in section: + allSections.add(section[MATERIAL]) + if EQUATION in section: + eqSection = section[EQUATION] + allSections.add(eqSection) + if self._ACTIVE_SOLVERS in eqSection: + for solverSection in eqSection[self._ACTIVE_SOLVERS]: + allSections.add(solverSection) + if BODY_FORCE in section: + allSections.add(section[BODY_FORCE]) + if INITIAL_CONDITION in section: + allSections.add(section[INITIAL_CONDITION]) + for name, section in self._boundaries.iteritems(): + section["Name"] = name + allSections.add(section) + return iter(allSections) + + +class Sif(object): + + _CHECK_KEYWORDS = "Check Keywords" + _HEADER = "Header" + _MESHDB_ATTR = "Mesh DB" + _INCLUDE_ATTR = "Include Path" + _RESULT_ATTR = "Results Directory" + + def __init__(self, sections=[], meshLocation="."): + self.sections = sections + self.meshPath = meshLocation + self.checkKeywords = WARN + self.incPath = "" + self.resPath = "" + + def write(self, stream): + self._writeCheckKeywords(stream) + stream.write(_NEWLINE * 2) + self._writeHeader(stream) + stream.write(_NEWLINE * 2) + writeSections(self.sections, stream) + + def _writeCheckKeywords(self, stream): + stream.write(self._CHECK_KEYWORDS) + stream.write(_WHITESPACE) + stream.write(self.checkKeywords) + + def _writeHeader(self, stream): + stream.write(self._HEADER) + stream.write(_NEWLINE) + self._writeAttr(self._MESHDB_ATTR, self.meshPath, stream) + stream.write(_NEWLINE) + if self.incPath: + self._writeAttr(self._INCLUDE_ATTR, self.incPath, stream) + stream.write(_NEWLINE) + if self.resPath: + self._writeAttr(self._RESULT_ATTR, self.resPath, stream) + stream.write(_NEWLINE) + stream.write(_SECTION_DELIM) + + def _writeAttr(self, name, value, stream): + stream.write(_INDENT) + stream.write(name) + stream.write(_WHITESPACE) + stream.write('"%s"' % value) + + +class Section(object): + + def __init__(self, name): + self.name = name + self.priority = 0 + self._attrs = dict() + + def __setitem__(self, key, value): + self._attrs[key] = value + + def __getitem__(self, key): + return self._attrs[key] + + def __delitem__(self, key): + del self._attrs[key] + + def __iter__(self): + return self._attrs.iteritems() + + def __contains__(self, item): + return item in self._attrs + + def __str__(self): + return str(self._attrs) + + def __repr__(self): + return self.name + + +class FileAttr(str): + pass + + +class _Writer(object): + + def __init__(self, idManager, sections, stream): + self._idMgr = idManager + self._sections = sections + self._stream = stream + + def write(self): + sortedSections = sorted( + self._sections, key=lambda s: s.priority, reverse=True) + for s in sortedSections: + self._writeSection(s) + self._stream.write(_NEWLINE) + + def _writeSection(self, s): + self._writeSectionHeader(s) + self._writeSectionBody(s) + self._writeSectionFooter(s) + self._stream.write(_NEWLINE) + + def _writeSectionHeader(self, s): + self._stream.write(s.name) + self._stream.write(_WHITESPACE) + if isNumbered(s): + self._stream.write(str(self._idMgr.getId(s))) + + def _writeSectionFooter(self, s): + self._stream.write(_NEWLINE) + self._stream.write(_SECTION_DELIM) + + def _writeSectionBody(self, s): + for key, data in s: + self._writeAttribute(key, data) + + def _writeAttribute(self, key, data): + if isinstance(data, Section): + self._stream.write(_NEWLINE) + self._writeScalarAttr(key, data) + elif isinstance(data, FileAttr): + self._stream.write(_NEWLINE) + self._writeFileAttr(key, data) + elif self._isCollection(data): + if len(data) == 1: + scalarData = self._getOnlyElement(data) + self._stream.write(_NEWLINE) + self._writeScalarAttr(key, scalarData) + elif len(data) > 1: + self._stream.write(_NEWLINE) + self._writeArrAttr(key, data) + else: + self._stream.write(_NEWLINE) + self._writeScalarAttr(key, data) + + def _getOnlyElement(self, collection): + it = iter(collection) + return it.next() + + def _isCollection(self, data): + return (not isinstance(data, basestring) + and isinstance(data, collections.Iterable)) + + def _checkScalar(self, dataType): + if issubclass(dataType, int): + return self._genIntAttr + if issubclass(dataType, float): + return self._genFloatAttr + if issubclass(dataType, bool): + return self._genBoolAttr + if issubclass(dataType, basestring): + return self._genStrAttr + return None + + def _writeScalarAttr(self, key, data): + attrType = self._getAttrTypeScalar(data) + if attrType is None: + raise ValueError("Unsupported data type: %s" % type(data)) + self._stream.write(_INDENT) + self._stream.write(key) + self._stream.write(_WHITESPACE) + self._stream.write("=") + self._stream.write(_WHITESPACE) + self._stream.write(attrType) + self._stream.write(_WHITESPACE) + self._stream.write(self._preprocess(data, type(data))) + + def _writeArrAttr(self, key, data): + attrType = self._getAttrTypeArr(data) + self._stream.write(_INDENT) + self._stream.write(key) + self._stream.write("(%d)" % len(data)) + self._stream.write(_WHITESPACE) + self._stream.write("=") + self._stream.write(_WHITESPACE) + self._stream.write(attrType) + for val in data: + self._stream.write(_WHITESPACE) + self._stream.write(self._preprocess(val, type(val))) + + def _writeFileAttr(self, key, data): + self._stream.write(_INDENT) + self._stream.write(key) + self._stream.write(_WHITESPACE) + self._stream.write("=") + self._stream.write(_WHITESPACE) + self._stream.write(_TYPE_FILE) + for val in data.split("/"): + if val: + self._stream.write(_WHITESPACE) + self._stream.write('"%s"' % val) + + def _getSifDataType(self, dataType): + if issubclass(dataType, Section): + return _TYPE_INTEGER + if issubclass(dataType, bool): + return _TYPE_LOGICAL + if issubclass(dataType, int): + return _TYPE_INTEGER + if issubclass(dataType, float): + return _TYPE_REAL + if issubclass(dataType, basestring): + return _TYPE_STRING + raise ValueError("Unsupported data type: %s" % dataType) + + def _preprocess(self, data, dataType): + if issubclass(dataType, Section): + return str(self._idMgr.getId(data)) + if issubclass(dataType, basestring): + return '"%s"' % data + return str(data) + + def _getAttrTypeScalar(self, data): + return self._getSifDataType(type(data)) + + def _getAttrTypeArr(self, data): + if not data: + raise ValueError("Collections must not be empty.") + it = iter(data) + dataType = type(it.next()) + for entry in it: + if not isinstance(entry, dataType): + raise ValueError("Collection must be homogenueous") + return self._getSifDataType(dataType) + + +class _IdManager(object): + + def __init__(self, firstId=1): + self._pool = dict() + self._ids = dict() + self.firstId = firstId + + def setId(self, section): + if section.name not in self._pool: + self._pool[section.name] = self.firstId + self._ids[section] = self._pool[section.name] + self._pool[section.name] += 1 + + def getId(self, section): + if section not in self._ids: + self.setId(section) + return self._ids[section] diff --git a/src/Mod/Fem/femsolver/elmer/solver.py b/src/Mod/Fem/femsolver/elmer/solver.py new file mode 100644 index 0000000000..18078dd9ef --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/solver.py @@ -0,0 +1,93 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "Elmer" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +import FemUtils + +from .. import run +from .. import solverbase +from . import tasks + +from .equations import heat +from .equations import elasticity +from .equations import flow + + +def create(doc, name="ElmerSolver"): + return FemUtils.createObject( + doc, name, Proxy, ViewProxy) + + +class Proxy(solverbase.Proxy): + """Proxy for FemSolverElmers Document Object.""" + + Type = "Fem::FemSolverObjectElmer" + + _EQUATIONS = { + "Heat": heat, + "Elasticity": elasticity, + "Flow": flow, + } + + def __init__(self, obj): + super(Proxy, self).__init__(obj) + obj.addProperty( + "App::PropertyInteger", "SteadyStateMaxIterations", + "Steady State", "") + obj.addProperty( + "App::PropertyInteger", "SteadyStateMinIterations", + "Steady State", "") + obj.addProperty( + "App::PropertyLink", "ElmerResult", + "Base", "", 4 | 8) + obj.addProperty( + "App::PropertyLink", "ElmerOutput", + "Base", "", 4 | 8) + + obj.SteadyStateMaxIterations = 1 + obj.SteadyStateMinIterations = 0 + + def createMachine(self, obj, directory): + return run.Machine( + solver=obj, directory=directory, + check=tasks.Check(), + prepare=tasks.Prepare(), + solve=tasks.Solve(), + results=tasks.Results()) + + def createEquation(self, doc, eqId): + return self._EQUATIONS[eqId].create(doc) + + def isSupported(self, eqId): + return eqId in self._EQUATIONS + + +class ViewProxy(solverbase.ViewProxy): + """Proxy for FemSolverElmers View Provider.""" + + def getIcon(self): + return ":/icons/fem-elmer.png" diff --git a/src/Mod/Fem/femsolver/elmer/tasks.py b/src/Mod/Fem/femsolver/elmer/tasks.py new file mode 100644 index 0000000000..d41649d4c8 --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/tasks.py @@ -0,0 +1,148 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "FemElmerTasks" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +import subprocess +import os.path +import FemUtils + +from .. import run +from .. import settings +from . import writer + + +class Check(run.Check): + + def run(self): + self.pushStatus("Checking analysis...\n") + if (self.checkMesh()): + self.checkMeshType() + self.checkMaterial() + self.checkEquations() + + def checkMeshType(self): + mesh = FemUtils.getSingleMember(self.analysis, "Fem::FemMeshObject") + if not FemUtils.isOfType(mesh, "FemMeshGmsh"): + self.report.error( + "Unsupported type of mesh. " + "Mesh must be created with gmsh.") + self.fail() + return False + return True + + def checkEquations(self): + equations = self.solver.Group + if not equations: + self.report.error( + "Solver has no equations. " + "Add at least one equation.") + self.fail() + + +class Prepare(run.Prepare): + + def run(self): + self.pushStatus("Preparing input files...\n") + w = writer.Writer(self.solver, self.directory) + try: + w.write() + self.checkHandled(w) + except writer.WriteError as e: + self.report.error(str(e)) + self.fail() + except IOError as e: + self.report.error("Can't access working directory.") + self.fail() + + def checkHandled(self, w): + handled = w.getHandledConstraints() + allConstraints = FemUtils.getMember(self.analysis, "Fem::Constraint") + for obj in set(allConstraints) - handled: + self.report.warning("Ignored constraint %s." % obj.Label) + + +class Solve(run.Solve): + + def run(self): + self.pushStatus("Executing solver...\n") + binary = settings.getBinary("ElmerSolver") + if binary is not None: + self._process = subprocess.Popen( + [binary], cwd=self.directory, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + self.signalAbort.add(self._process.terminate) + output = self._observeSolver(self._process) + self._process.communicate() + self.signalAbort.remove(self._process.terminate) + if not self.aborted: + self._updateOutput(output) + else: + self.report.error("ElmerSolver executable not found.") + self.fail() + + def _observeSolver(self, process): + output = "" + line = process.stdout.readline() + self.pushStatus(line) + output += line + line = process.stdout.readline() + while line: + line = "\n%s" % line.rstrip() + self.pushStatus(line) + output += line + line = process.stdout.readline() + return output + + def _updateOutput(self, output): + if self.solver.ElmerOutput is None: + self._createOutput() + self.solver.ElmerOutput.Text = output + + def _createOutput(self): + self.solver.ElmerOutput = self.analysis.Document.addObject( + "App::TextDocument", self.solver.Name + "Output") + self.solver.ElmerOutput.Label = self.solver.Label + "Output" + self.solver.ElmerOutput.ReadOnly = True + self.analysis.Member += [self.solver.ElmerOutput] + + +class Results(run.Results): + + def run(self): + if self.solver.ElmerResult is None: + self._createResults() + postPath = os.path.join(self.directory, "case0001.vtu") + self.solver.ElmerResult.read(postPath) + self.solver.ElmerResult.getLastPostObject().touch() + self.solver.Document.recompute() + + 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.analysis.Member += [self.solver.ElmerResult] diff --git a/src/Mod/Fem/femsolver/elmer/writer.py b/src/Mod/Fem/femsolver/elmer/writer.py new file mode 100644 index 0000000000..b585286b44 --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/writer.py @@ -0,0 +1,682 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "FemWriterElmer" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +import os +import os.path +import subprocess +import tempfile + +from FreeCAD import Units +import Fem +import FemUtils +import FemGmshTools +from .. import settings +from . import sifio + + +_STARTINFO_NAME = "ELMERSOLVER_STARTINFO" +_SIF_NAME = "case.sif" +_ELMERGRID_IFORMAT = "8" +_ELMERGRID_OFORMAT = "2" +_SOLID_PREFIX = "Solid" + + +UNITS = { + "L": "mm", + "M": "kg", + "T": "s", + "I": "A", + "O": "K", + "N": "mol", + "J": "cd", +} + + +CONSTS_DEF = { + "Gravity": "9.82 m/s^2", + "StefanBoltzmann": "5.67e-8 W/(m^2*K^4)", + "PermittivityOfVacuum": "8.8542e-12 s^4*A^2/(m*kg)", + "BoltzmannConstant": "1.3807e-23 J/K", +} + + +def getFromUi(value, unit, outputDim): + quantity = Units.Quantity(str(value) + str(unit)) + return convert(quantity, outputDim) + + +def convert(quantityStr, unit): + quantity = Units.Quantity(quantityStr) + for key, setting in UNITS.iteritems(): + unit = unit.replace(key, setting) + return float(quantity.getValueAs(unit)) + + +def _getAllSubObjects(obj): + s = ["Solid" + str(i + 1) for i in range(len(obj.Shape.Solids))] + s.extend(("Face" + str(i + 1) for i in range(len(obj.Shape.Faces)))) + s.extend(("Edge" + str(i + 1) for i in range(len(obj.Shape.Edges)))) + s.extend(("Vertex" + str(i + 1) for i in range(len(obj.Shape.Vertexes)))) + return s + + +def getConstant(name, dimension): + return convert(CONSTS_DEF[name], dimension) + + +class Writer(object): + + def __init__(self, solver, directory): + self.analysis = FemUtils.findAnalysisOfMember(solver) + self.solver = solver + self.directory = directory + self._usedVarNames = set() + self._builder = sifio.Builder() + self._handledObjects = set() + + def getHandledConstraints(self): + return self._handledObjects + + def write(self): + self._handleSimulation() + self._handleHeat() + self._handleElasticity() + self._handleFlow() + self._addOutputSolver() + + self._writeSif() + self._writeMesh() + self._writeStartinfo() + + def _writeMesh(self): + mesh = FemUtils.getSingleMember(self.analysis, "Fem::FemMeshObject") + unvPath = os.path.join(self.directory, "mesh.unv") + groups = [] + groups.extend(self._builder.getBodyNames()) + groups.extend(self._builder.getBoundaryNames()) + self._exportToUnv(groups, mesh, unvPath) + binary = settings.getBinary("ElmerGrid") + if binary is None: + raise WriteError("Couldn't find ElmerGrid binary.") + args = [binary, + _ELMERGRID_IFORMAT, + _ELMERGRID_OFORMAT, + unvPath, + "-out", self.directory] + subprocess.call(args) + + def _writeStartinfo(self): + path = os.path.join(self.directory, _STARTINFO_NAME) + with open(path, 'w') as f: + f.write(_SIF_NAME) + + def _exportToUnv(self, groups, mesh, meshPath): + unvGmshFd, unvGmshPath = tempfile.mkstemp(suffix=".unv") + brepFd, brepPath = tempfile.mkstemp(suffix=".brep") + geoFd, geoPath = tempfile.mkstemp(suffix=".geo") + os.close(brepFd) + os.close(geoFd) + os.close(unvGmshFd) + + tools = FemGmshTools.FemGmshTools(mesh) + tools.group_elements = {g: [g] for g in groups} + tools.ele_length_map = {} + tools.temp_file_geometry = brepPath + tools.temp_file_geo = geoPath + tools.temp_file_mesh = unvGmshPath + + tools.get_dimension() + tools.get_gmsh_command() + tools.get_region_data() + tools.get_boundary_layer_data() + tools.write_part_file() + tools.write_geo() + tools.run_gmsh_with_geo() + + ioMesh = Fem.FemMesh() + ioMesh.read(unvGmshPath) + ioMesh.write(meshPath) + + os.remove(brepPath) + os.remove(geoPath) + os.remove(unvGmshPath) + + def _handleSimulation(self): + self._simulation("Coordinate System", "Cartesian 3D") + self._simulation("Coordinate Mapping", (1, 2, 3)) + self._simulation("Simulation Type", "Steady state") + self._simulation("Steady State Max Iterations", 1) + self._simulation("Output Intervals", 1) + self._simulation("Timestepping Method", "BDF") + self._simulation("BDF Order", 1) + self._simulation("Use Mesh Names", True) + self._simulation( + "Steady State Max Iterations", + self.solver.SteadyStateMaxIterations) + self._simulation( + "Steady State Min Iterations", + self.solver.SteadyStateMinIterations) + + def _handleHeat(self): + activeIn = [] + for equation in self.solver.Group: + if FemUtils.isOfType(equation, "Fem::FemEquationElmerHeat"): + if equation.References: + activeIn = equation.References[0][1] + else: + activeIn = self._getAllBodies() + solverSection = self._getHeatSolver(equation) + for body in activeIn: + self._addSolver(body, solverSection) + if activeIn: + self._handleHeatConstants() + self._handleHeatBndConditions() + self._handleHeatInitial(activeIn) + self._handleHeatBodyForces(activeIn) + self._handleHeatMaterial(activeIn) + + def _getHeatSolver(self, equation): + s = self._createNonlinearSolver(equation) + s["Equation"] = equation.Name + s["Procedure"] = sifio.FileAttr("HeatSolve/HeatSolver") + s["Variable"] = self._getUniqueVarName("Temperature") + s["Exec Solver"] = "Always" + s["Stabilize"] = equation.Stabilize + s["Bubbles"] = equation.Bubbles + s["Optimize Bandwidth"] = True + return s + + def _handleHeatConstants(self): + self._constant( + "Stefan Boltzmann", + getConstant("StefanBoltzmann", "M/(O^4*T^3)")) + + def _handleHeatBndConditions(self): + for obj in self._getMember("Fem::ConstraintTemperature"): + if obj.References: + for name in obj.References[0][1]: + if obj.ConstraintType == "Temperature": + temp = getFromUi(obj.Temperature, "K", "O") + self._boundary(name, "Temperature", temp) + elif obj.ConstraintType == "CFlux": + flux = getFromUi(obj.CFlux, "kg*mm^2*s^-3", "M*L^2*T^-3") + self._boundary(name, "Temperature Load", flux) + self._handled(obj) + for obj in self._getMember("Fem::ConstraintHeatflux"): + if obj.References: + for name in obj.References[0][1]: + if obj.ConstraintType == "Convection": + film = getFromUi(obj.FilmCoef, "W/(m^2*K)", "M/(T^3*O)") + temp = getFromUi(obj.AmbientTemp, "K", "O") + self._boundary(name, "Heat Transfer Coefficient", film) + self._boundary(name, "External Temperature", temp) + elif obj.ConstraintType == "DFlux": + flux = getFromUi(obj.DFlux, "W/m^2", "M*T^-3") + self._boundary(name, "Heat Flux BC", True) + self._boundary(name, "Heat Flux", flux) + self._handled(obj) + + def _handleHeatInitial(self, bodies): + obj = self._getSingleMember("Fem::ConstraintInitialTemperature") + if obj is not None: + for name in bodies: + temp = getFromUi(obj.initialTemperature, "K", "O") + self._initial(name, "Temperature", temp) + self._handled(obj) + + def _handleHeatBodyForces(self, bodies): + obj = self._getSingleMember("Fem::FemConstraintBodyHeatSource") + if obj is not None: + for name in bodies: + heatSource = getFromUi(obj.HeatFlux, "W/kg", "L^2*T^-3") + self._bodyForce(name, "Heat Source", heatSource) + self._handled(obj) + + def _handleHeatMaterial(self, bodies): + tempObj = self._getSingleMember("Fem::ConstraintInitialTemperature") + if tempObj is not None: + refTemp = getFromUi(tempObj.initialTemperature, "K", "O") + for name in bodies: + self._material(name, "Reference Temperature", refTemp) + for obj in self._getMember("App::MaterialObject"): + m = obj.Material + refs = ( + obj.References[0][1] + if obj.References + else self._getAllBodies()) + for name in (n for n in refs if n in bodies): + self._material( + name, "Density", + self._getDensity(m)) + self._material( + name, "Heat Conductivity", + convert(m["ThermalConductivity"], "M*L/(T^3*O)")) + self._material( + name, "Heat Capacity", + convert(m["SpecificHeat"], "L^2/(T^2*O)")) + + def _handleElasticity(self): + activeIn = [] + for equation in self.solver.Group: + if FemUtils.isOfType(equation, "Fem::FemEquationElmerElasticity"): + if equation.References: + activeIn = equation.References[0][1] + else: + activeIn = self._getAllBodies() + solverSection = self._getElasticitySolver(equation) + for body in activeIn: + self._addSolver(body, solverSection) + if activeIn: + self._handleElasticityConstants() + self._handleElasticityBndConditions() + self._handleElasticityInitial(activeIn) + self._handleElasticityBodyForces(activeIn) + self._handleElasticityMaterial(activeIn) + + def _getElasticitySolver(self, equation): + s = self._createLinearSolver(equation) + s["Equation"] = equation.Name + s["Procedure"] = sifio.FileAttr("StressSolve/StressSolver") + s["Variable"] = self._getUniqueVarName("Displacement") + s["Variable DOFs"] = 3 + s["Eigen Analysis"] = equation.DoFrequencyAnalysis + s["Eigen System Values"] = equation.EigenmodesCount + s["Calculate Strains"] = equation.CalculateStrains + s["Calculate Stresses"] = equation.CalculateStresses + s["Calculate Principal"] = equation.CalculatePrincipal + s["Calculate Pangle"] = equation.CalculatePangle + s["Displace mesh"] = False + s["Exec Solver"] = "Always" + s["Stabilize"] = equation.Stabilize + s["Bubbles"] = equation.Bubbles + s["Optimize Bandwidth"] = True + return s + + def _handleElasticityConstants(self): + pass + + def _handleElasticityBndConditions(self): + for obj in self._getMember("Fem::ConstraintPressure"): + if obj.References: + for name in obj.References[0][1]: + pressure = getFromUi(obj.Pressure, "MPa", "M/(L*T^2)") + if not obj.Reversed: + pressure *= -1 + self._boundary(name, "Normal Force", pressure) + self._handled(obj) + for obj in self._getMember("Fem::ConstraintFixed"): + if obj.References: + for name in obj.References[0][1]: + self._boundary(name, "Displacement 1", 0.0) + self._boundary(name, "Displacement 2", 0.0) + self._boundary(name, "Displacement 3", 0.0) + self._handled(obj) + for obj in self._getMember("Fem::ConstraintForce"): + if obj.References: + for name in obj.References[0][1]: + force = getFromUi(obj.Force, "N", "M*L*T^-2") + self._boundary(name, "Force 1", obj.DirectionVector.x * force) + self._boundary(name, "Force 2", obj.DirectionVector.y * force) + self._boundary(name, "Force 3", obj.DirectionVector.z * force) + self._boundary(name, "Force 1 Normalize by Area", True) + self._boundary(name, "Force 2 Normalize by Area", True) + self._boundary(name, "Force 3 Normalize by Area", True) + self._handled(obj) + for obj in self._getMember("Fem::ConstraintDisplacement"): + if obj.References: + for name in obj.References[0][1]: + if not obj.xFree: + self._boundary( + name, "Displacement 1", obj.xDisplacement * 0.001) + elif obj.xFix: + self._boundary(name, "Displacement 1", 0.0) + if not obj.yFree: + self._boundary( + name, "Displacement 2", obj.yDisplacement * 0.001) + elif obj.yFix: + self._boundary(name, "Displacement 2", 0.0) + if not obj.zFree: + self._boundary( + name, "Displacement 3", obj.zDisplacement * 0.001) + elif obj.zFix: + self._boundary(name, "Displacement 3", 0.0) + self._handled(obj) + + def _handleElasticityInitial(self, bodies): + pass + + def _handleElasticityBodyForces(self, bodies): + obj = self._getSingleMember("FemConstraintSelfWeight") + if obj is not None: + for name in bodies: + gravity = getConstant("Gravity", "L/T^2") + m = self._getBodyMaterial(name).Material + + densityQuantity = Units.Quantity(m["Density"]) + dimension = "M/L^3" + if name.startswith("Edge"): + density.Unit = Units.Unit(-2, 1) + dimension = "M/L^2" + density = convert(densityQuantity, dimension) + + force1 = gravity * obj.Gravity_x * density + force2 = gravity * obj.Gravity_y * density + force3 = gravity * obj.Gravity_z * density + self._bodyForce(name, "Stress Bodyforce 1", force1) + self._bodyForce(name, "Stress Bodyforce 2", force2) + self._bodyForce(name, "Stress Bodyforce 3", force3) + self._handled(obj) + + def _getBodyMaterial(self, name): + for obj in self._getMember("App::MaterialObject"): + if not obj.References or name in obj.References[0][1]: + return obj + return None + + def _handleElasticityMaterial(self, bodies): + tempObj = self._getSingleMember("Fem::ConstraintInitialTemperature") + if tempObj is not None: + refTemp = getFromUi(tempObj.initialTemperature, "K", "O") + for name in bodies: + self._material(name, "Reference Temperature", refTemp) + for obj in self._getMember("App::MaterialObject"): + m = obj.Material + refs = ( + obj.References[0][1] + if obj.References + else self._getAllBodies()) + for name in (n for n in refs if n in bodies): + self._material( + name, "Density", + self._getDensity(m)) + self._material( + name, "Youngs Modulus", + self._getYoungsModulus(m)) + self._material( + name, "Poisson ratio", + float(m["PoissonRatio"])) + self._material( + name, "Heat expansion Coefficient", + convert(m["ThermalExpansionCoefficient"], "O^-1")) + + def _getDensity(self, m): + density = convert(m["Density"], "M/L^3") + if self._getMeshDimension() == 2: + density *= 1e3 + return density + + def _getYoungsModulus(self, m): + youngsModulus = convert(m["YoungsModulus"], "M/(L*T^2)") + if self._getMeshDimension() == 2: + youngsModulus *= 1e3 + return youngsModulus + + def _handleFlow(self): + activeIn = [] + for equation in self.solver.Group: + if FemUtils.isOfType(equation, "Fem::FemEquationElmerFlow"): + if equation.References: + activeIn = equation.References[0][1] + else: + activeIn = self._getAllBodies() + solverSection = self._getFlowSolver(equation) + for body in activeIn: + self._addSolver(body, solverSection) + if activeIn: + self._handleFlowConstants() + self._handleFlowBndConditions() + self._handleFlowInitialVelocity(activeIn) + #self._handleFlowInitial(activeIn) + #self._handleFlowBodyForces(activeIn) + self._handleFlowMaterial(activeIn) + self._handleFlowEquation(activeIn) + + def _getFlowSolver(self, equation): + s = self._createNonlinearSolver(equation) + s["Equation"] = "Navier-Stokes" + #s["Equation"] = equation.Name + s["Procedure"] = sifio.FileAttr("FlowSolve/FlowSolver") + s["Exec Solver"] = "Always" + s["Stabilize"] = equation.Stabilize + s["Bubbles"] = equation.Bubbles + s["Optimize Bandwidth"] = True + return s + + def _handleFlowConstants(self): + gravity = getConstant("Gravity", "L/T^2") + self._constant("Gravity", (0.0, -1.0, 0.0, gravity)) + + def _handleFlowMaterial(self, bodies): + tempObj = self._getSingleMember("Fem::ConstraintInitialTemperature") + if tempObj is not None: + refTemp = getFromUi(tempObj.initialTemperature, "K", "O") + for name in bodies: + self._material(name, "Reference Temperature", refTemp) + for obj in self._getMember("App::MaterialObject"): + m = obj.Material + refs = ( + obj.References[0][1] + if obj.References + else self._getAllBodies()) + for name in (n for n in refs if n in bodies): + if "Density" in m: + self._material( + name, "Density", + self._getDensity(m)) + if "ThermalConductivity" in m: + self._material( + name, "Heat Conductivity", + convert(m["ThermalConductivity"], "M*L/(T^3*O)")) + if "KinematicViscosity" in m: + density = self._getDensity(m) + kViscosity = convert(m["KinematicViscosity"], "L^2/T") + self._material( + name, "Viscosity", kViscosity * density) + if "ThermalExpansionCoefficient" in m: + value = convert(m["ThermalExpansionCoefficient"], "O^-1") + if value > 0: + self._material( + name, "Heat expansion Coefficient", value) + if "ReferencePressure" in m: + pressure = convert(m["ReferencePressure"], "M/(L*T^2)") + self._material(name, "Reference Pressure", pressure) + if "SpecificHeatRatio" in m: + self._material( + name, "Specific Heat Ratio", + float(m["SpecificHeatRatio"])) + if "CompressibilityModel" in m: + self._material( + name, "Compressibility Model", + m["CompressibilityModel"]) + + def _handleFlowInitialVelocity(self, bodies): + obj = self._getSingleMember("Fem::ConstraintInitialFlowVelocity") + if obj is not None: + for name in bodies: + if obj.VelocityXEnabled: + velocity = getFromUi(obj.VelocityX, "m/s", "L/T") + self._initial(name, "Velocity 1", velocity) + if obj.VelocityYEnabled: + velocity = getFromUi(obj.VelocityY, "m/s", "L/T") + self._initial(name, "Velocity 2", velocity) + if obj.VelocityZEnabled: + velocity = getFromUi(obj.VelocityZ, "m/s", "L/T") + self._initial(name, "Velocity 3", velocity) + self._handled(obj) + + def _handleFlowBndConditions(self): + for obj in self._getMember("Fem::ConstraintFlowVelocity"): + if obj.References: + for name in obj.References[0][1]: + if obj.VelocityXEnabled: + velocity = getFromUi(obj.VelocityX, "m/s", "L/T") + self._boundary(name, "Velocity 1", velocity) + if obj.VelocityYEnabled: + velocity = getFromUi(obj.VelocityY, "m/s", "L/T") + self._boundary(name, "Velocity 2", velocity) + if obj.VelocityZEnabled: + velocity = getFromUi(obj.VelocityZ, "m/s", "L/T") + self._boundary(name, "Velocity 3", velocity) + if obj.NormalToBoundary: + self._boundary(name, "Normal-Tangential Velocity", True) + self._handled(obj) + + def _handleFlowEquation(self, bodies): + for b in bodies: + self._equation(b, "Convection", "Computed") + + def _createLinearSolver(self, equation): + s = sifio.createSection(sifio.SOLVER) + s.priority = equation.Priority + s["Linear System Solver"] = equation.LinearSolverType + if equation.LinearSolverType == "Direct": + s["Linear System Direct Method"] = \ + equation.LinearDirectMethod + elif equation.LinearSolverType == "Iterative": + s["Linear System Iterative Method"] = \ + equation.LinearIterativeMethod + if equation.LinearIterativeMethod == "BiCGStabl": + s["BiCGstabl polynomial degree"] = \ + equation.BiCGstablDegree + s["Linear System Max Iterations"] = \ + equation.LinearIterations + s["Linear System Convergence Tolerance"] = \ + equation.LinearTolerance + s["Linear System Preconditioning"] = \ + equation.LinearPreconditioning + s["Steady State Convergence Tolerance"] = \ + equation.SteadyStateTolerance + s["Linear System Abort Not Converged"] = False + s["Linear System Residual Output"] = 1 + s["Linear System Precondition Recompute"] = 1 + return s + + def _createNonlinearSolver(self, equation): + s = self._createLinearSolver(equation) + s["Nonlinear System Max Iterations"] = \ + equation.NonlinearIterations + s["Nonlinear System Convergence Tolerance"] = \ + equation.NonlinearTolerance + s["Nonlinear System Relaxation Factor"] = \ + equation.RelaxationFactor + s["Nonlinear System Newton After Iterations"] = \ + equation.NonlinearNewtonAfterIterations + s["Nonlinear System Newton After Tolerance"] = \ + equation.NonlinearNewtonAfterTolerance + return s + + def _getUniqueVarName(self, varName): + postfix = 1 + if varName in self._usedVarNames: + varName += "%2d" % postfix + while varName in self._usedVarNames: + postfix += 1 + varName = varName[:-2] + "%2d" % postfix + self._usedVarNames.add(varName) + return varName + + def _getAllBodies(self): + obj = FemUtils.getSingleMember(self.analysis, "Fem::FemMeshObject") + bodyCount = 0 + prefix = "" + if obj.Part.Shape.Solids: + prefix = "Solid" + bodyCount = len(obj.Part.Shape.Solids) + elif obj.Part.Shape.Faces: + prefix = "Face" + bodyCount = len(obj.Part.Shape.Faces) + elif obj.Part.Shape.Edges: + prefix = "Edge" + bodyCount = len(obj.Part.Shape.Edges) + return [prefix + str(i + 1) for i in range(bodyCount)] + + def _getMeshDimension(self): + obj = FemUtils.getSingleMember(self.analysis, "Fem::FemMeshObject") + if obj.Part.Shape.Solids: + return 3 + elif obj.Part.Shape.Faces: + return 2 + elif obj.Part.Shape.Edges: + return 1 + return None + + def _addOutputSolver(self): + s = sifio.createSection(sifio.SOLVER) + s["Equation"] = "ResultOutput" + s["Exec Solver"] = "After simulation" + s["Procedure"] = sifio.FileAttr("ResultOutputSolve/ResultOutputSolver") + s["Output File Name"] = sifio.FileAttr("case") + s["Vtu Format"] = True + for name in self._getAllBodies(): + self._addSolver(name, s) + + def _writeSif(self): + sifPath = os.path.join(self.directory, _SIF_NAME) + with open(sifPath, 'w') as fstream: + sif = sifio.Sif(self._builder) + sif.write(fstream) + + def _handled(self, obj): + self._handledObjects.add(obj) + + def _simulation(self, key, attr): + self._builder.simulation(key, attr) + + def _constant(self, key, attr): + self._builder.constant(key, attr) + + def _initial(self, body, key, attr): + self._builder.initial(body, key, attr) + + def _material(self, body, key, attr): + self._builder.material(body, key, attr) + + def _equation(self, body, key, attr): + self._builder.equation(body, key, attr) + + def _bodyForce(self, body, key, attr): + self._builder.bodyForce(body, key, attr) + + def _addSolver(self, body, solverSection): + self._builder.addSolver(body, solverSection) + + def _boundary(self, boundary, key, attr): + self._builder.boundary(boundary, key, attr) + + def _addSection(self, section): + self._builder.addSection(section) + + def _getMember(self, t): + return FemUtils.getMember(self.analysis, t) + + def _getSingleMember(self, t): + return FemUtils.getSingleMember(self.analysis, t) + + +class WriteError(Exception): + pass diff --git a/src/Mod/Fem/femsolver/equationbase.py b/src/Mod/Fem/femsolver/equationbase.py new file mode 100644 index 0000000000..e95f4fc4b1 --- /dev/null +++ b/src/Mod/Fem/femsolver/equationbase.py @@ -0,0 +1,96 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2017 - Markus Hovorka * +# * * +# * 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__ = "_Base" +__author__ = "Markus Hovorka" +__url__ = "http://www.freecadweb.org" + + +import FreeCAD +if FreeCAD.GuiUp: + from pivy import coin + + +class BaseProxy(object): + + BaseType = "App::FeaturePython" + + def __init__(self, obj): + obj.Proxy = self + obj.addProperty( + "App::PropertyLinkSubList", "References", + "Base", "") + + def execute(self, obj): + return True + + +class BaseViewProxy(object): + + def __init__(self, vobj): + vobj.Proxy = self + + def attach(self, vobj): + default = coin.SoGroup() + vobj.addDisplayMode(default, "Default") + + def getDisplayModes(self, obj): + "Return a list of display modes." + modes = ["Default"] + return modes + + def getDefaultDisplayMode(self): + return "Default" + + def setDisplayMode(self, mode): + return mode + + +class HeatProxy(BaseProxy): + pass + + +class HeatViewProxy(BaseViewProxy): + + def getIcon(self): + return ":/icons/fem-equation-heat.svg" + + +class ElasticityProxy(BaseProxy): + pass + + +class ElasticityViewProxy(BaseViewProxy): + + def getIcon(self): + return ":/icons/fem-equation-elasticity.svg" + + +class FlowProxy(BaseProxy): + pass + + +class FlowViewProxy(BaseViewProxy): + + def getIcon(self): + return ":/icons/fem-equation-flow.svg"