Fem: Add support for Elmer static current solver - fixes #11895
This commit is contained in:
committed by
Max Wilfinger
parent
3f9ad28acf
commit
3d79de4ab3
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
1066
src/Mod/Fem/Gui/Resources/icons/FEM_EquationStaticCurrent.svg
Normal file
1066
src/Mod/Fem/Gui/Resources/icons/FEM_EquationStaticCurrent.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 30 KiB |
@@ -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>
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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"):
|
||||
|
||||
90
src/Mod/Fem/femsolver/elmer/equations/staticcurrent.py
Normal file
90
src/Mod/Fem/femsolver/elmer/equations/staticcurrent.py
Normal 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
|
||||
|
||||
|
||||
## @}
|
||||
111
src/Mod/Fem/femsolver/elmer/equations/staticcurrent_writer.py
Normal file
111
src/Mod/Fem/femsolver/elmer/equations/staticcurrent_writer.py
Normal 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)
|
||||
|
||||
|
||||
## @}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
## @}
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user