diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 2d2a1b28db..f2f4f454c6 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -265,6 +265,8 @@ SET(FemSolverElmerEquations_SRCS femsolver/elmer/equations/heat.py femsolver/elmer/equations/heat_writer.py femsolver/elmer/equations/linear.py + femsolver/elmer/equations/magnetodynamic2D.py + femsolver/elmer/equations/magnetodynamic2D_writer.py femsolver/elmer/equations/nonlinear.py ) diff --git a/src/Mod/Fem/Gui/Command.cpp b/src/Mod/Fem/Gui/Command.cpp index f2985bc4e0..28493d1b66 100644 --- a/src/Mod/Fem/Gui/Command.cpp +++ b/src/Mod/Fem/Gui/Command.cpp @@ -1344,6 +1344,136 @@ bool CmdFemCompEmConstraints::isActive() } +//=========================================================================== +// FEM_CompEmEquations (dropdown toolbar button for Electromagnetic equations) +//=========================================================================== + +DEF_STD_CMD_ACL(CmdFEMCompEmEquations) + +CmdFEMCompEmEquations::CmdFEMCompEmEquations() + : Command("FEM_CompEmEquations") +{ + sAppModule = "Fem"; + sGroup = QT_TR_NOOP("Fem"); + sMenuText = QT_TR_NOOP("Electromagnetic equations..."); + sToolTipText = QT_TR_NOOP( + "Electromagnetic equations for the Elmer solver"); + sWhatsThis = "FEM_CompEmEquations"; + sStatusTip = sToolTipText; +} + +void CmdFEMCompEmEquations::activated(int iMsg) +{ + Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); + if (iMsg == 0) + rcCmdMgr.runCommandByName("FEM_EquationElectricforce"); + else if (iMsg == 1) + rcCmdMgr.runCommandByName("FEM_EquationElectrostatic"); + else if (iMsg == 2) + rcCmdMgr.runCommandByName("FEM_EquationMagnetodynamic2D"); + else + return; + + // Since the default icon is reset when enabling/disabling the command we have + // to explicitly set the icon of the used command. + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + QList a = pcAction->actions(); + + assert(iMsg < a.size()); + pcAction->setIcon(a[iMsg]->icon()); +} + +Gui::Action* CmdFEMCompEmEquations::createAction() +{ + Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow()); + pcAction->setDropDownMenu(true); + applyCommandData(this->className(), pcAction); + + QAction* cmd0 = pcAction->addAction(QString()); + cmd0->setIcon(Gui::BitmapFactory().iconFromTheme("FEM_EquationElectricforce")); + QAction* cmd1 = pcAction->addAction(QString()); + cmd1->setIcon(Gui::BitmapFactory().iconFromTheme("FEM_EquationElectrostatic")); + QAction* cmd2 = pcAction->addAction(QString()); + cmd2->setIcon(Gui::BitmapFactory().iconFromTheme("FEM_EquationMagnetodynamic2D")); + + _pcAction = pcAction; + languageChange(); + + pcAction->setIcon(cmd0->icon()); + int defaultId = 0; + pcAction->setProperty("defaultAction", QVariant(defaultId)); + + return pcAction; +} + +void CmdFEMCompEmEquations::languageChange() +{ + Command::languageChange(); + + if (!_pcAction) + return; + + Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); + + Gui::ActionGroup* pcAction = qobject_cast(_pcAction); + QList a = pcAction->actions(); + + Gui::Command* EquationElectricforce = rcCmdMgr.getCommandByName("FEM_EquationElectricforce"); + if (EquationElectricforce) { + QAction* cmd0 = a[0]; + cmd0->setText(QApplication::translate("FEM_EquationElectricforce", + EquationElectricforce->getMenuText())); + cmd0->setToolTip(QApplication::translate("FEM_EquationElectricforce", + EquationElectricforce->getToolTipText())); + cmd0->setStatusTip(QApplication::translate("FEM_EquationElectricforce", + EquationElectricforce->getStatusTip())); + } + + Gui::Command* EquationElectrostatic = rcCmdMgr.getCommandByName("FEM_EquationElectrostatic"); + if (EquationElectrostatic) { + QAction* cmd1 = a[1]; + cmd1->setText(QApplication::translate("FEM_EquationElectrostatic", + EquationElectrostatic->getMenuText())); + cmd1->setToolTip(QApplication::translate("FEM_EquationElectrostatic", + EquationElectrostatic->getToolTipText())); + cmd1->setStatusTip(QApplication::translate("FEM_EquationElectrostatic", + EquationElectrostatic->getStatusTip())); + } + + Gui::Command* EquationMagnetodynamic2D = + rcCmdMgr.getCommandByName("FEM_EquationMagnetodynamic2D"); + if (EquationMagnetodynamic2D) { + QAction* cmd2 = a[2]; + cmd2->setText(QApplication::translate("FEM_EquationMagnetodynamic2D", + EquationMagnetodynamic2D->getMenuText())); + cmd2->setToolTip(QApplication::translate("FEM_EquationMagnetodynamic2D", + EquationMagnetodynamic2D->getToolTipText())); + cmd2->setStatusTip(QApplication::translate("FEM_EquationMagnetodynamic2D", + EquationMagnetodynamic2D->getStatusTip())); + } +} + +bool CmdFEMCompEmEquations::isActive() +{ + // only if there is an active analysis + Fem::FemAnalysis* ActiveAnalysis = + FemGui::ActiveAnalysisObserver::instance()->getActiveObject(); + if (!ActiveAnalysis + || !ActiveAnalysis->getTypeId().isDerivedFrom(Fem::FemAnalysis::getClassTypeId())) + return false; + + // only activate if a single Elmer object is selected + auto results = getSelection().getSelectionEx(nullptr, App::DocumentObject::getClassTypeId(), Gui::ResolveMode::FollowLink); //.getObjectsOfType(); + if (results.size() == 1) { + auto object = results.begin()->getObject(); + // FIXME: this is not unique since the Ccx solver object has the same type + std::string Type = "Fem::FemSolverObjectPython"; + if (Type.compare(object->getTypeId().getName()) == 0) + return true; + } + return false; +} + //================================================================================================ //================================================================================================ // commands vtk post processing @@ -2203,6 +2333,9 @@ void CreateFemCommands() rcCmdMgr.addCommand(new CmdFemCreateNodesSet()); rcCmdMgr.addCommand(new CmdFemDefineNodesSet()); + // equations + rcCmdMgr.addCommand(new CmdFEMCompEmEquations()); + // vtk post processing #ifdef FC_USE_VTK rcCmdMgr.addCommand(new CmdFemPostClipFilter); diff --git a/src/Mod/Fem/Gui/Resources/Fem.qrc b/src/Mod/Fem/Gui/Resources/Fem.qrc index d3e26dd411..9cc6c1a307 100755 --- a/src/Mod/Fem/Gui/Resources/Fem.qrc +++ b/src/Mod/Fem/Gui/Resources/Fem.qrc @@ -46,6 +46,7 @@ icons/FEM_EquationFlow.svg icons/FEM_EquationFlux.svg icons/FEM_EquationHeat.svg + icons/FEM_EquationMagnetodynamic2D.svg icons/FEM_CreateNodesSet.svg diff --git a/src/Mod/Fem/Gui/Resources/icons/FEM_EquationMagnetodynamic2D.svg b/src/Mod/Fem/Gui/Resources/icons/FEM_EquationMagnetodynamic2D.svg new file mode 100644 index 0000000000..136ca6fb64 --- /dev/null +++ b/src/Mod/Fem/Gui/Resources/icons/FEM_EquationMagnetodynamic2D.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + [Alexander Gryson] + + + 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/Workbench.cpp b/src/Mod/Fem/Gui/Workbench.cpp index 10c4a9c2e3..ffd6f6aef3 100755 --- a/src/Mod/Fem/Gui/Workbench.cpp +++ b/src/Mod/Fem/Gui/Workbench.cpp @@ -180,8 +180,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const << "FEM_SolverZ88" << "Separator" << "FEM_EquationElasticity" - << "FEM_EquationElectricforce" - << "FEM_EquationElectrostatic" + << "FEM_CompEmEquations" << "FEM_EquationFlow" << "FEM_EquationFlux" << "FEM_EquationHeat" @@ -347,8 +346,7 @@ Gui::MenuItem* Workbench::setupMenuBar() const << "FEM_SolverZ88" << "Separator" << "FEM_EquationElasticity" - << "FEM_EquationElectricforce" - << "FEM_EquationElectrostatic" + << "FEM_CompEmEquations" << "FEM_EquationFlow" << "FEM_EquationFlux" << "FEM_EquationHeat" diff --git a/src/Mod/Fem/ObjectsFem.py b/src/Mod/Fem/ObjectsFem.py index 6f28572cee..503d3e3f4d 100644 --- a/src/Mod/Fem/ObjectsFem.py +++ b/src/Mod/Fem/ObjectsFem.py @@ -802,6 +802,20 @@ def makeEquationHeat( return obj +def makeEquationMagnetodynamic2D( + doc, + base_solver=None, + name="Magnetodynamic2D" +): + """makeEquationMagnetodynamic2D(document, [base_solver], [name]): + creates a FEM magnetodynamic2D equation for a solver""" + from femsolver.elmer.equations import magnetodynamic2D + obj = magnetodynamic2D.create(doc, name) + if base_solver: + base_solver.addObject(obj) + return obj + + def makeSolverCalculixCcxTools( doc, name="SolverCcxTools" diff --git a/src/Mod/Fem/femcommands/commands.py b/src/Mod/Fem/femcommands/commands.py index de79368982..39271fc6b5 100644 --- a/src/Mod/Fem/femcommands/commands.py +++ b/src/Mod/Fem/femcommands/commands.py @@ -516,6 +516,23 @@ class _EquationHeat(CommandManager): self.do_activated = "add_obj_on_gui_selobj_noset_edit" +class _EquationMagnetodynamic2D(CommandManager): + "The FEM_EquationMagnetodynamic2D command definition" + + def __init__(self): + super(_EquationMagnetodynamic2D, self).__init__() + self.menutext = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationMagnetodynamic2D", + "Magnetodynamic2D equation" + ) + self.tooltip = Qt.QT_TRANSLATE_NOOP( + "FEM_EquationMagnetodynamic2D", + "Creates a FEM equation for\n2D magentodynamic forces" + ) + self.is_active = "with_solver_elmer" + self.do_activated = "add_obj_on_gui_selobj_noset_edit" + + class _Examples(CommandManager): "The FEM_Examples command definition" @@ -1201,6 +1218,10 @@ FreeCADGui.addCommand( "FEM_EquationHeat", _EquationHeat() ) +FreeCADGui.addCommand( + "FEM_EquationMagnetodynamic2D", + _EquationMagnetodynamic2D() +) FreeCADGui.addCommand( "FEM_Examples", _Examples() diff --git a/src/Mod/Fem/femsolver/elmer/equations/magnetodynamic2D.py b/src/Mod/Fem/femsolver/elmer/equations/magnetodynamic2D.py new file mode 100644 index 0000000000..920b962fba --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/equations/magnetodynamic2D.py @@ -0,0 +1,141 @@ +# *************************************************************************** +# * Copyright (c) 2023 Uwe Stöhr * +# * * +# * 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 Elmer equation object Magnetodynamic2D" +__author__ = "Uwe Stöhr" +__url__ = "https://www.freecadweb.org" +# +## \addtogroup FEM +# @{ + +from femtools import femutils +from . import nonlinear +from ... import equationbase + +def create(doc, name="Magnetodynamic2D"): + return femutils.createObject( + doc, name, Proxy, ViewProxy) + + +class Proxy(nonlinear.Proxy, equationbase.Magnetodynamic2DProxy): + + Type = "Fem::EquationElmerMagnetodynamic2D" + + def __init__(self, obj): + super(Proxy, self).__init__(obj) + + obj.addProperty( + "App::PropertyBool", + "IsHarmonic", + "Magnetodynamic2D", + "If the magnetic source is harmonically driven" + ) + obj.addProperty( + "App::PropertyFrequency", + "AngularFrequency", + "Magnetodynamic2D", + "Frequency of the driving current" + ) + obj.IsHarmonic = False + obj.AngularFrequency = 0 + obj.Priority = 10 + + # the post processor options + obj.addProperty( + "App::PropertyBool", + "CalculateCurrentDensity", + "Magnetodynamic2D", + "" + ) + obj.addProperty( + "App::PropertyBool", + "CalculateElectricField", + "Magnetodynamic2D", + "" + ) + obj.addProperty( + "App::PropertyBool", + "CalculateElementalFields", + "Magnetodynamic2D", + "" + ) + obj.addProperty( + "App::PropertyBool", + "CalculateHarmonicLoss", + "Magnetodynamic2D", + "" + ) + obj.addProperty( + "App::PropertyBool", + "CalculateJouleHeating", + "Magnetodynamic2D", + "" + ) + obj.addProperty( + "App::PropertyBool", + "CalculateMagneticFieldStrength", + "Magnetodynamic2D", + "" + ) + obj.addProperty( + "App::PropertyBool", + "CalculateMaxwellStress", + "Magnetodynamic2D", + "" + ) + obj.addProperty( + "App::PropertyBool", + "CalculateNodalFields", + "Magnetodynamic2D", + "" + ) + obj.addProperty( + "App::PropertyBool", + "CalculateNodalForces", + "Magnetodynamic2D", + "" + ) + obj.addProperty( + "App::PropertyBool", + "CalculateNodalHeating", + "Magnetodynamic2D", + "" + ) + obj.CalculateCurrentDensity = False + obj.CalculateElectricField = False + # FIXME: at the moment FreeCAD's post processor cannot display elementary field + # results, therefore disable despite this is by default on in Elmer + obj.CalculateElementalFields = False + obj.CalculateHarmonicLoss = False + obj.CalculateJouleHeating = False + obj.CalculateMagneticFieldStrength = False + obj.CalculateMaxwellStress = False + obj.CalculateNodalFields = True + obj.CalculateNodalForces = False + obj.CalculateNodalHeating = False + + +class ViewProxy(nonlinear.ViewProxy, equationbase.Magnetodynamic2DViewProxy): + pass + +## @} diff --git a/src/Mod/Fem/femsolver/elmer/equations/magnetodynamic2D_writer.py b/src/Mod/Fem/femsolver/elmer/equations/magnetodynamic2D_writer.py new file mode 100644 index 0000000000..45d5e9dd7d --- /dev/null +++ b/src/Mod/Fem/femsolver/elmer/equations/magnetodynamic2D_writer.py @@ -0,0 +1,208 @@ +# *************************************************************************** +# * Copyright (c) 2023 Uwe Stöhr * +# * * +# * 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 Magnetodynamics2D Elmer writer" +__author__ = "Uwe Stöhr" +__url__ = "https://www.freecad.org" + +## \addtogroup FEM +# @{ + +from FreeCAD import Console +from FreeCAD import Units + +from .. import sifio +from .. import writer as general_writer + +class MgDyn2Dwriter: + + def __init__(self, writer, solver): + self.write = writer + self.solver = solver + + def getMagnetodynamic2DSolver(self, equation): + # output the equation parameters + s = self.write.createNonlinearSolver(equation) + if not equation.IsHarmonic: + s["Equation"] = "MgDyn2D" + s["Procedure"] = sifio.FileAttr("MagnetoDynamics2D/MagnetoDynamics2D") + s["Variable"] = "Potential" + else: + s["Equation"] = "MgDyn2DHarmonic" + s["Procedure"] = sifio.FileAttr("MagnetoDynamics2D/MagnetoDynamics2DHarmonic") + s["Variable"] = "Potential[Potential Re:1 Potential Im:1]" + s["Exec Solver"] = "Always" + s["Optimize Bandwidth"] = True + s["Stabilize"] = equation.Stabilize + return s + + def getMagnetodynamic2DSolverPost(self, equation): + # output the equation parameters + s = self.write.createNonlinearSolver(equation) + s["Equation"] = "MgDyn2DPost" + s["Exec Solver"] = "Always" + s["Procedure"] = sifio.FileAttr("MagnetoDynamics/MagnetoDynamicsCalcFields") + if equation.IsHarmonic: + s["Angular Frequency"] = float(Units.Quantity(equation.AngularFrequency).Value) + s["Potential Variable"] = "Potential" + if equation.CalculateCurrentDensity is True: + s["Calculate Current Density"] = True + if equation.CalculateElectricField is True: + s["Calculate Electric Field"] = True + if equation.CalculateElementalFields is False: + s["Calculate Elemental Fields"] = False + if equation.CalculateHarmonicLoss is True: + s["Calculate Harmonic Loss"] = True + if equation.CalculateJouleHeating is True: + s["Calculate Joule Heating"] = True + if equation.CalculateMagneticFieldStrength is True: + s["Calculate Magnetic Field Strength"] = True + if equation.CalculateMaxwellStress is True: + s["Calculate Maxwell Stress"] = True + if equation.CalculateNodalFields is False: + s["Calculate Nodal Fields"] = False + if equation.CalculateNodalForces is True: + s["Calculate Nodal Forces"] = True + if equation.CalculateNodalHeating is True: + s["Calculate Nodal Heating"] = True + s["Optimize Bandwidth"] = True + s["Stabilize"] = equation.Stabilize + return s + + def handleMagnetodynamic2DConstants(self): + permeability = self.write.convert( + self.write.constsdef["PermeabilityOfVacuum"], + "M*L/(T^2*I^2)" + ) + permeability = round(permeability, 20) # to get rid of numerical artifacts + self.write.constant("Permeability Of Vacuum", permeability) + + permittivity = self.write.convert( + self.write.constsdef["PermittivityOfVacuum"], + "T^4*I^2/(L^3*M)" + ) + permittivity = round(permittivity, 20) # to get rid of numerical artifacts + self.write.constant("Permittivity Of Vacuum", permittivity) + + def handleMagnetodynamic2DMaterial(self, bodies): + for obj in self.write.getMember("App::MaterialObject"): + m = obj.Material + refs = ( + obj.References[0][1] + if obj.References + else self.write.getAllBodies()) + for name in (n for n in refs if n in bodies): + if "ElectricalConductivity" not in m: + Console.PrintMessage("m: {}\n".format(m)) + raise general_writer.WriteError( + "The electrical conductivity must be specified for all materials.\n\n" + ) + if "RelativePermeability" not in m: + Console.PrintMessage("m: {}\n".format(m)) + raise general_writer.WriteError( + "The relative permeability must be specified for all materials.\n\n" + ) + self.write.material(name, "Name", m["Name"]) + conductivity = self.write.convert(m["ElectricalConductivity"], "T^3*I^2/(L^3*M)") + conductivity = round(conductivity, 10) # to get rid of numerical artifacts + self.write.material( + name, "Electric Conductivity", + conductivity + ) + self.write.material( + name, "Relative Permeability", + float(m["RelativePermeability"]) + ) + # permittivity might be necessary for the post processor + if "RelativePermittivity" in m: + self.write.material( + name, "Relative Permittivity", + float(m["RelativePermittivity"]) + ) + + def _outputMagnetodynamic2DBodyForce(self, obj, name): + currentDensity = self.write.getFromUi( + obj.CurrentDensity.getValueAs("A/m^2"), "A/m^2", "I/L^2" + ) + currentDensity = round(currentDensity, 10) # to get rid of numerical artifacts + if currentDensity == 0.0: + # a zero density would break Elmer + raise general_writer.WriteError("The current density must not be zero!") + self.write.bodyForce(name, "Current Density", currentDensity) + + def handleMagnetodynamic2DBodyForces(self, bodies): + currentDensities = self.write.getMember("Fem::ConstraintCurrentDensity") + if len(currentDensities) == 0: + raise general_writer.WriteError( + "The Magnetodynamic2D equation needs at least one CurrentDensity constraint." + ) + # check that all bodies have a set material + for name in bodies: + if self.write.getBodyMaterial(name) == None: + raise general_writer.WriteError( + "The body {} is not referenced in any material.\n\n".format(name) + ) + for obj in currentDensities: + if obj.References: + for name in obj.References[0][1]: + self._outputMagnetodynamic2DBodyForce(obj, name) + self.write.handled(obj) + else: + # if there is only one current density without a reference, + # add it to all bodies + if len(currentDensities) == 1: + for name in bodies: + self._outputMagnetodynamic2DBodyForce(obj, name) + else: + raise general_writer.WriteError( + "Several current density constraints found without reference to a body.\n" + "Please set a body for each current density constraint." + ) + self.write.handled(obj) + + def handleMagnetodynamic2DBndConditions(self): + for obj in self.write.getMember("Fem::ConstraintElectrostaticPotential"): + if obj.References: + for name in obj.References[0][1]: + # output the FreeCAD label as comment + if obj.Label: + self.write.boundary(name, "! FreeCAD Name", obj.Label) + if obj.PotentialEnabled: + if hasattr(obj, "Potential"): + potential = float(obj.Potential.getValueAs("V")) + self.write.boundary(name, "Potential", potential) + if obj.ElectricInfinity: + self.write.boundary(name, "Infinity BC", True) + self.write.handled(obj) + + def handleMagnetodynamic2DEquation(self, bodies, equation): + for b in bodies: + if equation.IsHarmonic and (equation.AngularFrequency == 0): + raise general_writer.WriteError( + "The angular frequency must not be zero.\n\n" + ) + self.write.equation(b, "Name", equation.Name) + frequency = Units.Quantity(equation.AngularFrequency).Value + self.write.equation(b, "Angular Frequency", float(frequency)) + +## @} diff --git a/src/Mod/Fem/femsolver/elmer/solver.py b/src/Mod/Fem/femsolver/elmer/solver.py index b865802465..47cb33e180 100644 --- a/src/Mod/Fem/femsolver/elmer/solver.py +++ b/src/Mod/Fem/femsolver/elmer/solver.py @@ -40,6 +40,7 @@ from .equations import electrostatic from .equations import flow from .equations import flux from .equations import heat +from .equations import magnetodynamic2D from .. import run from .. import solverbase from femtools import femutils @@ -71,6 +72,7 @@ class Proxy(solverbase.Proxy): "Flux": flux, "Electricforce": electricforce, "Flow": flow, + "Magnetodynamic2D": magnetodynamic2D, } def __init__(self, obj): diff --git a/src/Mod/Fem/femsolver/elmer/writer.py b/src/Mod/Fem/femsolver/elmer/writer.py index 4a9d21066f..85e342f4c4 100644 --- a/src/Mod/Fem/femsolver/elmer/writer.py +++ b/src/Mod/Fem/femsolver/elmer/writer.py @@ -54,12 +54,15 @@ from .equations import electrostatic_writer as ES_writer from .equations import flow_writer from .equations import flux_writer from .equations import heat_writer +from .equations import magnetodynamic2D_writer as MgDyn2D_writer _STARTINFO_NAME = "ELMERSOLVER_STARTINFO" _SIF_NAME = "case.sif" _ELMERGRID_IFORMAT = "8" _ELMERGRID_OFORMAT = "2" +_NON_CARTESIAN_CS = ["Polar 2D", "Polar 3D", + "Cylindric", "Cylindric Symmetric"] def _getAllSubObjects(obj): @@ -96,6 +99,7 @@ class Writer(object): self._handleHeat() self._handleFlow() self._handleFlux() + self._handleMagnetodynamic2D() self._addOutputSolver() self._writeSif() @@ -530,6 +534,39 @@ class Writer(object): HeatW.handleHeatBodyForces(activeIn) HeatW.handleHeatMaterial(activeIn) + #------------------------------------------------------------------------------------------- + # Magnetodynamic2D + + def _handleMagnetodynamic2D(self): + MgDyn2D = MgDyn2D_writer.MgDyn2Dwriter(self, self.solver) + activeIn = [] + for equation in self.solver.Group: + if femutils.is_of_type(equation, "Fem::EquationElmerMagnetodynamic2D"): + if equation.References: + activeIn = equation.References[0][1] + else: + activeIn = self.getAllBodies() + # Magnetodynamic2D cannot handle all coordinate sysytems + if self.solver.CoordinateSystem in _NON_CARTESIAN_CS: + raise WriteError( + "The coordinate setting '{}'\n is not " + "supported by the equation 'Magnetodynamic2D'.\n\n" + "The possible settings are:\n'Cartesian 2D',\n" + "'Cartesian 3D',\nor 'Axi Symmetric'".format(self.solver.CoordinateSystem) + ) + + solverSection = MgDyn2D.getMagnetodynamic2DSolver(equation) + solverPostSection = MgDyn2D.getMagnetodynamic2DSolverPost(equation) + for body in activeIn: + self._addSolver(body, solverSection) + self._addSolver(body, solverPostSection) + MgDyn2D.handleMagnetodynamic2DEquation(activeIn, equation) + if activeIn: + MgDyn2D.handleMagnetodynamic2DConstants() + MgDyn2D.handleMagnetodynamic2DBndConditions() + MgDyn2D.handleMagnetodynamic2DBodyForces(activeIn) + MgDyn2D.handleMagnetodynamic2DMaterial(activeIn) + #------------------------------------------------------------------------------------------- # Solver handling @@ -753,7 +790,7 @@ class Writer(object): def equation(self, body, key, attr): self._builder.equation(body, key, attr) - def _bodyForce(self, body, key, attr): + def bodyForce(self, body, key, attr): self._builder.bodyForce(body, key, attr) def _addSolver(self, body, solverSection): diff --git a/src/Mod/Fem/femsolver/equationbase.py b/src/Mod/Fem/femsolver/equationbase.py index 3dac2a05f8..e4fac3c488 100644 --- a/src/Mod/Fem/femsolver/equationbase.py +++ b/src/Mod/Fem/femsolver/equationbase.py @@ -129,4 +129,14 @@ class HeatViewProxy(BaseViewProxy): return ":/icons/FEM_EquationHeat.svg" +class Magnetodynamic2DProxy(BaseProxy): + pass + + +class Magnetodynamic2DViewProxy(BaseViewProxy): + + def getIcon(self): + return ":/icons/FEM_EquationMagnetodynamic2D.svg" + + ## @} diff --git a/src/Mod/Fem/femtest/app/test_object.py b/src/Mod/Fem/femtest/app/test_object.py index 841005bd81..f341c0ab8c 100644 --- a/src/Mod/Fem/femtest/app/test_object.py +++ b/src/Mod/Fem/femtest/app/test_object.py @@ -84,14 +84,14 @@ class TestObjectCreate(unittest.TestCase): # thus they are not added to the analysis group ATM # https://forum.freecadweb.org/viewtopic.php?t=25283 # thus they should not be counted - # solver children: equations --> 6 + # solver children: equations --> 7 # gmsh mesh children: group, region, boundary layer --> 3 # resule children: mesh result --> 1 # post pipeline children: region, scalar, cut, wrap --> 4 # analysis itself is not in analysis group --> 1 - # thus: -14 + # thus: -16 - self.assertEqual(len(doc.Analysis.Group), count_defmake - 15) + self.assertEqual(len(doc.Analysis.Group), count_defmake - 16) self.assertEqual(len(doc.Objects), count_defmake) fcc_print("doc objects count: {}, method: {}".format( @@ -366,6 +366,10 @@ class TestObjectType(unittest.TestCase): "Fem::EquationElmerHeat", type_of_obj(ObjectsFem.makeEquationHeat(doc, solverelmer)) ) + self.assertEqual( + "Fem::EquationElmerMagnetodynamic2D", + type_of_obj(ObjectsFem.makeEquationMagnetodynamic2D(doc, solverelmer)) + ) fcc_print("doc objects count: {}, method: {}".format( len(doc.Objects), @@ -597,6 +601,10 @@ class TestObjectType(unittest.TestCase): ObjectsFem.makeEquationHeat(doc, solverelmer), "Fem::EquationElmerHeat" )) + self.assertTrue(is_of_type( + ObjectsFem.makeEquationMagnetodynamic2D(doc, solverelmer), + "Fem::EquationElmerMagnetodynamic2D" + )) fcc_print("doc objects count: {}, method: {}".format( len(doc.Objects), @@ -1422,6 +1430,21 @@ class TestObjectType(unittest.TestCase): "Fem::EquationElmerHeat" )) + # EquationElmerMagnetodynamic2D + equation_magnetodynamic2D = ObjectsFem.makeEquationMagnetodynamic2D(doc, solver_elmer) + self.assertTrue(is_derived_from( + equation_magnetodynamic2D, + "App::DocumentObject" + )) + self.assertTrue(is_derived_from( + equation_magnetodynamic2D, + "App::FeaturePython" + )) + self.assertTrue(is_derived_from( + equation_magnetodynamic2D, + "Fem::EquationElmerMagnetodynamic2D" + )) + fcc_print("doc objects count: {}, method: {}".format( len(doc.Objects), sys._getframe().f_code.co_name) @@ -1706,6 +1729,12 @@ class TestObjectType(unittest.TestCase): solverelmer ).isDerivedFrom("App::FeaturePython") ) + self.assertTrue( + ObjectsFem.makeEquationMagnetodynamic2D( + doc, + solverelmer + ).isDerivedFrom("App::FeaturePython") + ) fcc_print("doc objects count: {}, method: {}".format( len(doc.Objects), @@ -1786,6 +1815,7 @@ def create_all_fem_objects_doc( ObjectsFem.makeEquationFlow(doc, sol) ObjectsFem.makeEquationFlux(doc, sol) ObjectsFem.makeEquationHeat(doc, sol) + ObjectsFem.makeEquationMagnetodynamic2D(doc, sol) doc.recompute() diff --git a/src/Mod/Fem/femtest/app/test_open.py b/src/Mod/Fem/femtest/app/test_open.py index f3c81b28cc..17e213eda8 100644 --- a/src/Mod/Fem/femtest/app/test_open.py +++ b/src/Mod/Fem/femtest/app/test_open.py @@ -353,6 +353,11 @@ class TestObjectOpen(unittest.TestCase): doc.Heat.Proxy.__class__ ) + self.assertEqual( + "Fem::EquationElmerMagnetodynamic2D", + type_of_obj(ObjectsFem.makeEquationMagnetodynamic2D(doc)) + ) + """ # code was generated by the following code from a document with all objects diff --git a/src/Mod/Fem/femtest/gui/test_open.py b/src/Mod/Fem/femtest/gui/test_open.py index f6ffd0f010..94e513437a 100644 --- a/src/Mod/Fem/femtest/gui/test_open.py +++ b/src/Mod/Fem/femtest/gui/test_open.py @@ -328,6 +328,11 @@ class TestObjectOpen(unittest.TestCase): doc.Heat.ViewObject.Proxy.__class__ ) + self.assertEqual( + "Fem::EquationElmerMagnetodynamic2D", + type_of_obj(ObjectsFem.makeEquationMagnetodynamic2D(doc)) + ) + """ # code was generated by the following code from a document with all objects