diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt
index 56fb81cede..a8b9f378fe 100755
--- a/src/Mod/Fem/CMakeLists.txt
+++ b/src/Mod/Fem/CMakeLists.txt
@@ -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
diff --git a/src/Mod/Fem/Gui/Command.cpp b/src/Mod/Fem/Gui/Command.cpp
index 06fd5222a0..9032aa7b68 100644
--- a/src/Mod/Fem/Gui/Command.cpp
+++ b/src/Mod/Fem/Gui/Command.cpp
@@ -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()
diff --git a/src/Mod/Fem/Gui/Resources/Fem.qrc b/src/Mod/Fem/Gui/Resources/Fem.qrc
index db84762ef8..6342a0f9e5 100755
--- a/src/Mod/Fem/Gui/Resources/Fem.qrc
+++ b/src/Mod/Fem/Gui/Resources/Fem.qrc
@@ -51,6 +51,7 @@
icons/FEM_EquationHeat.svg
icons/FEM_EquationMagnetodynamic.svg
icons/FEM_EquationMagnetodynamic2D.svg
+ icons/FEM_EquationStaticCurrent.svg
icons/FEM_CreateElementsSet.svg
diff --git a/src/Mod/Fem/Gui/Resources/icons/FEM_EquationStaticCurrent.svg b/src/Mod/Fem/Gui/Resources/icons/FEM_EquationStaticCurrent.svg
new file mode 100644
index 0000000000..5a2ce5236d
--- /dev/null
+++ b/src/Mod/Fem/Gui/Resources/icons/FEM_EquationStaticCurrent.svg
@@ -0,0 +1,1066 @@
+
+
diff --git a/src/Mod/Fem/Gui/Resources/ui/CurrentDensity.ui b/src/Mod/Fem/Gui/Resources/ui/CurrentDensity.ui
index 3047b66c32..15d61e8109 100644
--- a/src/Mod/Fem/Gui/Resources/ui/CurrentDensity.ui
+++ b/src/Mod/Fem/Gui/Resources/ui/CurrentDensity.ui
@@ -345,6 +345,41 @@ Note: for 2D only setting for x is possible,
+ -
+
+
+ Normal:
+
+
+
+ -
+
+
+ true
+
+
+ A/m^2
+
+
+ Qt::AlignLeft|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ true
+
+
+ -1000000000000000000000.000000000000000
+
+
+ 1000000000000000000000.000000000000000
+
+
+ 1.000000000000000
+
+
+ 0.000000000000
+
+
+
diff --git a/src/Mod/Fem/ObjectsFem.py b/src/Mod/Fem/ObjectsFem.py
index ad8963d760..d5bc8838f5 100644
--- a/src/Mod/Fem/ObjectsFem.py
+++ b/src/Mod/Fem/ObjectsFem.py
@@ -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"""
diff --git a/src/Mod/Fem/femcommands/commands.py b/src/Mod/Fem/femcommands/commands.py
index c7ad19a824..69bed9352f 100644
--- a/src/Mod/Fem/femcommands/commands.py
+++ b/src/Mod/Fem/femcommands/commands.py
@@ -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())
diff --git a/src/Mod/Fem/femobjects/constraint_currentdensity.py b/src/Mod/Fem/femobjects/constraint_currentdensity.py
index 1c3f6499ff..3fa5781060 100644
--- a/src/Mod/Fem/femobjects/constraint_currentdensity.py
+++ b/src/Mod/Fem/femobjects/constraint_currentdensity.py
@@ -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"):
diff --git a/src/Mod/Fem/femsolver/elmer/equations/staticcurrent.py b/src/Mod/Fem/femsolver/elmer/equations/staticcurrent.py
new file mode 100644
index 0000000000..79b9393c10
--- /dev/null
+++ b/src/Mod/Fem/femsolver/elmer/equations/staticcurrent.py
@@ -0,0 +1,90 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+# ***************************************************************************
+# * Copyright (c) 2025 Mario Passaglia *
+# * *
+# * 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 *
+# * . *
+# * *
+# ***************************************************************************
+
+__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
+
+
+## @}
diff --git a/src/Mod/Fem/femsolver/elmer/equations/staticcurrent_writer.py b/src/Mod/Fem/femsolver/elmer/equations/staticcurrent_writer.py
new file mode 100644
index 0000000000..af12735a12
--- /dev/null
+++ b/src/Mod/Fem/femsolver/elmer/equations/staticcurrent_writer.py
@@ -0,0 +1,111 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+# ***************************************************************************
+# * Copyright (c) 2025 Mario Passaglia *
+# * *
+# * 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 *
+# * . *
+# * *
+# ***************************************************************************
+
+__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)
+
+
+## @}
diff --git a/src/Mod/Fem/femsolver/elmer/writer.py b/src/Mod/Fem/femsolver/elmer/writer.py
index c08f701e35..6128280a05 100644
--- a/src/Mod/Fem/femsolver/elmer/writer.py
+++ b/src/Mod/Fem/femsolver/elmer/writer.py
@@ -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
diff --git a/src/Mod/Fem/femsolver/equationbase.py b/src/Mod/Fem/femsolver/equationbase.py
index 478a904708..6d405b2908 100644
--- a/src/Mod/Fem/femsolver/equationbase.py
+++ b/src/Mod/Fem/femsolver/equationbase.py
@@ -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"
+
+
## @}
diff --git a/src/Mod/Fem/femtaskpanels/task_constraint_currentdensity.py b/src/Mod/Fem/femtaskpanels/task_constraint_currentdensity.py
index 8145263ebb..113b25680f 100644
--- a/src/Mod/Fem/femtaskpanels/task_constraint_currentdensity.py
+++ b/src/Mod/Fem/femtaskpanels/task_constraint_currentdensity.py
@@ -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")