Core: Generation of FEM interface bindings (#22581)

* Initial commit of FEM bindings.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* running black.

* Fixing imports.

* Update src/Mod/Fem/Gui/ViewProviderFemMeshPy.pyi

Co-authored-by: João Matos <joao@tritao.eu>

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: João Matos <joao@tritao.eu>
This commit is contained in:
Ian Abreu
2025-08-04 11:48:00 -04:00
committed by GitHub
parent 5eb4dffe50
commit 4aa0aff389
11 changed files with 674 additions and 0 deletions

View File

@@ -76,6 +76,12 @@ if(BUILD_FEM_VTK)
generate_from_xml(FemPostPipelinePy)
generate_from_xml(FemPostFilterPy)
generate_from_xml(FemPostBranchFilterPy)
generate_from_py_(FemPostObjectPy)
generate_from_py_(FemPostPipelinePy)
generate_from_py_(FemPostFilterPy)
generate_from_py_(FemPostBranchFilterPy)
endif(BUILD_FEM_VTK)
SOURCE_GROUP("Python" FILES ${Python_SRCS})

View File

@@ -0,0 +1,312 @@
from typing import Any, Final
from Base.Metadata import constmethod, export
from App.ComplexGeoData import ComplexGeoData
@export(
Father="ComplexGeoDataPy",
Name="FemMeshPy",
Twin="FemMesh",
TwinPointer="FemMesh",
Include="Mod/Fem/App/FemMesh.h",
Namespace="Fem",
FatherInclude="App/ComplexGeoDataPy.h",
FatherNamespace="Data",
)
class FemMeshPy(ComplexGeoData):
"""
FemMesh class
"""
@staticmethod
def PyMake(args: list, kwd: dict) -> Any: ...
def __init__(self, *args, **kwargs) -> None: ...
def setShape(self) -> Any:
"""Set the Part shape to mesh"""
...
def compute(self) -> Any:
"""Update the internal mesh structure"""
...
def addHypothesis(self) -> Any:
"""Add hypothesis"""
...
def setStandardHypotheses(self) -> Any:
"""Set some standard hypotheses for the whole shape"""
...
def addNode(self) -> Any:
"""Add a node by setting (x,y,z)."""
...
def addEdge(self) -> Any:
"""Add an edge by setting two node indices."""
...
def addEdgeList(self) -> Any:
"""Add list of edges by list of node indices and list of nodes per edge."""
...
def addFace(self) -> Any:
"""Add a face by setting three node indices."""
...
def addFaceList(self) -> Any:
"""Add list of faces by list of node indices and list of nodes per face."""
...
def addQuad(self) -> Any:
"""Add a quad by setting four node indices."""
...
def addVolume(self) -> Any:
"""Add a volume by setting an arbitrary number of node indices."""
...
def addVolumeList(self) -> Any:
"""Add list of volumes by list of node indices and list of nodes per volume."""
...
def read(self) -> Any:
"""Read in a various FEM mesh file formats.
read(file.endingToExportTo)
supported formats: DAT, INP, MED, STL, UNV, VTK, Z88"""
...
@constmethod
def write(self) -> Any:
"""Write out various FEM mesh file formats.
write(file.endingToExportTo)
supported formats: BDF, DAT, INP, MED, STL, UNV, VTK, Z88"""
...
@constmethod
def writeABAQUS(self) -> Any:
"""Write out as ABAQUS inp
writeABAQUS(file, int elemParam, bool groupParam, str volVariant, str faceVariant, str edgeVariant)
elemParam:
0: All elements
1: Highest elements only
2: FEM elements only (only edges not belonging to faces and faces not belonging to volumes)
groupParam:
True: Write group data
False: Do not write group data
volVariant: Volume elements
"standard": Tetra4 -> C3D4, Penta6 -> C3D6, Hexa8 -> C3D8, Tetra10 -> C3D10, Penta15 -> C3D15, Hexa20 -> C3D20
"reduced": Hexa8 -> C3D8R, Hexa20 -> C3D20R
"incompatible": Hexa8 -> C3D8I
"modified": Tetra10 -> C3D10T
"fluid": Tetra4 -> F3D4, Penta6 -> F3D6, Hexa8 -> F3D8
faceVariant: Face elements
"shell": Tria3 -> S3, Quad4 -> S4, Tria6 -> S6, Quad8 -> S8
"shell reduced": Tria3 -> S3, Quad4 -> S4R, Tria6 -> S6, Quad8 -> S8R
"membrane": Tria3 -> M3D3, Quad4 -> M3D4, Tria6 -> M3D6, Quad8 -> M3D8
"membrane reduced": Tria3 -> M3D3, Quad4 -> M3D4R, Tria6 -> M3D6, Quad8 -> M3D8R
"stress": Tria3 -> CPS3, Quad4 -> CPS4, Tria6 -> CPS6, Quad8 -> CPS8
"stress reduced": Tria3 -> CPS3, Quad4 -> CPS4R, Tria6 -> CPS6, Quad8 -> CPS8R
"strain": Tria3 -> CPE3, Quad4 -> CPE4, Tria6 -> CPE6, Quad8 -> CPE8
"strain reduced": Tria3 -> CPE3, Quad4 -> CPE4R, Tria6 -> CPE6, Quad8 -> CPE8R
"axisymmetric": Tria3 -> CAX3, Quad4 -> CAX4, Tria6 -> CAX6, Quad8 -> CAX8
"axisymmetric reduced": Tria3 -> CAX3, Quad4 -> CAX4R, Tria6 -> CAX6, Quad8 -> CAX8R
edgeVariant: Edge elements
"beam": Seg2 -> B31, Seg3 -> B32
"beam reduced": Seg2 -> B31R, Seg3 -> B32R
"truss": Seg2 -> T3D2, eg3 -> T3D3
"network": Seg3 -> D
Elements are selected according to CalculiX availability.
For example if volume variant "modified" is selected, Tetra10 mesh
elements are assigned to C3D10T and remain elements uses "standard".
Axisymmetric, plane strain and plane stress elements expect nodes in the plane z=0.
"""
...
def setTransform(self) -> Any:
"""Use a Placement object to perform a translation or rotation"""
...
@constmethod
def copy(self) -> Any:
"""Make a copy of this FEM mesh."""
...
@constmethod
def getFacesByFace(self) -> Any:
"""Return a list of face IDs which belong to a TopoFace"""
...
@constmethod
def getEdgesByEdge(self) -> Any:
"""Return a list of edge IDs which belong to a TopoEdge"""
...
@constmethod
def getVolumesByFace(self) -> Any:
"""Return a dict of volume IDs and face IDs which belong to a TopoFace"""
...
@constmethod
def getccxVolumesByFace(self) -> Any:
"""Return a dict of volume IDs and ccx face numbers which belong to a TopoFace"""
...
@constmethod
def getNodeById(self) -> Any:
"""Get the node position vector by a Node-ID"""
...
@constmethod
def getNodesBySolid(self) -> Any:
"""Return a list of node IDs which belong to a TopoSolid"""
...
@constmethod
def getNodesByFace(self) -> Any:
"""Return a list of node IDs which belong to a TopoFace"""
...
@constmethod
def getNodesByEdge(self) -> Any:
"""Return a list of node IDs which belong to a TopoEdge"""
...
@constmethod
def getNodesByVertex(self) -> Any:
"""Return a list of node IDs which belong to a TopoVertex"""
...
@constmethod
def getElementNodes(self) -> Any:
"""Return a tuple of node IDs to a given element ID"""
...
@constmethod
def getNodeElements(self) -> Any:
"""Return a tuple of specific element IDs associated to a given node ID"""
...
@constmethod
def getGroupName(self) -> Any:
"""Return a string of group name to a given group ID"""
...
@constmethod
def getGroupElementType(self) -> Any:
"""Return a string of group element type to a given group ID"""
...
@constmethod
def getGroupElements(self) -> Any:
"""Return a tuple of ElementIDs to a given group ID"""
...
@constmethod
def addGroup(self) -> Any:
"""Add a group to mesh with specific name and type
addGroup(name, typestring, [id])
name: string
typestring: "All", "Node", "Edge", "Face", "Volume", "0DElement", "Ball"
id: int
Optional id is used to force specific id for group, but does
not work, yet."""
...
@constmethod
def addGroupElements(self) -> Any:
"""Add a tuple of ElementIDs to a given group ID
addGroupElements(groupid, list_of_elements)
groupid: int
list_of_elements: list of int
Notice that the elements have to be in the mesh."""
...
@constmethod
def removeGroup(self) -> Any:
"""Remove a group with a given group ID
removeGroup(groupid)
groupid: int
Returns boolean."""
...
@constmethod
def getElementType(self) -> Any:
"""Return the element type of a given ID"""
...
@constmethod
def getIdByElementType(self) -> Any:
"""Return a tuple of IDs to a given element type"""
...
Nodes: Final[Any]
"""Dictionary of Nodes by ID (int ID:Vector())"""
NodeCount: Final[Any]
"""Number of nodes in the Mesh."""
Edges: Final[Any]
"""Tuple of edge IDs"""
EdgesOnly: Final[Any]
"""Tuple of edge IDs which does not belong to any face (and thus not belong to any volume too)"""
EdgeCount: Final[Any]
"""Number of edges in the Mesh."""
Faces: Final[Any]
"""Tuple of face IDs"""
FacesOnly: Final[Any]
"""Tuple of face IDs which does not belong to any volume"""
FaceCount: Final[Any]
"""Number of Faces in the Mesh."""
TriangleCount: Final[Any]
"""Number of Triangles in the Mesh."""
QuadrangleCount: Final[Any]
"""Number of Quadrangles in the Mesh."""
PolygonCount: Final[Any]
"""Number of Quadrangles in the Mesh."""
Volumes: Final[Any]
"""Tuple of volume IDs"""
VolumeCount: Final[Any]
"""Number of Volumes in the Mesh."""
TetraCount: Final[Any]
"""Number of Tetras in the Mesh."""
HexaCount: Final[Any]
"""Number of Hexas in the Mesh."""
PyramidCount: Final[Any]
"""Number of Pyramids in the Mesh."""
PrismCount: Final[Any]
"""Number of Prisms in the Mesh."""
PolyhedronCount: Final[Any]
"""Number of Polyhedrons in the Mesh."""
SubMeshCount: Final[Any]
"""Number of SubMeshs in the Mesh."""
GroupCount: Final[Any]
"""Number of Groups in the Mesh."""
Groups: Final[Any]
"""Tuple of Group IDs."""
Volume: Final[Any]
"""Volume of the mesh."""

View File

@@ -0,0 +1,36 @@
from typing import Any
from Base.Metadata import export
from Fem.FemPostObject import FemPostObject
@export(
Father="FemPostObjectPy",
Name="FemPostBranchFilterPy",
Twin="FemPostBranchFilter",
TwinPointer="FemPostBranchFilter",
Include="Mod/Fem/App/FemPostBranchFilter.h",
Namespace="Fem",
FatherInclude="Mod/Fem/App/FemPostObjectPy.h",
FatherNamespace="Fem",
)
class FemPostBranchFilterPy(FemPostObject):
"""
The FemPostBranch class.
"""
def getFilter(self) -> Any:
"""Returns all filters, that this pipeline uses (non recursive, result does not contain branch child filters)"""
...
def recomputeChildren(self) -> Any:
"""Recomputes all children of the pipeline"""
...
def getLastPostObject(self) -> Any:
"""Get the last post-processing object"""
...
def holdsPostObject(self) -> Any:
"""Check if this pipeline holds a given post-processing object"""
...

View File

@@ -0,0 +1,54 @@
from typing import Any
from Base.Metadata import export
from Fem.FemPostObject import FemPostObject
@export(
Father="FemPostObjectPy",
Name="FemPostFilterPy",
Twin="FemPostFilter",
TwinPointer="FemPostFilter",
Include="Mod/Fem/App/FemPostFilter.h",
Namespace="Fem",
FatherInclude="Mod/Fem/App/FemPostObjectPy.h",
FatherNamespace="Fem",
)
class FemPostFilterPy(FemPostObject):
"""
The FemPostFilter class.
"""
def addFilterPipeline(self) -> Any:
"""Registers a new vtk filter pipeline for data processing. Arguments are (name, source algorithm, target algorithm)."""
...
def setActiveFilterPipeline(self) -> Any:
"""Sets the filter pipeline that shall be used for data processing. Argument is the name of the filter pipeline to activate."""
...
def getParentPostGroup(self) -> Any:
"""Returns the postprocessing group the filter is in (e.g. a pipeline or branch object). None is returned if not in any."""
...
def getInputData(self) -> Any:
"""Returns the dataset available at the filter's input.
Note: Can lead to a full recompute of the whole pipeline, hence best to call this only in "execute", where the user expects long calculation cycles.
"""
...
def getInputVectorFields(self) -> Any:
"""Returns the names of all vector fields available on this filter's input.
Note: Can lead to a full recompute of the whole pipeline, hence best to call this only in "execute", where the user expects long calculation cycles.
"""
...
def getInputScalarFields(self) -> Any:
"""Returns the names of all scalar fields available on this filter's input.
Note: Can lead to a full recompute of the whole pipeline, hence best to call this only in "execute", where the user expects long calculation cycles.
"""
...
def getOutputAlgorithm(self) -> Any:
"""Returns the filters vtk algorithm currently used as output (the one generating the Data field). Note that the output algorithm may change depending on filter settings."""
...

View File

@@ -0,0 +1,36 @@
from typing import Any
from Base.Metadata import export
from App.GeoFeature import GeoFeature
@export(
Father="GeoFeaturePy",
Name="FemPostObjectPy",
Twin="FemPostObject",
TwinPointer="FemPostObject",
Include="Mod/Fem/App/FemPostObject.h",
Namespace="Fem",
FatherInclude="App/GeoFeaturePy.h",
FatherNamespace="App",
)
class FemPostObjectPy(GeoFeature):
"""
The FemPostObject class.
"""
def writeVTK(self) -> Any:
"""writeVTK(filename) -> None
Write data object to VTK file.
filename: str
File extension is automatically detected from data type."""
...
def getDataSet(self) -> Any:
"""getDataset() -> vtkDataSet
Returns the current output dataset. For normal filters this is equal to the objects Data property output. However, a pipelines Data property could store multiple frames, and hence Data can be of type vtkCompositeData, which is not a vtkDataset. To simplify implementations this function always returns a vtkDataSet, and for a pipeline it will be the dataset of the currently selected frame. Note that the returned value could be None, if no data is set at all.
"""
...

View File

@@ -0,0 +1,70 @@
from typing import Any
from Base.Metadata import export
from Fem.FemPostObject import FemPostObject
@export(
Father="FemPostObjectPy",
Name="FemPostPipelinePy",
Twin="FemPostPipeline",
TwinPointer="FemPostPipeline",
Include="Mod/Fem/App/FemPostPipeline.h",
Namespace="Fem",
FatherInclude="Mod/Fem/App/FemPostObjectPy.h",
FatherNamespace="Fem",
)
class FemPostPipelinePy(FemPostObject):
"""
The FemPostPipeline class.
"""
def read(self) -> Any:
"""read(filepath)
read([filepaths], [values], unit, frame_type)
Reads in a single vtk file or creates a multiframe result by reading in multiple result files. If multiframe is wanted, 4 argumenhts are needed:
1. List of result files each being one frame,
2. List of values valid for each frame (e.g. [s] if time data),
3. the unit of the value as FreeCAD.Units.Unit,
4. the Description of the frame type"""
...
def scale(self) -> Any:
"""scale the points of a loaded vtk file"""
...
def load(self) -> Any:
"""load(result_object)
load([result_objects], [values], unit, frame_type)
Load a single result object or create a multiframe result by loading multiple result frames. If multiframe is wanted, 4 argumenhts are needed:
1. List of result files each being one frame,
2. List of values valid for each frame (e.g. [s] if time data),
3. the unit of the value as FreeCAD.Units.Unit,
4. the Description of the frame type"""
...
def getFilter(self) -> Any:
"""Returns all filters, that this pipeline uses (non recursive, result does not contain branch child filters)"""
...
def recomputeChildren(self) -> Any:
"""Recomputes all children of the pipeline"""
...
def getLastPostObject(self) -> Any:
"""Get the last post-processing object"""
...
def holdsPostObject(self) -> Any:
"""Check if this pipeline holds a given post-processing object"""
...
def renameArrays(self) -> Any:
"""Change name of data arrays"""
...
def getOutputAlgorithm(self) -> Any:
"""Returns the pipeline vtk algorithm, which generates the data passed to the pipelines filters. Note that the output algorithm may change depending on pipeline settings."""
...

View File

@@ -38,6 +38,11 @@ generate_from_xml(ViewProviderFemMeshPy)
generate_from_xml(ViewProviderFemPostPipelinePy)
generate_from_xml(ViewProviderFemPostFilterPy)
generate_from_py_(ViewProviderFemConstraintPy)
generate_from_py_(ViewProviderFemMeshPy)
generate_from_py_(ViewProviderFemPostPipelinePy)
generate_from_py_(ViewProviderFemPostFilterPy)
SET(Python_SRCS
ViewProviderFemConstraintPy.xml
ViewProviderFemConstraintPyImp.cpp

View File

@@ -0,0 +1,41 @@
from typing import Any, Final
from Base.Metadata import export
from Gui.ViewProviderGeometryObject import ViewProviderGeometryObject
@export(
Father="ViewProviderGeometryObjectPy",
Name="ViewProviderFemConstraintPy",
Twin="ViewProviderFemConstraint",
TwinPointer="ViewProviderFemConstraint",
Include="Mod/Fem/Gui/ViewProviderFemConstraint.h",
Namespace="FemGui",
FatherInclude="Gui/ViewProviderGeometryObjectPy.h",
FatherNamespace="Gui",
)
class ViewProviderFemConstraintPy(ViewProviderGeometryObject):
"""
This is the ViewProviderFemConstraint class
"""
def loadSymbol(self) -> Any:
"""loadSymbol(filename) -> None
Load constraint symbol from Open Inventor file.
The file structure should be as follows:
A separator containing a separator with the symbol used in
multiple copies at points on the surface and an optional
separator with a symbol excluded from multiple copies.
filename : str
Open Inventor file."""
...
SymbolNode: Final[Any]
"""A pivy SoSeparator with the nodes of the constraint symbols"""
ExtraSymbolNode: Final[Any]
"""A pivy SoSeparator with the nodes of the constraint extra symbols"""
RotateSymbol: bool
"""Apply rotation on copies of the constraint symbol"""

View File

@@ -0,0 +1,58 @@
from typing import Any, Final
from Base.Metadata import export
from Gui.ViewProviderGeometryObject import ViewProviderGeometryObject
@export(
Father="ViewProviderGeometryObjectPy",
Name="ViewProviderFemMeshPy",
Twin="ViewProviderFemMesh",
TwinPointer="ViewProviderFemMesh",
Include="Mod/Fem/Gui/ViewProviderFemMesh.h",
Namespace="FemGui",
FatherInclude="Gui/ViewProviderGeometryObjectPy.h",
FatherNamespace="Gui",
)
class ViewProviderFemMeshPy(ViewProviderGeometryObject):
"""
ViewProviderFemMesh class
"""
def applyDisplacement(self) -> Any:
""""""
...
def resetNodeColor(self) -> Any:
"""Reset color set by method setNodeColorByScalars."""
...
def resetNodeDisplacement(self) -> Any:
"""Reset displacements set by method setNodeDisplacementByVectors."""
...
def resetHighlightedNodes(self) -> Any:
"""Reset highlighted nodes."""
...
def setNodeColorByScalars(self) -> Any:
"""Sets mesh node colors using element list and value list."""
...
def setNodeDisplacementByVectors(self) -> Any:
""""""
...
NodeColor: dict
"""Postprocessing color of the nodes. The faces between the nodes get interpolated."""
ElementColor: dict
"""Postprocessing color of the elements. All faces of the element get the same color."""
NodeDisplacement: dict
"""Postprocessing color of the nodes. The faces between the nodes get interpolated."""
HighlightedNodes: list
"""List of nodes which get highlighted."""
VisibleElementFaces: Final[list]
"""List of elements and faces which are actually shown. These are all surface faces of the mesh."""

View File

@@ -0,0 +1,28 @@
from typing import Any
from Base.Metadata import export
from Gui.ViewProviderDocumentObject import ViewProviderDocumentObject
@export(
Father="ViewProviderDocumentObjectPy",
Name="ViewProviderFemPostFilterPy",
Twin="ViewProviderFemPostObject",
TwinPointer="ViewProviderFemPostObject",
Include="Mod/Fem/Gui/ViewProviderFemPostObject.h",
Namespace="FemGui",
FatherInclude="Gui/ViewProviderDocumentObjectPy.h",
FatherNamespace="Gui",
)
class ViewProviderFemPostFilterPy(ViewProviderDocumentObject):
"""
ViewProviderFemPostPipeline class
"""
def createDisplayTaskWidget(self) -> Any:
"""Returns the display option task panel for a post processing edit task dialog."""
...
def createExtractionTaskWidget(self) -> Any:
"""Returns the data extraction task panel for a post processing edit task dialog."""
...

View File

@@ -0,0 +1,28 @@
from typing import Any
from Base.Metadata import export
from Gui.ViewProviderDocumentObject import ViewProviderDocumentObject
@export(
Father="ViewProviderDocumentObjectPy",
Name="ViewProviderFemPostPipelinePy",
Twin="ViewProviderFemPostPipeline",
TwinPointer="ViewProviderFemPostPipeline",
Include="Mod/Fem/Gui/ViewProviderFemPostPipeline.h",
Namespace="FemGui",
FatherInclude="Gui/ViewProviderDocumentObjectPy.h",
FatherNamespace="Gui",
)
class ViewProviderFemPostPipelinePy(ViewProviderDocumentObject):
"""
ViewProviderFemPostPipeline class
"""
def transformField(self) -> Any:
"""Scales values of given result mesh field by given factor"""
...
def updateColorBars(self) -> Any:
"""Update coloring of pipeline and its childs"""
...