[FEM] Elmer: add support for 3D magnetodynamics

- adds the corresponding Elmer equation (it is now possible to do Elmer's tutorial example no. 14)
This commit is contained in:
Uwe
2023-02-05 07:46:12 +01:00
parent 5a39a9b4b1
commit 3b653b78f9
15 changed files with 837 additions and 17 deletions

View File

@@ -266,6 +266,8 @@ SET(FemSolverElmerEquations_SRCS
femsolver/elmer/equations/heat.py
femsolver/elmer/equations/heat_writer.py
femsolver/elmer/equations/linear.py
femsolver/elmer/equations/magnetodynamic.py
femsolver/elmer/equations/magnetodynamic_writer.py
femsolver/elmer/equations/magnetodynamic2D.py
femsolver/elmer/equations/magnetodynamic2D_writer.py
femsolver/elmer/equations/nonlinear.py

View File

@@ -1386,6 +1386,8 @@ void CmdFEMCompEmEquations::activated(int iMsg)
else if (iMsg == 1)
rcCmdMgr.runCommandByName("FEM_EquationElectricforce");
else if (iMsg == 2)
rcCmdMgr.runCommandByName("FEM_EquationMagnetodynamic");
else if (iMsg == 3)
rcCmdMgr.runCommandByName("FEM_EquationMagnetodynamic2D");
else
return;
@@ -1410,7 +1412,9 @@ Gui::Action* CmdFEMCompEmEquations::createAction()
QAction* cmd1 = pcAction->addAction(QString());
cmd1->setIcon(Gui::BitmapFactory().iconFromTheme("FEM_EquationElectricforce"));
QAction* cmd2 = pcAction->addAction(QString());
cmd2->setIcon(Gui::BitmapFactory().iconFromTheme("FEM_EquationMagnetodynamic2D"));
cmd2->setIcon(Gui::BitmapFactory().iconFromTheme("FEM_EquationMagnetodynamic"));
QAction* cmd3 = pcAction->addAction(QString());
cmd3->setIcon(Gui::BitmapFactory().iconFromTheme("FEM_EquationMagnetodynamic2D"));
_pcAction = pcAction;
languageChange();
@@ -1456,15 +1460,27 @@ void CmdFEMCompEmEquations::languageChange()
EquationElectricforce->getStatusTip()));
}
Gui::Command* EquationMagnetodynamic =
rcCmdMgr.getCommandByName("FEM_EquationMagnetodynamic");
if (EquationMagnetodynamic) {
QAction* cmd2 = a[2];
cmd2->setText(QApplication::translate("FEM_EquationMagnetodynamic",
EquationMagnetodynamic->getMenuText()));
cmd2->setToolTip(QApplication::translate("FEM_EquationMagnetodynamic",
EquationMagnetodynamic->getToolTipText()));
cmd2->setStatusTip(QApplication::translate("FEM_EquationMagnetodynamic",
EquationMagnetodynamic->getStatusTip()));
}
Gui::Command* EquationMagnetodynamic2D =
rcCmdMgr.getCommandByName("FEM_EquationMagnetodynamic2D");
if (EquationMagnetodynamic2D) {
QAction* cmd2 = a[2];
cmd2->setText(QApplication::translate("FEM_EquationMagnetodynamic2D",
QAction* cmd3 = a[3];
cmd3->setText(QApplication::translate("FEM_EquationMagnetodynamic2D",
EquationMagnetodynamic2D->getMenuText()));
cmd2->setToolTip(QApplication::translate("FEM_EquationMagnetodynamic2D",
cmd3->setToolTip(QApplication::translate("FEM_EquationMagnetodynamic2D",
EquationMagnetodynamic2D->getToolTipText()));
cmd2->setStatusTip(QApplication::translate("FEM_EquationMagnetodynamic2D",
cmd3->setStatusTip(QApplication::translate("FEM_EquationMagnetodynamic2D",
EquationMagnetodynamic2D->getStatusTip()));
}
}

View File

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

View File

@@ -0,0 +1,97 @@
<?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="linearGradient1060">
<stop style="stop-color:red;stop-opacity:1" offset="0" id="stop1056" />
<stop style="stop-color:blue;stop-opacity:1" offset="1" id="stop1058" />
</linearGradient>
<linearGradient id="linearGradient1052">
<stop style="stop-color:blue;stop-opacity:1" offset="0" id="stop1048" />
<stop style="stop-color:red;stop-opacity:1" offset="1" id="stop1050" />
</linearGradient>
<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,-158.32025,53.58162)" />
<linearGradient xlink:href="#linearGradient1052" id="linearGradient1054" x1="30.187231" y1="32" x2="55.119591" y2="32.922386" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.0453455,0,0,1.0605002,-0.66254501,-3.080369)" />
<linearGradient xlink:href="#linearGradient1060" id="linearGradient1062" x1="14.611035" y1="32" x2="30.187231" y2="32" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.0453455,0,0,1.0605002,-0.66254501,-3.080369)" />
</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 -131.8894,65.000005 H -164.02031 V 97.051626 H -131.8894 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.426906,45.993393 22.426906,45.993393" 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>
<g id="g433" style="fill:#a40000;stroke:#000101">
<path id="path445" style="fill:#e9e9ff;fill-rule:evenodd;stroke:none;stroke-width:1.0529;stroke-linejoin:round" d="M 25.65263,20.562163 L 47.979387,15.974185 V 49.879802 L 25.65263,39.655497 Z" points="47.979387,15.974185 47.979387,49.879802 25.65263,39.655497 25.65263,20.562163 " />
<path id="path435" style="fill:#353564;fill-rule:evenodd;stroke:none;stroke-width:1.8119;stroke-linejoin:round" d="M 14.611035,18.300799 V 39.218372 L 25.65263,39.655497 V 20.562163 Z" points="14.611035,39.218372 25.65263,39.655497 25.65263,20.562163 14.611035,18.300799 " />
<path id="path443" style="fill:#afafde;fill-rule:evenodd;stroke:none;stroke-width:1.0529;stroke-linejoin:round" d="M 14.611035,39.218372 L 30.893542,50.914988 L 47.979387,49.879802 L 25.65263,39.655497 Z" points="30.893542,50.914988 47.979387,49.879802 25.65263,39.655497 14.611035,39.218372 " />
<path id="path437" style="fill:#4d4d9f;fill-rule:evenodd;stroke:none;stroke-width:1.0529;stroke-linejoin:round" d="M 14.611035,18.300799 L 30.893542,10.79629 L 47.979387,15.974185 L 25.65263,20.562163 Z" points="30.893542,10.79629 47.979387,15.974185 25.65263,20.562163 14.611035,18.300799 " />
<path id="path441" style="fill:url(#linearGradient1054);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.0529;stroke-linejoin:round" d="M 30.893542,10.79629 V 50.914988 L 47.979387,49.879802 V 15.974185 Z" points="30.893542,50.914988 47.979387,49.879802 47.979387,15.974185 30.893542,10.79629 " />
<path id="path439" style="fill:url(#linearGradient1062);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.0529;stroke-linejoin:round" d="M 14.611035,18.300799 L 30.893542,10.79629 V 50.914988 L 14.611035,39.218372 Z" points="30.893542,10.79629 30.893542,50.914988 14.611035,39.218372 14.611035,18.300799 " />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -63,7 +63,10 @@
</size>
</property>
<property name="toolTip">
<string>Real part of potential x-component</string>
<string>Real part of potential x-component
Note: if a face was selected this will be the value
in normal face direction
settings for y and z will be ignored</string>
</property>
<property name="keyboardTracking">
<bool>true</bool>
@@ -107,7 +110,10 @@
</size>
</property>
<property name="toolTip">
<string>Imaginary part of potential x-component</string>
<string>Imaginary part of potential x-component
Note: if a face was selected this will be the value
in normal face direction
settings for y and z will be ignored</string>
</property>
<property name="keyboardTracking">
<bool>true</bool>

View File

@@ -241,7 +241,8 @@ with a harmonic/oscillating driving force</string>
</size>
</property>
<property name="toolTip">
<string>Real part of potential x-component</string>
<string>Real part of potential x-component
Note: has no effect if a solid was selected</string>
</property>
<property name="keyboardTracking">
<bool>true</bool>
@@ -285,7 +286,8 @@ with a harmonic/oscillating driving force</string>
</size>
</property>
<property name="toolTip">
<string>Imaginary part of potential x-component</string>
<string>Imaginary part of potential x-component
Note: has no effect if a solid was selected</string>
</property>
<property name="keyboardTracking">
<bool>true</bool>
@@ -339,7 +341,8 @@ with a harmonic/oscillating driving force</string>
</size>
</property>
<property name="toolTip">
<string>Real part of potential y-component</string>
<string>Real part of potential y-component
Note: has no effect if a solid was selected</string>
</property>
<property name="keyboardTracking">
<bool>true</bool>
@@ -383,7 +386,8 @@ with a harmonic/oscillating driving force</string>
</size>
</property>
<property name="toolTip">
<string>Imaginary part of potential y-component</string>
<string>Imaginary part of potential y-component
Note: has no effect if a solid was selected</string>
</property>
<property name="keyboardTracking">
<bool>true</bool>
@@ -437,7 +441,8 @@ with a harmonic/oscillating driving force</string>
</size>
</property>
<property name="toolTip">
<string>Real part of potential z-component</string>
<string>Real part of potential z-component
Note: has no effect if a solid was selected</string>
</property>
<property name="keyboardTracking">
<bool>true</bool>
@@ -481,7 +486,8 @@ with a harmonic/oscillating driving force</string>
</size>
</property>
<property name="toolTip">
<string>Imaginary part of potential z-component</string>
<string>Imaginary part of potential z-component
Note: has no effect if a solid was selected</string>
</property>
<property name="keyboardTracking">
<bool>true</bool>

View File

@@ -817,6 +817,20 @@ def makeEquationHeat(
return obj
def makeEquationMagnetodynamic(
doc,
base_solver=None,
name="Magnetodynamic"
):
"""makeEquationMagnetodynamic(document, [base_solver], [name]):
creates a FEM magnetodynamic equation for a solver"""
from femsolver.elmer.equations import magnetodynamic
obj = magnetodynamic.create(doc, name)
if base_solver:
base_solver.addObject(obj)
return obj
def makeEquationMagnetodynamic2D(
doc,
base_solver=None,

View File

@@ -533,6 +533,23 @@ class _EquationHeat(CommandManager):
self.do_activated = "add_obj_on_gui_selobj_noset_edit"
class _EquationMagnetodynamic(CommandManager):
"The FEM_EquationMagnetodynamic command definition"
def __init__(self):
super(_EquationMagnetodynamic, self).__init__()
self.menutext = Qt.QT_TRANSLATE_NOOP(
"FEM_EquationMagnetodynamic",
"Magnetodynamic equation"
)
self.tooltip = Qt.QT_TRANSLATE_NOOP(
"FEM_EquationMagnetodynamic",
"Creates a FEM equation for\n magentodynamic forces"
)
self.is_active = "with_solver_elmer"
self.do_activated = "add_obj_on_gui_selobj_noset_edit"
class _EquationMagnetodynamic2D(CommandManager):
"The FEM_EquationMagnetodynamic2D command definition"
@@ -1239,6 +1256,10 @@ FreeCADGui.addCommand(
"FEM_EquationHeat",
_EquationHeat()
)
FreeCADGui.addCommand(
"FEM_EquationMagnetodynamic",
_EquationMagnetodynamic()
)
FreeCADGui.addCommand(
"FEM_EquationMagnetodynamic2D",
_EquationMagnetodynamic2D()

View File

@@ -0,0 +1,206 @@
# ***************************************************************************
# * 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 Magnetodynamic"
__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="Magnetodynamic"):
return femutils.createObject(
doc, name, Proxy, ViewProxy)
class Proxy(nonlinear.Proxy, equationbase.MagnetodynamicProxy):
Type = "Fem::EquationElmerMagnetodynamic"
def __init__(self, obj):
super(Proxy, self).__init__(obj)
obj.addProperty(
"App::PropertyBool",
"IsHarmonic",
"Magnetodynamic",
"If the magnetic source is harmonically driven"
)
obj.addProperty(
"App::PropertyFrequency",
"AngularFrequency",
"Magnetodynamic",
"Frequency of the driving current"
)
obj.addProperty(
"App::PropertyBool",
"UsePiolaTransform",
"Magnetodynamic",
"Must be True if basis functions for edge element interpolation\n"
"are selected to be members of optimal edge element family\n"
"or if second-order approximation is used."
)
obj.addProperty(
"App::PropertyBool",
"QuadraticApproximation",
"Magnetodynamic",
"Enables second-order approximation of driving current"
)
obj.addProperty(
"App::PropertyBool",
"StaticConductivity",
"Magnetodynamic",
"See Elmer models manual for info"
)
obj.addProperty(
"App::PropertyBool",
"FixInputCurrentDensity",
"Magnetodynamic",
"Ensures divergence-freeness of current density"
)
obj.addProperty(
"App::PropertyBool",
"AutomatedSourceProjectionBCs",
"Magnetodynamic",
"See Elmer models manual for info"
)
obj.addProperty(
"App::PropertyBool",
"UseLagrangeGauge",
"Magnetodynamic",
"See Elmer models manual for info"
)
obj.addProperty(
"App::PropertyFloat",
"LagrangeGaugePenalizationCoefficient",
"Magnetodynamic",
"See Elmer models manual for info"
)
obj.addProperty(
"App::PropertyBool",
"UseTreeGauge",
"Magnetodynamic",
"See Elmer models manual for info\n"
"Will be ignored if 'UsePiolaTransform' is True"
)
obj.addProperty(
"App::PropertyBool",
"LinearSystemRefactorize",
"Linear System",
""
)
obj.IsHarmonic = False
obj.AngularFrequency = 0
obj.Priority = 10
# the post processor options
obj.addProperty(
"App::PropertyBool",
"CalculateCurrentDensity",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateElectricField",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateElementalFields",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateHarmonicLoss",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateJouleHeating",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateMagneticFieldStrength",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateMaxwellStress",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateNodalFields",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateNodalForces",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"CalculateNodalHeating",
"Results",
""
)
obj.addProperty(
"App::PropertyBool",
"DiscontinuousBodies",
"Results",
""
)
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
obj.DiscontinuousBodies = False
class ViewProxy(nonlinear.ViewProxy, equationbase.MagnetodynamicViewProxy):
pass
## @}

View File

@@ -0,0 +1,375 @@
# ***************************************************************************
# * 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 Magnetodynamics 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 MgDynwriter:
def __init__(self, writer, solver):
self.write = writer
self.solver = solver
def getMagnetodynamicSolver(self, equation):
# output the equation parameters
s = self.write.createNonlinearSolver(equation)
if not equation.IsHarmonic:
s["Equation"] = "MgDyn"
s["Procedure"] = sifio.FileAttr("MagnetoDynamics/WhitneyAVSolver")
s["Variable"] = "av"
else:
s["Equation"] = "MgDynHarmonic"
s["Procedure"] = sifio.FileAttr("MagnetoDynamics/WhitneyAVHarmonicSolver")
s["Variable"] = "av[av re:1 av im:1]"
# round to get rid of numerical artifacts
frequency = float(Units.Quantity(equation.AngularFrequency).Value)
s["Angular Frequency"] = round(frequency, 6)
s["Exec Solver"] = "Always"
s["Optimize Bandwidth"] = True
s["Stabilize"] = equation.Stabilize
if equation.LinearSystemRefactorize is True:
s["Linear System Refactorize"] = True
if equation.UsePiolaTransform is True:
s["Use Piola Transform"] = True
if equation.QuadraticApproximation is True:
s["Quadratic Approximation"] = True
if equation.StaticConductivity is True:
s["Static Conductivity"] = True
if equation.FixInputCurrentDensity is True:
s["Fix Input Current Density"] = True
if equation.AutomatedSourceProjectionBCs is True:
s["Automated Source Projection BCs"] = True
if equation.UseLagrangeGauge is True:
s["Use Lagrange Gauge"] = True
if equation.LagrangeGaugePenalizationCoefficient != 0.0:
s["Lagrange Gauge Penalization Coefficient"] = \
equation.LagrangeGaugePenalizationCoefficient
if equation.UseTreeGauge is True:
s["Use Tree Gauge"] = True
return s
def getMagnetodynamicSolverPost(self, equation):
# output the equation parameters
s = self.write.createNonlinearSolver(equation)
s["Equation"] = "MgDynPost"
s["Exec Solver"] = "Before Saving"
s["Procedure"] = sifio.FileAttr("MagnetoDynamics/MagnetoDynamicsCalcFields")
if equation.IsHarmonic:
frequency = float(Units.Quantity(equation.AngularFrequency).Value)
s["Angular Frequency"] = round(frequency, 6)
s["Potential Variable"] = "av"
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
if equation.DiscontinuousBodies is True:
s["Discontinuous Bodies"] = True
s["Optimize Bandwidth"] = True
s["Stabilize"] = equation.Stabilize
return s
def handleMagnetodynamicConstants(self):
permeability = self.write.convert(
self.write.constsdef["PermeabilityOfVacuum"],
"M*L/(T^2*I^2)"
)
# we round in the following to get rid of numerical artifacts
self.write.constant("Permeability Of Vacuum", round(permeability, 20))
permittivity = self.write.convert(
self.write.constsdef["PermittivityOfVacuum"],
"T^4*I^2/(L^3*M)"
)
self.write.constant("Permittivity Of Vacuum", round(permittivity, 20))
def handleMagnetodynamicMaterial(self, bodies):
# 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 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 _outputMagnetodynamicBodyForce(self, obj, name, equation):
if hasattr(obj, "CurrentDensity_re_1"):
# output only if current density is enabled and needed
if not obj.CurrentDensity_re_1_Disabled:
currentDensity = float(obj.CurrentDensity_re_1.getValueAs("A/m^2"))
self.write.bodyForce(name, "Current Density 1", round(currentDensity, 6))
if not obj.CurrentDensity_re_2_Disabled:
currentDensity = float(obj.CurrentDensity_re_2.getValueAs("A/m^2"))
self.write.bodyForce(name, "Current Density 2", round(currentDensity, 6))
if not obj.CurrentDensity_re_3_Disabled:
currentDensity = float(obj.CurrentDensity_re_3.getValueAs("A/m^2"))
self.write.bodyForce(name, "Current Density 3", round(currentDensity, 6))
# imaginaries are only needed for harmonic equation
if equation.IsHarmonic:
if not obj.CurrentDensity_im_1_Disabled:
currentDensity = float(obj.CurrentDensity_im_1.getValueAs("A/m^2"))
self.write.bodyForce(name, "Current Density Im 1", round(currentDensity, 6))
if not obj.CurrentDensity_im_2_Disabled:
currentDensity = float(obj.CurrentDensity_im_2.getValueAs("A/m^2"))
self.write.bodyForce(name, "Current Density Im 2", round(currentDensity, 6))
if not obj.CurrentDensity_im_3_Disabled:
currentDensity = float(obj.CurrentDensity_im_3.getValueAs("A/m^2"))
self.write.bodyForce(name, "Current Density Im 3", round(currentDensity, 6))
if hasattr(obj, "Magnetization_im_1"):
# output only if magnetization is enabled and needed
if not obj.Magnetization_re_1_Disabled:
magnetization = float(obj.Magnetization_re_1.getValueAs("A/m"))
self.write.bodyForce(name, "Magnetization 1", magnetization)
if not obj.Magnetization_re_2_Disabled:
magnetization = float(obj.Magnetization_re_2.getValueAs("A/m"))
self.write.bodyForce(name, "Magnetization 2", magnetization)
if not obj.Magnetization_re_3_Disabled:
magnetization = float(obj.Magnetization_re_3.getValueAs("A/m"))
self.write.bodyForce(name, "Magnetization 3", magnetization)
# imaginaries are only needed for harmonic equation
if equation.IsHarmonic:
if not obj.Magnetization_im_1_Disabled:
magnetization = float(obj.Magnetization_im_1.getValueAs("A/m"))
self.write.bodyForce(name, "Magnetization Im 1", magnetization)
if not obj.Magnetization_im_2_Disabled:
magnetization = float(obj.Magnetization_im_2.getValueAs("A/m"))
self.write.bodyForce(name, "Magnetization Im 2", magnetization)
if not obj.Magnetization_im_3_Disabled:
magnetization = float(obj.Magnetization_im_3.getValueAs("A/m"))
self.write.bodyForce(name, "Magnetization Im 3", magnetization)
if hasattr(obj, "PotentialEnabled"):
# check for PotentialEnabled not Potential since PotentialEnabled was
# added later and only with this the imaginary property is available
if obj.PotentialEnabled:
# output only if potential is enabled and needed
potential = float(obj.Potential.getValueAs("V"))
self.write.bodyForce(name, "Electric Potential", round(potential, 6))
# imaginary is only needed for harmonic equation
if equation.IsHarmonic:
if not obj.AV_im_Disabled:
potential = float(obj.AV_im.getValueAs("V"))
self.write.bodyForce(name, "Electric Potential Im", round(potential, 6))
def handleMagnetodynamicBodyForces(self, bodies, equation):
# the current density can either be a body force or a boundary constraint
# therefore only output here if a solid is referenced
currentDensities = self.write.getMember("Fem::ConstraintCurrentDensity")
for obj in currentDensities:
if obj.References:
firstName = obj.References[0][1][0]
firstName = firstName.rstrip('0123456789')
if firstName == "Solid":
for name in obj.References[0][1]:
self._outputMagnetodynamicBodyForce(obj, name, equation)
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._outputMagnetodynamicBodyForce(obj, name, equation)
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)
magnetizations = self.write.getMember("Fem::ConstraintMagnetization")
for obj in magnetizations:
if obj.References:
for name in obj.References[0][1]:
self._outputMagnetodynamicBodyForce(obj, name, equation)
self.write.handled(obj)
else:
# if there is only one magnetization without a reference,
# add it to all bodies
if len(magnetizations) == 1:
for name in bodies:
self._outputMagnetodynamicBodyForce(obj, name, equation)
else:
raise general_writer.WriteError(
"Several magnetization constraints found without reference to a body.\n"
"Please set a body for each current density constraint."
)
self.write.handled(obj)
# the potential can either be a body force or a boundary constraint
# therefore only output here if a solid is referenced
potentials = self.write.getMember("Fem::ConstraintElectrostaticPotential")
for obj in potentials:
if obj.References:
firstName = obj.References[0][1][0]
firstName = firstName.rstrip('0123456789')
if firstName == "Solid":
for name in obj.References[0][1]:
# output only if potentiual is enabled and needed
self._outputMagnetodynamicBodyForce(obj, name, equation)
self.write.handled(obj)
def _outputMagnetodynamicBndConditions(self, obj, name, equation):
if hasattr(obj, "CurrentDensity_re_1"):
# output only if current density is enabled and needed
if not obj.CurrentDensity_re_1_Disabled:
currentDensity = float(obj.CurrentDensity_re_1.getValueAs("A/m^2"))
self.write.boundary(name, "Current Density 1", round(currentDensity, 6))
# imaginaries are only needed for harmonic equation
if equation.IsHarmonic:
if not obj.CurrentDensity_im_1_Disabled:
currentDensity = float(obj.CurrentDensity_im_1.getValueAs("A/m^2"))
self.write.boundary(name, "Current Density Im 1", round(currentDensity, 6))
if hasattr(obj, "PotentialEnabled"):
# check for PotentialEnabled not Potential since PotentialEnabled was
# added later and only with this the vectorial properties are available
if obj.PotentialEnabled:
potential = float(obj.Potential.getValueAs("V"))
if equation.IsHarmonic:
self.write.boundary(name, "AV re", round(potential, 6))
else:
self.write.boundary(name, "AV", round(potential, 6))
if not obj.AV_re_1_Disabled:
potential = float(obj.AV_re_1.getValueAs("V"))
if equation.IsHarmonic:
self.write.boundary(name, "AV re {e} 1", round(potential, 6))
else:
self.write.boundary(name, "AV {e} 1", round(potential, 6))
if not obj.AV_re_2_Disabled:
potential = float(obj.AV_re_2.getValueAs("V"))
if equation.IsHarmonic:
self.write.boundary(name, "AV re {e} 2", round(potential, 6))
else:
self.write.boundary(name, "AV {e} 2", round(potential, 6))
if not obj.AV_re_3_Disabled:
potential = float(obj.AV_re_3.getValueAs("V"))
if equation.IsHarmonic:
self.write.boundary(name, "AV re {e} 3", round(potential, 6))
else:
self.write.boundary(name, "AV {e} 3", round(potential, 6))
# imaginaries are only needed for harmonic equation
if equation.IsHarmonic:
if not obj.AV_im_Disabled:
potential = float(obj.AV_im.getValueAs("V"))
self.write.boundary(name, "AV im", round(potential, 6))
if not obj.AV_im_1_Disabled:
potential = float(obj.AV_im_1.getValueAs("V"))
self.write.boundary(name, "AV im {e} 1", round(potential, 6))
if not obj.AV_im_2_Disabled:
potential = float(obj.AV_im_2.getValueAs("V"))
self.write.boundary(name, "AV im {e} 2", round(potential, 6))
if not obj.AV_im_3_Disabled:
potential = float(obj.AV_im_3.getValueAs("V"))
self.write.boundary(name, "AV im {e} 3", round(potential, 6))
def handleMagnetodynamicBndConditions(self, equation):
# the current density can either be a body force or a boundary constraint
# therefore only output here if a face is referenced
currentDensities = self.write.getMember("Fem::ConstraintCurrentDensity")
for obj in currentDensities:
if obj.References:
firstName = obj.References[0][1][0]
firstName = firstName.rstrip('0123456789')
if firstName == "Face":
for name in obj.References[0][1]:
self._outputMagnetodynamicBndConditions(obj, name, equation)
self.write.handled(obj)
# the potential can either be a body force or a boundary constraint
# therefore only output here if a face is referenced
potentials = self.write.getMember("Fem::ConstraintElectrostaticPotential")
if len(potentials) == 0:
raise general_writer.WriteError(
"The Magnetodynamic equation needs at least one ElectrostaticPotential"
"constraint."
)
for obj in potentials:
if obj.References:
firstName = obj.References[0][1][0]
firstName = firstName.rstrip('0123456789')
if firstName == "Face":
for name in obj.References[0][1]:
# output the FreeCAD label as comment
if obj.Label:
self.write.boundary(name, "! FreeCAD Name", obj.Label)
# output only if potentiual is enabled and needed
self._outputMagnetodynamicBndConditions(obj, name, equation)
self.write.handled(obj)
## @}

View File

@@ -54,6 +54,7 @@ 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 magnetodynamic_writer as MgDyn_writer
from .equations import magnetodynamic2D_writer as MgDyn2D_writer
@@ -99,6 +100,7 @@ class Writer(object):
self._handleHeat()
self._handleFlow()
self._handleFlux()
self._handleMagnetodynamic()
self._handleMagnetodynamic2D()
self._addOutputSolver()
@@ -534,6 +536,30 @@ class Writer(object):
HeatW.handleHeatBodyForces(activeIn)
HeatW.handleHeatMaterial(activeIn)
#-------------------------------------------------------------------------------------------
# Magnetodynamic
def _handleMagnetodynamic(self):
MgDyn = MgDyn_writer.MgDynwriter(self, self.solver)
activeIn = []
for equation in self.solver.Group:
if femutils.is_of_type(equation, "Fem::EquationElmerMagnetodynamic"):
if equation.References:
activeIn = equation.References[0][1]
else:
activeIn = self.getAllBodies()
solverSection = MgDyn.getMagnetodynamicSolver(equation)
solverPostSection = MgDyn.getMagnetodynamicSolverPost(equation)
for body in activeIn:
self._addSolver(body, solverSection)
self._addSolver(body, solverPostSection)
if activeIn:
MgDyn.handleMagnetodynamicConstants()
MgDyn.handleMagnetodynamicBndConditions(equation)
MgDyn.handleMagnetodynamicBodyForces(activeIn, equation)
MgDyn.handleMagnetodynamicMaterial(activeIn)
#-------------------------------------------------------------------------------------------
# Magnetodynamic2D

View File

@@ -129,6 +129,16 @@ class HeatViewProxy(BaseViewProxy):
return ":/icons/FEM_EquationHeat.svg"
class MagnetodynamicProxy(BaseProxy):
pass
class MagnetodynamicViewProxy(BaseViewProxy):
def getIcon(self):
return ":/icons/FEM_EquationMagnetodynamic.svg"
class Magnetodynamic2DProxy(BaseProxy):
pass

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 --> 7
# solver children: equations --> 8
# 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: -16
# thus: -17
self.assertEqual(len(doc.Analysis.Group), count_defmake - 16)
self.assertEqual(len(doc.Analysis.Group), count_defmake - 17)
self.assertEqual(len(doc.Objects), count_defmake)
fcc_print("doc objects count: {}, method: {}".format(
@@ -374,6 +374,10 @@ class TestObjectType(unittest.TestCase):
"Fem::EquationElmerMagnetodynamic2D",
type_of_obj(ObjectsFem.makeEquationMagnetodynamic2D(doc, solverelmer))
)
self.assertEqual(
"Fem::EquationElmerMagnetodynamic",
type_of_obj(ObjectsFem.makeEquationMagnetodynamic(doc, solverelmer))
)
fcc_print("doc objects count: {}, method: {}".format(
len(doc.Objects),
@@ -613,6 +617,10 @@ class TestObjectType(unittest.TestCase):
ObjectsFem.makeEquationMagnetodynamic2D(doc, solverelmer),
"Fem::EquationElmerMagnetodynamic2D"
))
self.assertTrue(is_of_type(
ObjectsFem.makeEquationMagnetodynamic(doc, solverelmer),
"Fem::EquationElmerMagnetodynamic"
))
fcc_print("doc objects count: {}, method: {}".format(
len(doc.Objects),
@@ -1453,7 +1461,7 @@ class TestObjectType(unittest.TestCase):
"Fem::EquationElmerHeat"
))
# EquationElmerMagnetodynamic2D
# EquationElmerMagnetodynamic2D
equation_magnetodynamic2D = ObjectsFem.makeEquationMagnetodynamic2D(doc, solver_elmer)
self.assertTrue(is_derived_from(
equation_magnetodynamic2D,
@@ -1468,6 +1476,21 @@ class TestObjectType(unittest.TestCase):
"Fem::EquationElmerMagnetodynamic2D"
))
# EquationElmerMagnetodynamic
equation_magnetodynamic = ObjectsFem.makeEquationMagnetodynamic(doc, solver_elmer)
self.assertTrue(is_derived_from(
equation_magnetodynamic,
"App::DocumentObject"
))
self.assertTrue(is_derived_from(
equation_magnetodynamic,
"App::FeaturePython"
))
self.assertTrue(is_derived_from(
equation_magnetodynamic,
"Fem::EquationElmerMagnetodynamic"
))
fcc_print("doc objects count: {}, method: {}".format(
len(doc.Objects),
sys._getframe().f_code.co_name)
@@ -1763,6 +1786,12 @@ class TestObjectType(unittest.TestCase):
solverelmer
).isDerivedFrom("App::FeaturePython")
)
self.assertTrue(
ObjectsFem.makeEquationMagnetodynamic(
doc,
solverelmer
).isDerivedFrom("App::FeaturePython")
)
fcc_print("doc objects count: {}, method: {}".format(
len(doc.Objects),
@@ -1845,6 +1874,7 @@ def create_all_fem_objects_doc(
ObjectsFem.makeEquationFlux(doc, sol)
ObjectsFem.makeEquationHeat(doc, sol)
ObjectsFem.makeEquationMagnetodynamic2D(doc, sol)
ObjectsFem.makeEquationMagnetodynamic(doc, sol)
doc.recompute()

View File

@@ -363,6 +363,11 @@ class TestObjectOpen(unittest.TestCase):
type_of_obj(ObjectsFem.makeEquationMagnetodynamic2D(doc))
)
self.assertEqual(
"Fem::EquationElmerMagnetodynamic",
type_of_obj(ObjectsFem.makeEquationMagnetodynamic(doc))
)
"""
# code was generated by the following code from a document with all objects

View File

@@ -338,6 +338,11 @@ class TestObjectOpen(unittest.TestCase):
type_of_obj(ObjectsFem.makeEquationMagnetodynamic2D(doc))
)
self.assertEqual(
"Fem::EquationElmerMagnetodynamic",
type_of_obj(ObjectsFem.makeEquationMagnetodynamic(doc))
)
"""
# code was generated by the following code from a document with all objects