[FEM] Elmer: add support for 2D magnetodynamics

- adds the corresponding Elmer equation (it is now possible to do Elmer's tutorial example no. 16)
This commit is contained in:
Uwe
2023-02-02 04:28:47 +01:00
parent a5e0ac5949
commit ca05213e2e
15 changed files with 694 additions and 8 deletions

View File

@@ -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
)

View File

@@ -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<Gui::ActionGroup*>(_pcAction);
QList<QAction*> 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<Gui::ActionGroup*>(_pcAction);
QList<QAction*> 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<femsolver.elmer.solver.Proxy>();
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);

View File

@@ -46,6 +46,7 @@
<file>icons/FEM_EquationFlow.svg</file>
<file>icons/FEM_EquationFlux.svg</file>
<file>icons/FEM_EquationHeat.svg</file>
<file>icons/FEM_EquationMagnetodynamic2D.svg</file>
<!-- gui command icons: meshes -->
<file>icons/FEM_CreateNodesSet.svg</file>

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg version="1.1" id="svg2" height="64" width="64" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs id="defs4">
<linearGradient id="linearGradient959">
<stop style="stop-color:#cc0000;stop-opacity:1" offset="0" id="stop957" />
<stop id="stop955" offset="1" style="stop-color:#729fcf;stop-opacity:1" />
</linearGradient>
<linearGradient id="linearGradient940">
<stop style="stop-color:#000000;stop-opacity:1" offset="0" id="stop936" />
<stop style="stop-color:#000000;stop-opacity:0" offset="1" id="stop938" />
</linearGradient>
<linearGradient id="linearGradient882">
<stop id="stop876" offset="0" style="stop-color:#73d216;stop-opacity:1" />
<stop style="stop-color:#729fcf;stop-opacity:1" offset="1" id="stop878" />
</linearGradient>
<linearGradient id="linearGradient3802">
<stop id="stop3804" offset="0" style="stop-color:#2e3436;stop-opacity:1" />
<stop id="stop3806" offset="1" style="stop-color:#555753;stop-opacity:1" />
</linearGradient>
<linearGradient gradientUnits="userSpaceOnUse" y2="42" x2="47" y1="58" x1="49" id="linearGradient3808" xlink:href="#linearGradient3802" />
<linearGradient id="linearGradient3767">
<stop style="stop-color:#edd400;stop-opacity:1" offset="0" id="stop3769" />
<stop style="stop-color:#fce94f;stop-opacity:1" offset="1" id="stop3771" />
</linearGradient>
<linearGradient xlink:href="#linearGradient3767" id="linearGradient985" gradientUnits="userSpaceOnUse" gradientTransform="translate(0,-4)" x1="32.567314" y1="41.431564" x2="5.3436856" y2="34.638504" />
<linearGradient gradientTransform="translate(-48.762712,7.1864407)" xlink:href="#linearGradient882" id="linearGradient4039" x1="89.610168" y1="46.389832" x2="91.338982" y2="32.542374" gradientUnits="userSpaceOnUse" />
<linearGradient id="linearGradient4033">
<stop style="stop-color:#73d216;stop-opacity:1" offset="0" id="stop4035" />
<stop id="stop4041" offset="0.5" style="stop-color:#729fcf;stop-opacity:1" />
<stop style="stop-color:#cc0000;stop-opacity:1" offset="1" id="stop4037" />
</linearGradient>
<linearGradient xlink:href="#linearGradient940" id="linearGradient942" x1="14.474576" y1="24.519833" x2="32" y2="28.99441" gradientUnits="userSpaceOnUse" />
<linearGradient xlink:href="#linearGradient959" id="linearGradient951" gradientUnits="userSpaceOnUse" x1="14.474576" y1="22.892714" x2="23.237288" y2="26.757122" />
<linearGradient xlink:href="#linearGradient959" id="linearGradient968" gradientUnits="userSpaceOnUse" x1="12.863206" y1="12.118106" x2="12.863206" y2="46.133854" gradientTransform="matrix(0.94458924,0,0,0.94225828,21.548537,4.555805)" />
</defs>
<metadata id="metadata7">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>[Alexander Gryson]</dc:title>
</cc:Agent>
</dc:creator>
<dc:date>2017-03-11</dc:date>
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson</dc:title>
</cc:Agent>
</dc:contributor>
</cc:Work>
</rdf:RDF>
</metadata>
<path style="fill:url(#linearGradient968);fill-opacity:1;stroke:#302b00;stroke-width:2.00001;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" d="M 47.979387,15.97419 H 15.848478 V 48.025811 H 47.979387 Z" id="path987" />
<g id="g1227" transform="matrix(0.94458924,0,0,0.94225826,1.5680741,1.7290257)" style="stroke-width:1.05998">
<path style="fill:none;stroke:black;stroke-width:1.05998px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 30.23622,15.11811 C 30.23622,15.11811 14.253491,-15.11811 0,-15.11811 C -14.253491,-15.11811 -24.781645,1.9496015 -30.23622,15.11811 C -34.575308,25.593594 -34.575308,38.658374 -30.23622,49.133858 C -24.781645,62.302367 -14.253491,79.370079 0,79.370079 C 14.253491,79.370079 30.23622,49.133858 30.23622,49.133858" id="path556" />
<path style="fill:none;stroke:black;stroke-width:1.05998px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 22.677165,15.11811 C 22.677165,15.11811 20.752297,0 15.11811,0 C 3.8497361,0 0.25590134,19.929541 0,31.195009 C -0.28178213,43.599819 2.7132999,63.970187 15.11811,64.251969 C 20.750844,64.37992 22.677165,49.133858 22.677165,49.133858" id="path621" />
<path style="fill:none;stroke:black;stroke-width:1.05998px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 15.11811,15.11811 C 15.11811,15.11811 7.7932043,25.51775 7.5590551,31.147069 C 7.2771021,37.925667 15.11811,49.133858 15.11811,49.133858" id="path623" />
</g>
<g id="g1227-3" transform="matrix(-0.94458924,0,0,-0.94225826,62.259791,62.270969)" style="stroke-width:1.05998">
<path style="fill:none;stroke:black;stroke-width:1.05998px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 30.23622,15.11811 C 30.23622,15.11811 14.253491,-15.11811 0,-15.11811 C -14.253491,-15.11811 -24.781645,1.9496015 -30.23622,15.11811 C -34.575308,25.593594 -34.575308,38.658374 -30.23622,49.133858 C -24.781645,62.302367 -14.253491,79.370079 0,79.370079 C 14.253491,79.370079 30.23622,49.133858 30.23622,49.133858" id="path556-9" />
<path style="fill:none;stroke:black;stroke-width:1.05998px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 22.677165,15.11811 C 22.677165,15.11811 20.752297,0 15.11811,0 C 3.8497361,0 0.25590134,19.929541 0,31.195009 C -0.28178213,43.599819 2.7132999,63.970187 15.11811,64.251969 C 20.750844,64.37992 22.677165,49.133858 22.677165,49.133858" id="path621-3" />
<path style="fill:none;stroke:black;stroke-width:1.05998px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" d="M 15.11811,15.11811 C 15.11811,15.11811 7.7932043,25.51775 7.5590551,31.147069 C 7.2771021,37.925667 15.11811,49.133858 15.11811,49.133858" id="path623-3" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -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"

View File

@@ -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"

View File

@@ -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()

View File

@@ -0,0 +1,141 @@
# ***************************************************************************
# * Copyright (c) 2023 Uwe Stöhr <uwestoehr@lyx.org> *
# * *
# * 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
## @}

View File

@@ -0,0 +1,208 @@
# ***************************************************************************
# * Copyright (c) 2023 Uwe Stöhr <uwestoehr@lyx.org> *
# * *
# * 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))
## @}

View File

@@ -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):

View File

@@ -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):

View File

@@ -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"
## @}

View File

@@ -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()

View File

@@ -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

View File

@@ -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