From 3d79de4ab38987c82c4bc3d073b3d144a80319d2 Mon Sep 17 00:00:00 2001 From: marioalexis Date: Mon, 24 Feb 2025 17:39:24 -0300 Subject: [PATCH] Fem: Add support for Elmer static current solver - fixes #11895 --- src/Mod/Fem/CMakeLists.txt | 2 + src/Mod/Fem/Gui/Command.cpp | 16 + src/Mod/Fem/Gui/Resources/Fem.qrc | 1 + .../icons/FEM_EquationStaticCurrent.svg | 1066 +++++++++++++++++ .../Fem/Gui/Resources/ui/CurrentDensity.ui | 35 + src/Mod/Fem/ObjectsFem.py | 11 + src/Mod/Fem/femcommands/commands.py | 14 + .../femobjects/constraint_currentdensity.py | 9 + .../elmer/equations/staticcurrent.py | 90 ++ .../elmer/equations/staticcurrent_writer.py | 111 ++ src/Mod/Fem/femsolver/elmer/writer.py | 24 + src/Mod/Fem/femsolver/equationbase.py | 10 + .../task_constraint_currentdensity.py | 5 + 13 files changed, 1394 insertions(+) create mode 100644 src/Mod/Fem/Gui/Resources/icons/FEM_EquationStaticCurrent.svg create mode 100644 src/Mod/Fem/femsolver/elmer/equations/staticcurrent.py create mode 100644 src/Mod/Fem/femsolver/elmer/equations/staticcurrent_writer.py 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 @@ + + + + + + + image/svg+xml + + + + [vdwalts] + + + 2016-08-01 + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/ + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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")