Add FEM Constraint Tutorial: Difference between revisions

From FreeCAD Documentation
No edit summary
mNo edit summary
 
(34 intermediate revisions by 10 users not shown)
Line 1: Line 1:
<languages/>
In this tutorial we are going to add the flow velocity constraint to FreeCAD and implement support for elmer solver. Please make sure you have read and understood ([https://www.freecadweb.org/wiki/Extend_FEM_Module Extend FEM Module]) before reading this tutorial.
<translate>


<!--T:1-->
This tutorial only covers how to implement constraints in python. In contrast to solver and equations constraints follow the classic FEM module structure. That is, all modules of a constraint have there place in eather the PyObjects or PyGui package.
{{TutorialInfo
|Topic= Add FEM Constraint
|Level=
|Time=
|Author=[[User:M42kus|M42kus]]
|FCVersion=
|Files=
}}


== Introduction == <!--T:29-->
== Summery ==


<!--T:2-->
# '''Create document object:''' The docuemnt object that resides inside the analysis and though which the constraint can be parametrized and attached to boundaries.
In this tutorial, we are going to add the flow velocity constraint to FreeCAD and implement support for the Elmer solver. Please make sure you have read and understood [[Extend_FEM_Module|Extend FEM Module]] before reading this tutorial.
# '''Create GUI command:''' Add a command to the FEM workbench that adds a flow constriant to the active analysis.
# '''Create a task panel:''' The task panel is nessecary to allow the user to set the boundaries at which he wants to set the velocity constraint. It also makes entering the paramters a little more user friendly.
# '''Extend elmers writer:''' Add support for the new constraint to elmer by extinging its sif file exporter.


<!--T:3-->
== Create Document Object ==
This tutorial only covers how to implement constraints in Python. In contrast to solver and equations, constraints follow the classic FEM module structure. That is, all modules of a constraint have their place in either the {{Incode|femobjects}} or {{Incode|femviewprovider}} package.


== Summary == <!--T:4-->

<!--T:5-->
# '''Create document object:''' The document object that resides inside the analysis and through which the constraint can be parametrized and attached to boundaries.
# '''Create GUI command:''' Add a command to the FEM workbench that adds a flow constraint to the active analysis.
# '''Create a task panel:''' The task panel is necessary to allow the user to set the boundaries at which he wants to set the velocity constraint. It also makes entering the parameters a little more user-friendly.
# '''Extend Elmer's writer:''' Add support for the new constraint to Elmer by extending its sif file exporter.

== Create document object == <!--T:6-->

<!--T:7-->
In this step we are going to modify the following files:
In this step we are going to modify the following files:
</translate>
* src/Mod/Fem/CMakeLists.txt
* src/Mod/Fem/App/CMakeLists.txt
* {{FileName|src/Mod/Fem/CMakeLists.txt}}
* src/Mod/Fem/ObjectsFem.py
* {{FileName|src/Mod/Fem/App/CMakeLists.txt}}
* {{FileName|src/Mod/Fem/ObjectsFem.py}}
and add the following files:
<translate>
* src/Mod/Fem/PyObjects/_FemConstraintFlowVelocity.py
* src/Mod/Fem/PyGui/_ViewProviderFemConstraintFlowVelocity.py


<!--T:8-->
A document proxy and a view proxy are required for the new constraint. Those reside in separate modules. The document proxy in PyObjects and the view proxy in PyGui. Just copy the modules from an existing constraint e.g.
And add the following files:
</translate>
* {{FileName|src/Mod/Fem/femobjects/constraint_flowvelocity.py}}
* {{FileName|src/Mod/Fem/femviewprovider/view_constraint_flowvelocity.py}}
<translate>


<!--T:9-->
* PyObjects/_FemConstraintSelfWeight.py
A document proxy and a view proxy are required for the new constraint. Those reside in separate modules. The document proxy in femobjects and the view proxy in femviewprovider. Just copy the modules from an existing constraint e.g.:
* PyGui/_ViewProviderFemConstraintSelfWeight.py
</translate>
* {{FileName|femobjects/constraint_selfweight.py}}
* {{FileName|femviewprovider/view_constraint_selfweight.py}}
<translate>


<!--T:10-->
Adjust the Type variable and the properties to your needs. The document proxy of the flow constraint looks like the following:
Adjust the Type variable and the properties to your needs. The document proxy of the flow constraint looks like the following:


</translate>
<pre>class Proxy(FemConstraint.Proxy):
{{Code|code=
Type = &quot;Fem::ConstraintFlowVelocity&quot;
class Proxy(FemConstraint.Proxy):
Type = "Fem::ConstraintFlowVelocity"
def __init__(self, obj):
def __init__(self, obj):
super(Proxy, self).__init__(obj)
super(Proxy, self).__init__(obj)
obj.addProperty(
obj.addProperty(
&quot;App::PropertyFloat&quot;, &quot;VelocityX&quot;,
"App::PropertyFloat", "VelocityX",
&quot;Parameter&quot;, &quot;Body heat flux&quot;)
"Parameter", "Body heat flux")
obj.addProperty(
obj.addProperty(
&quot;App::PropertyBool&quot;, &quot;VelocityXEnabled&quot;,
"App::PropertyBool", "VelocityXEnabled",
&quot;Parameter&quot;, &quot;Body heat flux&quot;)
"Parameter", "Body heat flux")
obj.addProperty(
obj.addProperty(
&quot;App::PropertyFloat&quot;, &quot;VelocityY&quot;,
"App::PropertyFloat", "VelocityY",
&quot;Parameter&quot;, &quot;Body heat flux&quot;)
"Parameter", "Body heat flux")
obj.addProperty(
obj.addProperty(
&quot;App::PropertyBool&quot;, &quot;VelocityYEnabled&quot;,
"App::PropertyBool", "VelocityYEnabled",
&quot;Parameter&quot;, &quot;Body heat flux&quot;)
"Parameter", "Body heat flux")
obj.addProperty(
obj.addProperty(
&quot;App::PropertyFloat&quot;, &quot;VelocityZ&quot;,
"App::PropertyFloat", "VelocityZ",
&quot;Parameter&quot;, &quot;Body heat flux&quot;)
"Parameter", "Body heat flux")
obj.addProperty(
obj.addProperty(
&quot;App::PropertyBool&quot;, &quot;VelocityZEnabled&quot;,
"App::PropertyBool", "VelocityZEnabled",
&quot;Parameter&quot;, &quot;Body heat flux&quot;)
"Parameter", "Body heat flux")
obj.addProperty(
obj.addProperty(
&quot;App::PropertyBool&quot;, &quot;NormalToBoundary&quot;,
"App::PropertyBool", "NormalToBoundary",
&quot;Parameter&quot;, &quot;Body heat flux&quot;)</pre>
"Parameter", "Body heat flux")
}}
<translate>

<!--T:11-->
The module containing the view proxy might look a little more complicated. But for now just adjust the icon path. We are going to come back to this file in later steps of the tutorial.
The module containing the view proxy might look a little more complicated. But for now just adjust the icon path. We are going to come back to this file in later steps of the tutorial.


</translate>
<pre>class ViewProxy(FemConstraint.ViewProxy):
{{Code|code=
class ViewProxy(FemConstraint.ViewProxy):
def getIcon(self):
def getIcon(self):
return &quot;:/icons/fem-constraint-flow-velocity.svg&quot;</pre>
return ":/icons/fem-constraint-flow-velocity.svg"
}}
Add the two new modules to the build system like descripted in ([https://www.freecadweb.org/wiki/Extend_FEM_Module Extend FEM Module]). Locate the correct list be searching for constraint modules.
<translate>


<!--T:12-->
As all objects of the FEM workbench the velocity constraint must be registerd in <code>ObjectsFem.py</code>. The following method adds a velocity constraint to the active document. This method will be used by the GUI command to add the constraint. It must be inserted somewhere in <code>ObjectsFem.py</code>.
Add the two new modules to the build system as described in [https://www.freecadweb.org/wiki/Extend_FEM_Module Extend FEM Module]. Locate the correct list by searching for constraint modules.


<!--T:13-->
<pre>def makeConstraintFlowVelocity(name=&quot;FlowVelocity&quot;):
As all objects of the FEM workbench, the velocity constraint must be registered in {{Incode|ObjectsFem.py}}. The following method adds a velocity constraint to the active document. This method will be used by the GUI command to add the constraint. It must be inserted somewhere in {{Incode|ObjectsFem.py}}.
obj = FreeCAD.ActiveDocument.addObject(&quot;Fem::ConstraintPython&quot;, name)

import PyObjects._FemConstraintFlowVelocity
</translate>
PyObjects._FemConstraintFlowVelocity.Proxy(obj)
{{Code|code=
def makeConstraintFlowVelocity(name="FlowVelocity"):
obj = FreeCAD.ActiveDocument.addObject("Fem::ConstraintPython", name)
import femobjects.constraint_flowvelocity
femobjects.constraint_flowvelocity.Proxy(obj)
if FreeCAD.GuiUp:
if FreeCAD.GuiUp:
import PyGui._ViewProviderFemConstraintFlowVelocity
import femviewprovider.view_constraint_flowvelocity
PyGui._ViewProviderFemConstraintFlowVelocity.ViewProxy(obj.ViewObject)
femviewprovider.view_constraint_flowvelocity.ViewProxy(obj.ViewObject)
return obj</pre>
return obj
}}
== Create GUI command ==
<translate>


== Create GUI command == <!--T:14-->

<!--T:15-->
In this step we are going to modify the following files:
In this step we are going to modify the following files:
</translate>
* src/Mod/Fem/CMakeLists.txt
* src/Mod/Fem/App/CMakeLists.txt
* {{FileName|src/Mod/Fem/CMakeLists.txt}}
* src/Mod/Fem/Gui/Workbench.cpp
* {{FileName|src/Mod/Fem/App/CMakeLists.txt}}
* {{FileName|src/Mod/Fem/Gui/Workbench.cpp}}
and add the following new file:
<translate>
* src/Mod/Fem/PyGui/_CommandFemConstraintFlowVelocity.py

<!--T:16-->
And add the following new file:
</translate>
* {{FileName|src/Mod/Fem/femobjects/constraint_flowvelocity.py}}
<translate>


<!--T:17-->
The command allows the user to actually add the constraint to the active analysis. Just copy a command from an existing constraint. Commands reside in the <code>PyGui</code> package. Adjust the resources attribute and the make method called in Activated to your needs. Also use a different command id in the addCommand call on the bottom of the module. The following class is the command class of the velocity constraint.
The command allows the user to actually add the constraint to the active analysis. Just copy a command from an existing constraint. Commands reside in the {{Incode|femviewprovider}} package. Adjust the resources attribute and the make method called in Activated to your needs. Also use a different command id in the addCommand call on the bottom of the module. The following class is the command class of the velocity constraint.


</translate>
<pre>class Command(FemCommands.FemCommands):
{{Code|code=
class Command(FemCommands.FemCommands):


def __init__(self):
def __init__(self):
Line 87: Line 143:
'Pixmap': 'fem-constraint-flow-velocity',
'Pixmap': 'fem-constraint-flow-velocity',
'MenuText': QtCore.QT_TRANSLATE_NOOP(
'MenuText': QtCore.QT_TRANSLATE_NOOP(
&quot;FEM_ConstraintFlowVelocity&quot;,
"FEM_ConstraintFlowVelocity",
&quot;Constraint Velocity&quot;),
"Constraint Velocity"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP(
'ToolTip': QtCore.QT_TRANSLATE_NOOP(
&quot;FEM_ConstraintFlowVelocity&quot;,
"FEM_ConstraintFlowVelocity",
&quot;Creates a FEM constraint body heat flux&quot;)}
"Creates a FEM constraint body heat flux")}
self.is_active = 'with_analysis'
self.is_active = 'with_analysis'


def Activated(self):
def Activated(self):
App.ActiveDocument.openTransaction(
App.ActiveDocument.openTransaction(
&quot;Create FemConstraintFlowVelocity&quot;)
"Create FemConstraintFlowVelocity")
Gui.addModule(&quot;ObjectsFem&quot;)
Gui.addModule("ObjectsFem")
Gui.doCommand(
Gui.doCommand(
&quot;FemGui.getActiveAnalysis().Member += &quot;
"FemGui.getActiveAnalysis().Member += "
&quot;[ObjectsFem.makeConstraintFlowVelocity()]&quot;)
"[ObjectsFem.makeConstraintFlowVelocity()]")


Gui.addCommand('FEM_AddConstraintFlowVelocity', Command())</pre>
Gui.addCommand('FEM_AddConstraintFlowVelocity', Command())
}}
Add the new command file to the build system as decripted in ([https://www.freecadweb.org/wiki/Extend_FEM_Module Extend FEM Module]). Locate the correct list be searching for existing command modules.
<translate>


<!--T:18-->
Add the new command file to the build system as decripted in [https://www.freecadweb.org/wiki/Extend_FEM_Module Extend FEM Module]. Locate the correct list be searching for existing command modules.

<!--T:19-->
Put the command into Gui/Workbench.cpp to add it to the toolbar and menu. Search for an existing constraint of the same category as the new one (e.g. Flow) copy-paste it and adjust the command id. This should be done two times. Once for the menu and again for the toolbar.
Put the command into Gui/Workbench.cpp to add it to the toolbar and menu. Search for an existing constraint of the same category as the new one (e.g. Flow) copy-paste it and adjust the command id. This should be done two times. Once for the menu and again for the toolbar.


== Create a Task Panel ==
== Create a task panel == <!--T:20-->


<!--T:21-->
In this step we are going to modify the following file:
In this step, we are going to modify the following file:
* src/Mod/Fem/PyGui/_ViewProviderFemConstraintFlowVelocity.py
</translate>
* {{FileName|src/Mod/Fem/femviewprovider/view_constraint_flowvelocity.py}}
<translate>


<!--T:22-->
In FreeCAD constraint objects benefit greatly from task panels. Task panels can make use of more powerful input widgets which expose the unit of entered values directely to the user. The velocity constraint even requires the use of a task panel since a task panel is the only way of specifieing the face(s) on which the constraint shall be applied.
In FreeCAD, constraint objects benefit greatly from task panels. Task panels can make use of more powerful input widgets that expose the unit of entered values directly to the user. The velocity constraint even requires the use of a task panel since a task panel is the only way of specifying the face(s) on which the constraint shall be applied.


<!--T:23-->
The location of the module in which task panels are implemented is not strictely defined. For the velocity constraint we are just going to put the task panel in the same module we put the view proxy. The task panel is quite complicated. It makes use of the FemSolectionWidgets.BoundarySelector(). Thats a qt widget which allows the user to select the boundaries on which the constraint shall be applied. In addition to this widget it generates another one by loading a ui file specifically created for the velocity constraint. Via this widget the velocity vector can be specified.
The location of the module in which task panels are implemented is not strictly defined. For the velocity constraint, we are just going to put the task panel in the same module where we put the view proxy. The task panel is quite complicated. It makes use of the FemSolectionWidgets.BoundarySelector(). That's a qt widget that allows the user to select the boundaries on which the constraint shall be applied. In addition to this widget, it generates another one by loading a UI file specifically created for the velocity constraint. Via this widget, the velocity vector can be specified.


<!--T:24-->
Most of the time is should be sufficient to just copy this class, use a suitable ui file (instead of TaskPanelFemFlowVelocity.ui) and adjust _initParamWidget() as well as _applyWidgetChanges(). If the new constraint requires bodies as references instead of boundaries just replace the BoundarySelector object with the SolidSelector.
Most of the time it should be sufficient to just copy this class, use a suitable UI file (instead of TaskPanelFemFlowVelocity.ui) and adjust _initParamWidget() as well as _applyWidgetChanges(). If the new constraint requires bodies as references instead of boundaries just replace the BoundarySelector object with the SolidSelector.


</translate>
<pre>class _TaskPanel(object):
{{Code|code=
class _TaskPanel(object):


def __init__(self, obj):
def __init__(self, obj):
Line 126: Line 195:
self._refWidget.setReferences(obj.References)
self._refWidget.setReferences(obj.References)
self._paramWidget = Gui.PySideUic.loadUi(
self._paramWidget = Gui.PySideUic.loadUi(
App.getHomePath() + &quot;Mod/Fem/PyGui/TaskPanelFemFlowVelocity.ui&quot;)
App.getHomePath() + "Mod/Fem/femviewprovider/TaskPanelFemFlowVelocity.ui")
self._initParamWidget()
self._initParamWidget()
self.form = [self._refWidget, self._paramWidget]
self.form = [self._refWidget, self._paramWidget]
analysis = FemMisc.findAnalysisOfMember(obj)
analysis = FemMisc.findAnalysisOfMember(obj)
self._mesh = FemMisc.getSingleMember(analysis, &quot;Fem::FemMeshObject&quot;)
self._mesh = FemMisc.getSingleMember(analysis, "Fem::FemMeshObject")
self._part = self._mesh.Part if self._mesh is not None else None
self._part = self._mesh.Part if self._mesh is not None else None
self._partVisible = None
self._partVisible = None
Line 166: Line 235:


def _initParamWidget(self):
def _initParamWidget(self):
unit = &quot;m/s&quot;
unit = "m/s"
self._paramWidget.velocityXTxt.setText(
self._paramWidget.velocityXTxt.setText(
str(self._obj.VelocityX) + unit)
str(self._obj.VelocityX) + unit)
Line 183: Line 252:


def _applyWidgetChanges(self):
def _applyWidgetChanges(self):
unit = &quot;m/s&quot;
unit = "m/s"
self._obj.VelocityXEnabled = \
self._obj.VelocityXEnabled = \
not self._paramWidget.velocityXBox.isChecked()
not self._paramWidget.velocityXBox.isChecked()
Line 199: Line 268:
quantity = Units.Quantity(self._paramWidget.velocityZTxt.text())
quantity = Units.Quantity(self._paramWidget.velocityZTxt.text())
self._obj.VelocityZ = float(quantity.getValueAs(unit))
self._obj.VelocityZ = float(quantity.getValueAs(unit))
self._obj.NormalToBoundary = self._paramWidget.normalBox.isChecked()</pre>
self._obj.NormalToBoundary = self._paramWidget.normalBox.isChecked()
}}
The view proxy must be extended to support the task panel we just implemented. The following extended view proxy opens the task panel when the user makes a double click on the constraint object in the tree view.
<translate>


<!--T:25-->
<pre>class ViewProxy(FemConstraint.ViewProxy):
The view proxy must be extended to support the task panel we just implemented. The following extended view proxy opens the task panel when the user makes a double-click on the constraint object in the tree view.

</translate>
{{Code|code=
class ViewProxy(FemConstraint.ViewProxy):


def getIcon(self):
def getIcon(self):
return &quot;:/icons/fem-constraint-flow-velocity.svg&quot;
return ":/icons/fem-constraint-flow-velocity.svg"


def setEdit(self, vobj, mode=0):
def setEdit(self, vobj, mode=0):
Line 218: Line 293:
Gui.Control.closeDialog()
Gui.Control.closeDialog()
Gui.ActiveDocument.setEdit(vobj.Object.Name)
Gui.ActiveDocument.setEdit(vobj.Object.Name)
return True</pre>
return True
}}
== Extend Elmers Writer ==
<translate>

== Extend Elmer's writer == <!--T:26-->


<!--T:27-->
In this step we are going to modify the following file:
In this step we are going to modify the following file:
</translate>
* src/Mod/Fem/FemSolver/Elmer/Writer.py
* {{FileName|src/Mod/Fem/femsolver/elmer/writer.py}}
<translate>


<!--T:28-->
The writer module contains methods for all equation types. Depending on the type of the constriant, boundary condition, initial condition or body force one has to modifiy different methods. For our flow velocity we have to adjust _handleFlowBndConditions(...).
The writer module contains methods for all equation types. Depending on the type of the constraint, boundary condition, initial condition or body force one has to modify different methods. For our flow velocity we have to adjust {{Incode|_handleFlowBndConditions(...)}}.


</translate>
<pre>def _handleFlowBndConditions(self):
{{Code|code=
for obj in self._getMember(&quot;Fem::ConstraintFlowVelocity&quot;):
def _handleFlowBndConditions(self):
for obj in self._getMember("Fem::ConstraintFlowVelocity"):
if obj.References:
if obj.References:
for name in obj.References[0][1]:
for name in obj.References[0][1]:
if obj.VelocityXEnabled:
if obj.VelocityXEnabled:
velocity = getFromUi(obj.VelocityX, &quot;m/s&quot;, &quot;L/T&quot;)
velocity = getFromUi(obj.VelocityX, "m/s", "L/T")
self._boundary(name, &quot;Velocity 1&quot;, velocity)
self._boundary(name, "Velocity 1", velocity)
if obj.VelocityYEnabled:
if obj.VelocityYEnabled:
velocity = getFromUi(obj.VelocityY, &quot;m/s&quot;, &quot;L/T&quot;)
velocity = getFromUi(obj.VelocityY, "m/s", "L/T")
self._boundary(name, &quot;Velocity 2&quot;, velocity)
self._boundary(name, "Velocity 2", velocity)
if obj.VelocityZEnabled:
if obj.VelocityZEnabled:
velocity = getFromUi(obj.VelocityZ, &quot;m/s&quot;, &quot;L/T&quot;)
velocity = getFromUi(obj.VelocityZ, "m/s", "L/T")
self._boundary(name, &quot;Velocity 3&quot;, velocity)
self._boundary(name, "Velocity 3", velocity)
if obj.NormalToBoundary:
if obj.NormalToBoundary:
self._boundary(name, &quot;Normal-Tangential Velocity&quot;, True)
self._boundary(name, "Normal-Tangential Velocity", True)
self._handled(obj)</pre>
self._handled(obj)
}}
<translate>


</translate>
[[Category:FEM{{#translation:}}]]

Latest revision as of 10:04, 13 January 2024

Tutorial
Topic
Add FEM Constraint
Level
Time to complete
Authors
M42kus
FreeCAD version
Example files
See also
None

Introduction

In this tutorial, we are going to add the flow velocity constraint to FreeCAD and implement support for the Elmer solver. Please make sure you have read and understood Extend FEM Module before reading this tutorial.

This tutorial only covers how to implement constraints in Python. In contrast to solver and equations, constraints follow the classic FEM module structure. That is, all modules of a constraint have their place in either the femobjects or femviewprovider package.

Summary

  1. Create document object: The document object that resides inside the analysis and through which the constraint can be parametrized and attached to boundaries.
  2. Create GUI command: Add a command to the FEM workbench that adds a flow constraint to the active analysis.
  3. Create a task panel: The task panel is necessary to allow the user to set the boundaries at which he wants to set the velocity constraint. It also makes entering the parameters a little more user-friendly.
  4. Extend Elmer's writer: Add support for the new constraint to Elmer by extending its sif file exporter.

Create document object

In this step we are going to modify the following files:

  • src/Mod/Fem/CMakeLists.txt
  • src/Mod/Fem/App/CMakeLists.txt
  • src/Mod/Fem/ObjectsFem.py

And add the following files:

  • src/Mod/Fem/femobjects/constraint_flowvelocity.py
  • src/Mod/Fem/femviewprovider/view_constraint_flowvelocity.py

A document proxy and a view proxy are required for the new constraint. Those reside in separate modules. The document proxy in femobjects and the view proxy in femviewprovider. Just copy the modules from an existing constraint e.g.:

  • femobjects/constraint_selfweight.py
  • femviewprovider/view_constraint_selfweight.py

Adjust the Type variable and the properties to your needs. The document proxy of the flow constraint looks like the following:

class Proxy(FemConstraint.Proxy):
    Type = "Fem::ConstraintFlowVelocity"
    def __init__(self, obj):
        super(Proxy, self).__init__(obj)
        obj.addProperty(
            "App::PropertyFloat", "VelocityX",
            "Parameter", "Body heat flux")
        obj.addProperty(
            "App::PropertyBool", "VelocityXEnabled",
            "Parameter", "Body heat flux")
        obj.addProperty(
            "App::PropertyFloat", "VelocityY",
            "Parameter", "Body heat flux")
        obj.addProperty(
            "App::PropertyBool", "VelocityYEnabled",
            "Parameter", "Body heat flux")
        obj.addProperty(
            "App::PropertyFloat", "VelocityZ",
            "Parameter", "Body heat flux")
        obj.addProperty(
            "App::PropertyBool", "VelocityZEnabled",
            "Parameter", "Body heat flux")
        obj.addProperty(
            "App::PropertyBool", "NormalToBoundary",
            "Parameter", "Body heat flux")

The module containing the view proxy might look a little more complicated. But for now just adjust the icon path. We are going to come back to this file in later steps of the tutorial.

class ViewProxy(FemConstraint.ViewProxy):
    def getIcon(self):
        return ":/icons/fem-constraint-flow-velocity.svg"

Add the two new modules to the build system as described in Extend FEM Module. Locate the correct list by searching for constraint modules.

As all objects of the FEM workbench, the velocity constraint must be registered in ObjectsFem.py. The following method adds a velocity constraint to the active document. This method will be used by the GUI command to add the constraint. It must be inserted somewhere in ObjectsFem.py.

def makeConstraintFlowVelocity(name="FlowVelocity"):
    obj = FreeCAD.ActiveDocument.addObject("Fem::ConstraintPython", name)
    import femobjects.constraint_flowvelocity
    femobjects.constraint_flowvelocity.Proxy(obj)
    if FreeCAD.GuiUp:
        import femviewprovider.view_constraint_flowvelocity
        femviewprovider.view_constraint_flowvelocity.ViewProxy(obj.ViewObject)
    return obj

Create GUI command

In this step we are going to modify the following files:

  • src/Mod/Fem/CMakeLists.txt
  • src/Mod/Fem/App/CMakeLists.txt
  • src/Mod/Fem/Gui/Workbench.cpp

And add the following new file:

  • src/Mod/Fem/femobjects/constraint_flowvelocity.py

The command allows the user to actually add the constraint to the active analysis. Just copy a command from an existing constraint. Commands reside in the femviewprovider package. Adjust the resources attribute and the make method called in Activated to your needs. Also use a different command id in the addCommand call on the bottom of the module. The following class is the command class of the velocity constraint.

class Command(FemCommands.FemCommands):

    def __init__(self):
        super(Command, self).__init__()
        self.resources = {
            'Pixmap': 'fem-constraint-flow-velocity',
            'MenuText': QtCore.QT_TRANSLATE_NOOP(
                "FEM_ConstraintFlowVelocity",
                "Constraint Velocity"),
            'ToolTip': QtCore.QT_TRANSLATE_NOOP(
                "FEM_ConstraintFlowVelocity",
                "Creates a FEM constraint body heat flux")}
        self.is_active = 'with_analysis'

    def Activated(self):
        App.ActiveDocument.openTransaction(
            "Create FemConstraintFlowVelocity")
        Gui.addModule("ObjectsFem")
        Gui.doCommand(
            "FemGui.getActiveAnalysis().Member += "
            "[ObjectsFem.makeConstraintFlowVelocity()]")

Gui.addCommand('FEM_AddConstraintFlowVelocity', Command())

Add the new command file to the build system as decripted in Extend FEM Module. Locate the correct list be searching for existing command modules.

Put the command into Gui/Workbench.cpp to add it to the toolbar and menu. Search for an existing constraint of the same category as the new one (e.g. Flow) copy-paste it and adjust the command id. This should be done two times. Once for the menu and again for the toolbar.

Create a task panel

In this step, we are going to modify the following file:

  • src/Mod/Fem/femviewprovider/view_constraint_flowvelocity.py

In FreeCAD, constraint objects benefit greatly from task panels. Task panels can make use of more powerful input widgets that expose the unit of entered values directly to the user. The velocity constraint even requires the use of a task panel since a task panel is the only way of specifying the face(s) on which the constraint shall be applied.

The location of the module in which task panels are implemented is not strictly defined. For the velocity constraint, we are just going to put the task panel in the same module where we put the view proxy. The task panel is quite complicated. It makes use of the FemSolectionWidgets.BoundarySelector(). That's a qt widget that allows the user to select the boundaries on which the constraint shall be applied. In addition to this widget, it generates another one by loading a UI file specifically created for the velocity constraint. Via this widget, the velocity vector can be specified.

Most of the time it should be sufficient to just copy this class, use a suitable UI file (instead of TaskPanelFemFlowVelocity.ui) and adjust _initParamWidget() as well as _applyWidgetChanges(). If the new constraint requires bodies as references instead of boundaries just replace the BoundarySelector object with the SolidSelector.

class _TaskPanel(object):

    def __init__(self, obj):
        self._obj = obj
        self._refWidget = FemSelectionWidgets.BoundarySelector()
        # self._refWidget = FemSelectionWidgets.SolidSelector()
        self._refWidget.setReferences(obj.References)
        self._paramWidget = Gui.PySideUic.loadUi(
            App.getHomePath() + "Mod/Fem/femviewprovider/TaskPanelFemFlowVelocity.ui")
        self._initParamWidget()
        self.form = [self._refWidget, self._paramWidget]
        analysis = FemMisc.findAnalysisOfMember(obj)
        self._mesh = FemMisc.getSingleMember(analysis, "Fem::FemMeshObject")
        self._part = self._mesh.Part if self._mesh is not None else None
        self._partVisible = None
        self._meshVisible = None

    def open(self):
        if self._mesh is not None and self._part is not None:
            self._meshVisible = self._mesh.ViewObject.isVisible()
            self._partVisible = self._part.ViewObject.isVisible()
            self._mesh.ViewObject.hide()
            self._part.ViewObject.show()

    def reject(self):
        self._restoreVisibility()
        return True

    def accept(self):
        if self._obj.References != self._refWidget.references():
            self._obj.References = self._refWidget.references()
        self._applyWidgetChanges()
        self._obj.Document.recompute()
        self._restoreVisibility()
        return True

    def _restoreVisibility(self):
        if self._mesh is not None and self._part is not None:
            if self._meshVisible:
                self._mesh.ViewObject.show()
            else:
                self._mesh.ViewObject.hide()
            if self._partVisible:
                self._part.ViewObject.show()
            else:
                self._part.ViewObject.hide()

    def _initParamWidget(self):
        unit = "m/s"
        self._paramWidget.velocityXTxt.setText(
            str(self._obj.VelocityX) + unit)
        self._paramWidget.velocityYTxt.setText(
            str(self._obj.VelocityY) + unit)
        self._paramWidget.velocityZTxt.setText(
            str(self._obj.VelocityZ) + unit)
        self._paramWidget.velocityXBox.setChecked(
            not self._obj.VelocityXEnabled)
        self._paramWidget.velocityYBox.setChecked(
            not self._obj.VelocityYEnabled)
        self._paramWidget.velocityZBox.setChecked(
            not self._obj.VelocityZEnabled)
        self._paramWidget.normalBox.setChecked(
            self._obj.NormalToBoundary)

    def _applyWidgetChanges(self):
        unit = "m/s"
        self._obj.VelocityXEnabled = \
            not self._paramWidget.velocityXBox.isChecked()
        if self._obj.VelocityXEnabled:
            quantity = Units.Quantity(self._paramWidget.velocityXTxt.text())
            self._obj.VelocityX = float(quantity.getValueAs(unit))
        self._obj.VelocityYEnabled = \
            not self._paramWidget.velocityYBox.isChecked()
        if self._obj.VelocityYEnabled:
            quantity = Units.Quantity(self._paramWidget.velocityYTxt.text())
            self._obj.VelocityY = float(quantity.getValueAs(unit))
        self._obj.VelocityZEnabled = \
            not self._paramWidget.velocityZBox.isChecked()
        if self._obj.VelocityZEnabled:
            quantity = Units.Quantity(self._paramWidget.velocityZTxt.text())
            self._obj.VelocityZ = float(quantity.getValueAs(unit))
        self._obj.NormalToBoundary = self._paramWidget.normalBox.isChecked()

The view proxy must be extended to support the task panel we just implemented. The following extended view proxy opens the task panel when the user makes a double-click on the constraint object in the tree view.

class ViewProxy(FemConstraint.ViewProxy):

    def getIcon(self):
        return ":/icons/fem-constraint-flow-velocity.svg"

    def setEdit(self, vobj, mode=0):
        task = _TaskPanel(vobj.Object)
        Gui.Control.showDialog(task)

    def unsetEdit(self, vobj, mode=0):
        Gui.Control.closeDialog()

    def doubleClicked(self, vobj):
        if Gui.Control.activeDialog():
            Gui.Control.closeDialog()
        Gui.ActiveDocument.setEdit(vobj.Object.Name)
        return True

Extend Elmer's writer

In this step we are going to modify the following file:

  • src/Mod/Fem/femsolver/elmer/writer.py

The writer module contains methods for all equation types. Depending on the type of the constraint, boundary condition, initial condition or body force one has to modify different methods. For our flow velocity we have to adjust _handleFlowBndConditions(...).

def _handleFlowBndConditions(self):
    for obj in self._getMember("Fem::ConstraintFlowVelocity"):
        if obj.References:
            for name in obj.References[0][1]:
                if obj.VelocityXEnabled:
                    velocity = getFromUi(obj.VelocityX, "m/s", "L/T")
                    self._boundary(name, "Velocity 1", velocity)
                if obj.VelocityYEnabled:
                    velocity = getFromUi(obj.VelocityY, "m/s", "L/T")
                    self._boundary(name, "Velocity 2", velocity)
                if obj.VelocityZEnabled:
                    velocity = getFromUi(obj.VelocityZ, "m/s", "L/T")
                    self._boundary(name, "Velocity 3", velocity)
                if obj.NormalToBoundary:
                    self._boundary(name, "Normal-Tangential Velocity", True)
            self._handled(obj)