Fem: Add support for Elmer static current solver - fixes #11895

This commit is contained in:
marioalexis
2025-02-24 17:39:24 -03:00
committed by Max Wilfinger
parent 3f9ad28acf
commit 3d79de4ab3
13 changed files with 1394 additions and 0 deletions

View File

@@ -285,6 +285,8 @@ SET(FemSolverElmerEquations_SRCS
femsolver/elmer/equations/magnetodynamic2D.py
femsolver/elmer/equations/magnetodynamic2D_writer.py
femsolver/elmer/equations/nonlinear.py
femsolver/elmer/equations/staticcurrent.py
femsolver/elmer/equations/staticcurrent_writer.py
)
SET(FemSolverFenics_SRCS

View File

@@ -1542,6 +1542,9 @@ void CmdFemCompEmEquations::activated(int iMsg)
else if (iMsg == 3) {
rcCmdMgr.runCommandByName("FEM_EquationMagnetodynamic2D");
}
else if (iMsg == 4) {
rcCmdMgr.runCommandByName("FEM_EquationStaticCurrent");
}
else {
return;
}
@@ -1569,6 +1572,8 @@ Gui::Action* CmdFemCompEmEquations::createAction()
cmd2->setIcon(Gui::BitmapFactory().iconFromTheme("FEM_EquationMagnetodynamic"));
QAction* cmd3 = pcAction->addAction(QString());
cmd3->setIcon(Gui::BitmapFactory().iconFromTheme("FEM_EquationMagnetodynamic2D"));
QAction* cmd4 = pcAction->addAction(QString());
cmd4->setIcon(Gui::BitmapFactory().iconFromTheme("FEM_EquationStaticCurrent"));
_pcAction = pcAction;
languageChange();
@@ -1637,6 +1642,17 @@ void CmdFemCompEmEquations::languageChange()
cmd3->setStatusTip(QApplication::translate("FEM_EquationMagnetodynamic2D",
EquationMagnetodynamic2D->getStatusTip()));
}
Gui::Command* EquationStaticCurrent = rcCmdMgr.getCommandByName("FEM_EquationStaticCurrent");
if (EquationStaticCurrent) {
QAction* cmd4 = a[4];
cmd4->setText(QApplication::translate("FEM_EquationStaticCurrent",
EquationStaticCurrent->getMenuText()));
cmd4->setToolTip(QApplication::translate("FEM_EquationStaticCurrent",
EquationStaticCurrent->getToolTipText()));
cmd4->setStatusTip(QApplication::translate("FEM_EquationStaticCurrent",
EquationStaticCurrent->getStatusTip()));
}
}
bool CmdFemCompEmEquations::isActive()

View File

@@ -51,6 +51,7 @@
<file>icons/FEM_EquationHeat.svg</file>
<file>icons/FEM_EquationMagnetodynamic.svg</file>
<file>icons/FEM_EquationMagnetodynamic2D.svg</file>
<file>icons/FEM_EquationStaticCurrent.svg</file>
<!-- gui command icons: meshes -->
<file>icons/FEM_CreateElementsSet.svg</file>

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -345,6 +345,41 @@ Note: for 2D only setting for x is possible,
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="l_normal">
<property name="text">
<string>Normal:</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="Gui::QuantitySpinBox" name="normalQSB">
<property name="enabled">
<bool>true</bool>
</property>
<property name="unit" stdset="0">
<string notr="true">A/m^2</string>
</property>
<property name="alignment">
<set>Qt::AlignLeft|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="keyboardTracking">
<bool>true</bool>
</property>
<property name="minimum">
<double>-1000000000000000000000.000000000000000</double>
</property>
<property name="maximum">
<double>1000000000000000000000.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>0.000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>

View File

@@ -767,6 +767,17 @@ def makeEquationMagnetodynamic2D(doc, base_solver=None, name="Magnetodynamic2D")
return obj
def makeEquationStaticCurrent(doc, base_solver=None, name="StaticCurrent"):
"""makeEquationStaticCurrent(document, [base_solver], [name]):
creates a FEM static current equation for a solver"""
from femsolver.elmer.equations import staticcurrent
obj = staticcurrent.create(doc, name)
if base_solver:
base_solver.addObject(obj)
return obj
def makeSolverCalculiXCcxTools(doc, name="SolverCcxTools"):
"""makeSolverCalculiXCcxTools(document, [name]):
makes a Calculix solver object for the ccx tools module"""

View File

@@ -502,6 +502,19 @@ class _EquationMagnetodynamic2D(CommandManager):
self.do_activated = "add_obj_on_gui_selobj_expand_noset_edit"
class _EquationStaticCurrent(CommandManager):
"The FEM_EquationStaticCurrent command definition"
def __init__(self):
super().__init__()
self.menutext = Qt.QT_TRANSLATE_NOOP("FEM_EquationStaticCurrent", "Static current equation")
self.tooltip = Qt.QT_TRANSLATE_NOOP(
"FEM_EquationStaticCurrent", "Creates a FEM equation for static current"
)
self.is_active = "with_solver_elmer"
self.do_activated = "add_obj_on_gui_selobj_expand_noset_edit"
class _Examples(CommandManager):
"The FEM_Examples command definition"
@@ -1179,6 +1192,7 @@ FreeCADGui.addCommand("FEM_EquationFlux", _EquationFlux())
FreeCADGui.addCommand("FEM_EquationHeat", _EquationHeat())
FreeCADGui.addCommand("FEM_EquationMagnetodynamic", _EquationMagnetodynamic())
FreeCADGui.addCommand("FEM_EquationMagnetodynamic2D", _EquationMagnetodynamic2D())
FreeCADGui.addCommand("FEM_EquationStaticCurrent", _EquationStaticCurrent())
FreeCADGui.addCommand("FEM_Examples", _Examples())
FreeCADGui.addCommand("FEM_MaterialEditor", _MaterialEditor())
FreeCADGui.addCommand("FEM_MaterialFluid", _MaterialFluid())

View File

@@ -98,6 +98,15 @@ class ConstraintCurrentDensity(base_fempythonobject.BaseFemPythonObject):
)
obj.setPropertyStatus("CurrentDensity_im_3", "LockDynamic")
obj.CurrentDensity_im_3 = "0 A/m^2"
if not hasattr(obj, "NormalCurrentDensity"):
obj.addProperty(
"App::PropertyCurrentDensity",
"NormalCurrentDensity",
"Current Density",
"Current density normal to boundary",
)
obj.setPropertyStatus("NormalCurrentDensity", "LockDynamic")
obj.NormalCurrentDensity = "0 A/m^2"
# now the enable bools
if not hasattr(obj, "CurrentDensity_re_1_Disabled"):

View File

@@ -0,0 +1,90 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * Copyright (c) 2025 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD 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 *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
__title__ = "FreeCAD FEM solver Elmer equation object StaticCurrent"
__author__ = "Mario Passaglia"
__url__ = "https://www.freecad.org"
## \addtogroup FEM
# @{
from femtools import femutils
from ... import equationbase
from . import linear
def create(doc, name="StaticCurrent"):
return femutils.createObject(doc, name, Proxy, ViewProxy)
class Proxy(linear.Proxy, equationbase.StaticCurrentProxy):
Type = "Fem::EquationElmerStaticCurrent"
def __init__(self, obj):
super().__init__(obj)
obj.addProperty("App::PropertyBool", "CalculateVolumeCurrent", "StaticCurrent", "")
obj.CalculateVolumeCurrent = True
obj.addProperty("App::PropertyBool", "CalculateJouleHeating", "StaticCurrent", "")
obj.addProperty(
"App::PropertyBool",
"ConstantWeights",
"StaticCurrent",
"Used to turn constant weighting on for the results",
)
obj.addProperty(
"App::PropertyBool",
"CalculateNodalHeating",
"StaticCurrent",
"Calculate nodal heating that may be used to couple the heat equation optimally when using conforming finite element meshes",
)
obj.addProperty(
"App::PropertyBool",
"HeatSource",
"StaticCurrent",
"Use Joule heating as a heat source in combination with heat equation",
)
obj.addProperty(
"App::PropertyBool",
"PowerControl",
"StaticCurrent",
"Apply power control with the desired heating power",
)
obj.addProperty(
"App::PropertyBool",
"CurrentControl",
"StaticCurrent",
"Apply current control with the desired current",
)
obj.addProperty(
"App::PropertyElectricCurrent", "Current", "StaticCurrent", "Current control value"
)
obj.addProperty("App::PropertyPower", "Power", "StaticCurrent", "Power control value")
class ViewProxy(linear.ViewProxy, equationbase.StaticCurrentViewProxy):
pass
## @}

View File

@@ -0,0 +1,111 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * Copyright (c) 2025 Mario Passaglia <mpassaglia[at]cbc.uba.ar> *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD 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 *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
__title__ = "FreeCAD FEM StaticCurrent Elmer writer"
__author__ = "Mario Passaglia"
__url__ = "https://www.freecad.org"
## \addtogroup FEM
# @{
from FreeCAD import Units
from .. import sifio
class SCwriter:
def __init__(self, writer, solver):
self.write = writer
self.solver = solver
def getStaticCurrentSolver(self, equation):
# output the equation parameters
s = self.write.createLinearSolver(equation)
s["Equation"] = "Stat Current Solver"
s["Procedure"] = sifio.FileAttr("StatCurrentSolve/StatCurrentSolver")
s["Variable"] = self.write.getUniqueVarName("Potential")
s["Variable DOFs"] = 1
s["Calculate Volume Current"] = equation.CalculateVolumeCurrent
s["Calculate Joule Heating"] = equation.CalculateJouleHeating
s["Constant Weights"] = equation.ConstantWeights
s["Calculate Nodal Heating"] = equation.CalculateNodalHeating
if equation.PowerControl:
s["Power Control"] = equation.Power.getValueAs("W").Value
if equation.CurrentControl:
s["Current Control"] = equation.Current.getValueAs("A").Value
s["Exec Solver"] = "Always"
s["Optimize Bandwidth"] = True
s["Stabilize"] = equation.Stabilize
return s
def handleStaticCurrentConstants(self):
pass
def handleStaticCurrentMaterial(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):
self.write.material(name, "Name", m["Name"])
if "ElectricalConductivity" in m:
self.write.material(
name,
"Electric Conductivity",
Units.Quantity(m["ElectricalConductivity"]).getValueAs("S/m").Value,
)
def handleStaticCurrentBndConditions(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.BoundaryCondition == "Dirichlet":
if obj.PotentialEnabled:
self.write.boundary(name, "Current Density BC", False)
self.write.boundary(
name, "Potential", obj.Potential.getValueAs("V").Value
)
self.write.handled(obj)
for obj in self.write.getMember("Fem::ConstraintCurrentDensity"):
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)
self.write.boundary(name, "Current Density BC", True)
self.write.boundary(
name, "Current Density", obj.NormalCurrentDensity.getValueAs("A/m^2").Value
)
self.write.handled(obj)
def handleStaticCurrentBodyForces(self, bodies, equation):
for name in bodies:
self.write.bodyForce(name, "Joule Heat", equation.HeatSource)
## @}

View File

@@ -57,6 +57,7 @@ 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
from .equations import staticcurrent_writer as SC_writer
_STARTINFO_NAME = "ELMERSOLVER_STARTINFO"
@@ -109,6 +110,7 @@ class Writer:
self._handleFlux()
self._handleMagnetodynamic()
self._handleMagnetodynamic2D()
self._handleStaticCurrent()
self._addOutputSolver()
self._writeSif()
@@ -614,6 +616,28 @@ class Writer:
MgDyn2D.handleMagnetodynamic2DBodyForces(activeIn, equation)
MgDyn2D.handleMagnetodynamic2DMaterial(activeIn)
# -------------------------------------------------------------------------------------------
# StaticCurrent
def _handleStaticCurrent(self):
SCW = SC_writer.SCwriter(self, self.solver)
activeIn = []
for equation in self.solver.Group:
if femutils.is_of_type(equation, "Fem::EquationElmerStaticCurrent"):
if equation.References:
activeIn = equation.References[0][1]
else:
activeIn = self.getAllBodies()
solverSection = SCW.getStaticCurrentSolver(equation)
for body in activeIn:
self._addSolver(body, solverSection)
SCW.handleStaticCurrentBodyForces(activeIn, equation)
if activeIn:
SCW.handleStaticCurrentConstants()
SCW.handleStaticCurrentBndConditions()
SCW.handleStaticCurrentMaterial(activeIn)
# -------------------------------------------------------------------------------------------
# Solver handling

View File

@@ -157,4 +157,14 @@ class Magnetodynamic2DViewProxy(BaseViewProxy):
return ":/icons/FEM_EquationMagnetodynamic2D.svg"
class StaticCurrentProxy(BaseProxy):
pass
class StaticCurrentViewProxy(BaseViewProxy):
def getIcon(self):
return ":/icons/FEM_EquationStaticCurrent.svg"
## @}

View File

@@ -123,6 +123,10 @@ class _TaskPanel(base_femtaskpanel._BaseTaskPanel):
FreeCADGui.ExpressionBinding(self._paramWidget.imagZQSB).bind(
self.obj, "CurrentDensity_im_3"
)
self._paramWidget.normalQSB.setProperty("value", self.obj.NormalCurrentDensity)
FreeCADGui.ExpressionBinding(self._paramWidget.normalQSB).bind(
self.obj, "NormalCurrentDensity"
)
self._paramWidget.reXunspecBox.setChecked(self.obj.CurrentDensity_re_1_Disabled)
self._paramWidget.reYunspecBox.setChecked(self.obj.CurrentDensity_re_2_Disabled)
@@ -176,3 +180,4 @@ class _TaskPanel(base_femtaskpanel._BaseTaskPanel):
self._paramWidget.imZunspecBox, self._paramWidget.imagZQSB
)
)
self.obj.NormalCurrentDensity = self._paramWidget.normalQSB.property("value")