Add FEM Constraint Tutorial/fr

Dans ce tutoriel, nous allons ajouter la contrainte de vitesse d'écoulement à FreeCAD et implémenter le support pour le solveur elmer. Assurez-vous d'avoir lu et compris Module d'extension FEM avant de lire ce tutoriel.

Ce tutoriel explique uniquement comment implémenter des contraintes en Python. Contrairement au solveur et aux équations, les contraintes suivent la structure de module FEM classique. C'est-à-dire que tous les modules d'une contrainte ont leur place dans soit le paquet PyObjects soit PyGui.

Résumé

 * 1) Créer un objet de document: l'objet du document qui est dans l'analyse et où la contrainte peut être paramétrée et liée.
 * 2) Créer une commande graphique: ajoute une commande à l'atelier FEM lequel ajoute une contrainte de flux à l'analyse active.
 * 3) Créer un panneau de tâches: le panneau de tâches est nécessaire pour permettre à l'utilisateur de définir les limites auxquelles il souhaite définir la contrainte de vélocité. Cela rend également la saisie des paramètres un peu plus conviviale.
 * 4) Extension d'elmers writer: ajoute le support pour la nouvelle contrainte à elmer en développant son exportateur de fichier sif.

Créer un objet de document
Dans cette étape, nous allons modifier les fichiers suivants: et ajouter les fichiers suivants:
 * src/Mod/Fem/CMakeLists.txt
 * src/Mod/Fem/App/CMakeLists.txt
 * src/Mod/Fem/ObjectsFem.py
 * src/Mod/Fem/PyObjects/_FemConstraintFlowVelocity.py
 * src/Mod/Fem/PyGui/_ViewProviderFemConstraintFlowVelocity.py

Un document proxy et une vue proxy sont nécessaires pour la nouvelle contrainte. Ceux-ci sont dans des modules séparés. Le document proxy est dans PyObjects et l a vue proxy dans PyGui. Copiez simplement les modules à partir d'une contrainte existante, par exemple: 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 = &quot;Fem::ConstraintFlowVelocity&quot; def __init__(self, obj): super(Proxy, self).__init__(obj) obj.addProperty(           &quot;App::PropertyFloat&quot;, &quot;VelocityX&quot;,            &quot;Parameter&quot;, &quot;Body heat flux&quot;) obj.addProperty(           &quot;App::PropertyBool&quot;, &quot;VelocityXEnabled&quot;,            &quot;Parameter&quot;, &quot;Body heat flux&quot;) obj.addProperty(           &quot;App::PropertyFloat&quot;, &quot;VelocityY&quot;,            &quot;Parameter&quot;, &quot;Body heat flux&quot;) obj.addProperty(           &quot;App::PropertyBool&quot;, &quot;VelocityYEnabled&quot;,            &quot;Parameter&quot;, &quot;Body heat flux&quot;) obj.addProperty(           &quot;App::PropertyFloat&quot;, &quot;VelocityZ&quot;,            &quot;Parameter&quot;, &quot;Body heat flux&quot;) obj.addProperty(           &quot;App::PropertyBool&quot;, &quot;VelocityZEnabled&quot;,            &quot;Parameter&quot;, &quot;Body heat flux&quot;) obj.addProperty(           &quot;App::PropertyBool&quot;, &quot;NormalToBoundary&quot;,            &quot;Parameter&quot;, &quot;Body heat flux&quot;) 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 &quot;:/icons/fem-constraint-flow-velocity.svg&quot; Add the two new modules to the build system like descripted in Extend FEM Module. Locate the correct list be searching for constraint modules.
 * PyObjects/_FemConstraintSelfWeight.py
 * PyGui/_ViewProviderFemConstraintSelfWeight.py

As all objects of the FEM workbench the velocity constraint must be registerd in. 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. def makeConstraintFlowVelocity(name=&quot;FlowVelocity&quot;): obj = FreeCAD.ActiveDocument.addObject(&quot;Fem::ConstraintPython&quot;, name) import PyObjects._FemConstraintFlowVelocity PyObjects._FemConstraintFlowVelocity.Proxy(obj) if FreeCAD.GuiUp: import PyGui._ViewProviderFemConstraintFlowVelocity PyGui._ViewProviderFemConstraintFlowVelocity.ViewProxy(obj.ViewObject) return obj

Create GUI command
In this step we are going to modify the following files: and add the following new file:
 * src/Mod/Fem/CMakeLists.txt
 * src/Mod/Fem/App/CMakeLists.txt
 * src/Mod/Fem/Gui/Workbench.cpp
 * src/Mod/Fem/PyGui/_CommandFemConstraintFlowVelocity.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  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(               &quot;FEM_ConstraintFlowVelocity&quot;,                &quot;Constraint Velocity&quot;), 'ToolTip': QtCore.QT_TRANSLATE_NOOP(               &quot;FEM_ConstraintFlowVelocity&quot;,                &quot;Creates a FEM constraint body heat flux&quot;)} self.is_active = 'with_analysis'

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

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: 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.
 * src/Mod/Fem/PyGui/_ViewProviderFemConstraintFlowVelocity.py

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.

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. 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 + &quot;Mod/Fem/PyGui/TaskPanelFemFlowVelocity.ui&quot;) self._initParamWidget self.form = [self._refWidget, self._paramWidget] analysis = FemMisc.findAnalysisOfMember(obj) self._mesh = FemMisc.getSingleMember(analysis, &quot;Fem::FemMeshObject&quot;) 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 = &quot;m/s&quot; 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 = &quot;m/s&quot; 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 &quot;:/icons/fem-constraint-flow-velocity.svg&quot;

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 Elmers Writer
In this step we are going to modify the following file: 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(...). def _handleFlowBndConditions(self): for obj in self._getMember(&quot;Fem::ConstraintFlowVelocity&quot;): if obj.References: for name in obj.References[0][1]: if obj.VelocityXEnabled: velocity = getFromUi(obj.VelocityX, &quot;m/s&quot;, &quot;L/T&quot;) self._boundary(name, &quot;Velocity 1&quot;, velocity) if obj.VelocityYEnabled: velocity = getFromUi(obj.VelocityY, &quot;m/s&quot;, &quot;L/T&quot;) self._boundary(name, &quot;Velocity 2&quot;, velocity) if obj.VelocityZEnabled: velocity = getFromUi(obj.VelocityZ, &quot;m/s&quot;, &quot;L/T&quot;) self._boundary(name, &quot;Velocity 3&quot;, velocity) if obj.NormalToBoundary: self._boundary(name, &quot;Normal-Tangential Velocity&quot;, True) self._handled(obj)
 * src/Mod/Fem/FemSolver/Elmer/Writer.py