Wrapping a Cplusplus class in Python: Difference between revisions
mNo edit summary |
Wandererfan (talk | contribs) (translate tags were mixed up with code tags) |
||
(14 intermediate revisions by 6 users not shown) | |||
Line 2: | Line 2: | ||
<translate> |
<translate> |
||
<!--T:1--> |
|||
{{VeryImportantMessage|This article is a stub. Please contribute your knowledge to it!}} |
{{VeryImportantMessage|This article is a stub. Please contribute your knowledge to it!}} |
||
{{TOCright}} |
{{TOCright}} |
||
== Background == |
== Background == <!--T:2--> |
||
<!--T:3--> |
|||
FreeCAD uses a custom XML-based system to create the Python wrapper for a C++ class. To wrap a C++ class for use in Python, two files must be manually created, and two files are automatically generated by the CMake build system (in addition to the C++ header and implementation files for the class). |
FreeCAD uses a custom XML-based system to create the Python wrapper for a C++ class. To wrap a C++ class for use in Python, two files must be manually created, and two files are automatically generated by the CMake build system (in addition to the C++ header and implementation files for the class). |
||
<!--T:4--> |
|||
You must create: |
You must create: |
||
* {{incode|[YourClass]Py.xml}} |
* {{incode|[YourClass]Py.xml}} |
||
* {{incode|[YourClass]PyImp.cpp}} |
* {{incode|[YourClass]PyImp.cpp}} |
||
<!--T:5--> |
|||
Edit the appropriate {{FileName|CMakeLists.txt}} file to add references to these two files. From the XML file, the build system will then create: |
Edit the appropriate {{FileName|CMakeLists.txt}} file to add references to these two files. From the XML file, the build system will then create: |
||
* {{incode|[YourClass]Py.cpp}} |
* {{incode|[YourClass]Py.cpp}} |
||
* {{incode|[YourClass]Py.h}} |
* {{incode|[YourClass]Py.h}} |
||
== Class Description XML File == |
== Class Description XML File == <!--T:6--> |
||
<!--T:7--> |
|||
The XML file {{incode|[YourClass]Py.xml}} provides information about the functions and attributes that the Python class implements, as well as the user documentation for those items that displays in the FreeCAD [[Python console]]. |
The XML file {{incode|[YourClass]Py.xml}} provides information about the functions and attributes that the Python class implements, as well as the user documentation for those items that displays in the FreeCAD [[Python_console|Python console]]. |
||
<!--T:8--> |
|||
For this example, we will look at the wrapper for the Axis C++ class. The XML description file begins: |
For this example, we will look at the wrapper for the Axis C++ class. The XML description file begins with: |
||
</translate> |
|||
{{Code|lang=xml|code= |
{{Code|lang=xml|code= |
||
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||
<GenerateModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="generateMetaModel_Module.xsd"> |
<GenerateModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="generateMetaModel_Module.xsd"> |
||
<PythonExport |
|||
Father="PyObjectBase" |
|||
Name="AxisPy" |
|||
Twin="Axis" |
|||
TwinPointer="Axis" |
|||
Include="Base/Axis.h" |
|||
FatherInclude="Base/PyObjectBase.h" |
|||
Namespace="Base" |
|||
Constructor="true" |
|||
Delete="true" |
|||
FatherNamespace="Base"> |
|||
<Documentation> |
|||
<Author Licence="LGPL" Name="Juergen Riegel" EMail="FreeCAD@juergen-riegel.net" /> |
|||
<UserDocu>User documentation here |
|||
<!-- Lengthy comments removed. See source file. --> |
|||
An defines a direction and a position (base) in 3D space. |
|||
⚫ | |||
⚫ | |||
The following constructors are supported: |
|||
⚫ | |||
Axis() -- empty constructor |
|||
Axis(Axis) -- copy constructor |
|||
Axis(Base, Direction) -- define position and direction |
|||
⚫ | |||
⚫ | |||
⚫ | |||
}} |
}} |
||
<translate> |
|||
<!--T:10--> |
|||
Following this preamble, a list of methods and attributes is given. The format of a method is: |
Following this preamble, a list of methods and attributes is given. The format of a method is: |
||
</translate> |
|||
{{Code|lang=xml|code= |
{{Code|lang=xml|code= |
||
<Methode Name="move"> |
|||
<Documentation> |
|||
<UserDocu> |
<UserDocu> |
||
move(Vector) |
move(Vector) |
||
Move the axis base along the vector |
Move the axis base along the vector |
||
</UserDocu> |
</UserDocu> |
||
</Documentation> |
|||
</Methode> |
|||
}} |
}} |
||
<translate> |
|||
<!--T:11--> |
|||
The format of an attribute is: |
The format of an attribute is: |
||
</translate> |
|||
{{Code|lang=xml|code= |
{{Code|lang=xml|code= |
||
<Attribute Name="Direction" ReadOnly="false"> |
|||
<Documentation> |
|||
<UserDocu>Direction vector of the Axis</UserDocu> |
<UserDocu>Direction vector of the Axis</UserDocu> |
||
</Documentation> |
|||
<Parameter Name="Direction" Type="Object" /> |
|||
</Attribute> |
|||
}} |
}} |
||
<translate> |
|||
<!--T:12--> |
|||
For an attribute, if "ReadOnly" is false, you will provide both a getter and a setter function. If it is true, only a getter is allowed. In this case we will be required to provide two functions in the implementation C++ file: |
For an attribute, if "ReadOnly" is false, you will provide both a getter and a setter function. If it is true, only a getter is allowed. In this case we will be required to provide two functions in the implementation C++ file: |
||
</translate> |
|||
{{Code|lang=cpp|code= |
{{Code|lang=cpp|code= |
||
Py::Object AxisPy::getDirection(void) const |
Py::Object AxisPy::getDirection(void) const |
||
}} |
}} |
||
<translate> |
|||
⚫ | |||
<!--T:26--> |
|||
⚫ | |||
</translate> |
|||
{{Code|lang=cpp|code= |
{{Code|lang=cpp|code= |
||
void AxisPy::setDirection(Py::Object arg) |
void AxisPy::setDirection(Py::Object arg) |
||
}} |
}} |
||
<translate> |
|||
== Implementation Cplusplus File == |
== Implementation Cplusplus File == <!--T:13--> |
||
<!--T:14--> |
|||
The implementation C++ file {{incode|[YourClass]PyImp.cpp}} provides the "glue" that connects the C++ and Python structures together, effectively translating from one language to the other. The FreeCAD C++-to-Python system provides a number of C++ classes that map to their corresponding Python type. The most fundamental of these is the {{incode|Py::Object}} class -- rarely created directly, this class provides the base of the inheritance tree, and is used as the return type for any function that is returning Python data. |
The implementation C++ file {{incode|[YourClass]PyImp.cpp}} provides the "glue" that connects the C++ and Python structures together, effectively translating from one language to the other. The FreeCAD C++-to-Python system provides a number of C++ classes that map to their corresponding Python type. The most fundamental of these is the {{incode|Py::Object}} class -- rarely created directly, this class provides the base of the inheritance tree, and is used as the return type for any function that is returning Python data. |
||
=== Include Files === |
=== Include Files === <!--T:15--> |
||
<!--T:16--> |
|||
Your C++ implementation file will include the following files: |
Your C++ implementation file will include the following files: |
||
</translate> |
|||
{{Code|lang=cpp|code= |
{{Code|lang=cpp|code= |
||
#include "PreCompiled.h" |
#include "PreCompiled.h" |
||
Line 97: | Line 122: | ||
#include "[YourClass]Py.cpp" |
#include "[YourClass]Py.cpp" |
||
}} |
}} |
||
<translate> |
|||
<!--T:19--> |
|||
Of course, you may include whatever other C++ headers your code requires to function as well. |
Of course, you may include whatever other C++ headers your code requires to function as well. |
||
=== Constructor === |
=== Constructor === <!--T:20--> |
||
<!--T:21--> |
|||
Your C++ implementation must contain the definition of the PyInit function: for example, for the Axis class wrapper, this is |
Your C++ implementation must contain the definition of the PyInit function: for example, for the Axis class wrapper, this is |
||
</translate> |
|||
{{Code|lang=cpp|code= |
{{Code|lang=cpp|code= |
||
int AxisPy::PyInit(PyObject* args, PyObject* /*kwd*/) |
int AxisPy::PyInit(PyObject* args, PyObject* /*kwd*/) |
||
}} |
}} |
||
<translate> |
|||
<!--T:22--> |
|||
Within this function you will most likely need to parse incoming arguments to the constructor: the most important function for this purpose is the Python-provided {{incode|PyArg_ParseTuple}}. It takes in the passed argument list, a descriptor for the expected arguments that it should parse, and type information and storage locations for the parsed results. For example: |
Within this function you will most likely need to parse incoming arguments to the constructor: the most important function for this purpose is the Python-provided {{incode|PyArg_ParseTuple}}. It takes in the passed argument list, a descriptor for the expected arguments that it should parse, and type information and storage locations for the parsed results. For example: |
||
</translate> |
|||
{{Code|lang=cpp|code= |
{{Code|lang=cpp|code= |
||
PyObject* d; |
PyObject* d; |
||
Line 118: | Line 152: | ||
} |
} |
||
}} |
}} |
||
<translate> |
|||
<!--T:23--> |
|||
For a complete list of format specifiers see [https://docs.python.org/3/c-api/arg.html Python C API documentation]. Note that several related functions are also defined which allow the use of keywords, etc. The complete set is: |
For a complete list of format specifiers see [https://docs.python.org/3/c-api/arg.html Python C API documentation]. Note that several related functions are also defined which allow the use of keywords, etc. The complete set is: |
||
</translate> |
|||
{{Code|lang=cpp|code= |
{{Code|lang=cpp|code= |
||
PyAPI_FUNC(int) PyArg_Parse (PyObject *, const char *, ...); |
PyAPI_FUNC(int) PyArg_Parse (PyObject *, const char *, ...); |
||
Line 128: | Line 165: | ||
PyAPI_FUNC(int) PyArg_VaParseTupleAndKeywords (PyObject *, PyObject *, const char *, char **, va_list); |
PyAPI_FUNC(int) PyArg_VaParseTupleAndKeywords (PyObject *, PyObject *, const char *, char **, va_list); |
||
}} |
}} |
||
<translate> |
|||
== Links == <!--T:27--> |
|||
<!--T:28--> |
|||
* [[Exposing_Cplusplus_to_Python|Exposing Cplusplus to Python]] |
|||
* [https://github.com/FreeCAD/FreeCAD/commit/20b86e55b8dd1873f4c19e036d047528c9ff7f4e Commit 20b86e5, exposing OCC's precision methods to Python] |
|||
</translate> |
</translate> |
||
{{Powerdocnavi{{#translation:}}}} |
{{Powerdocnavi{{#translation:}}}} |
||
[[Category:Developer{{#translation:}}]] |
|||
[[Category:Developer Documentation{{#translation:}}]] |
Latest revision as of 03:52, 20 December 2022
Background
FreeCAD uses a custom XML-based system to create the Python wrapper for a C++ class. To wrap a C++ class for use in Python, two files must be manually created, and two files are automatically generated by the CMake build system (in addition to the C++ header and implementation files for the class).
You must create:
[YourClass]Py.xml
[YourClass]PyImp.cpp
Edit the appropriate CMakeLists.txt file to add references to these two files. From the XML file, the build system will then create:
[YourClass]Py.cpp
[YourClass]Py.h
Class Description XML File
The XML file [YourClass]Py.xml
provides information about the functions and attributes that the Python class implements, as well as the user documentation for those items that displays in the FreeCAD Python console.
For this example, we will look at the wrapper for the Axis C++ class. The XML description file begins with:
<?xml version="1.0" encoding="UTF-8"?>
<GenerateModel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="generateMetaModel_Module.xsd">
<PythonExport
Father="PyObjectBase"
Name="AxisPy"
Twin="Axis"
TwinPointer="Axis"
Include="Base/Axis.h"
FatherInclude="Base/PyObjectBase.h"
Namespace="Base"
Constructor="true"
Delete="true"
FatherNamespace="Base">
<Documentation>
<Author Licence="LGPL" Name="Juergen Riegel" EMail="FreeCAD@juergen-riegel.net" />
<UserDocu>User documentation here
</UserDocu>
<DeveloperDocu>Developer documentation here</DeveloperDocu>
</Documentation>
Following this preamble, a list of methods and attributes is given. The format of a method is:
<Methode Name="move">
<Documentation>
<UserDocu>
move(Vector)
Move the axis base along the vector
</UserDocu>
</Documentation>
</Methode>
The format of an attribute is:
<Attribute Name="Direction" ReadOnly="false">
<Documentation>
<UserDocu>Direction vector of the Axis</UserDocu>
</Documentation>
<Parameter Name="Direction" Type="Object" />
</Attribute>
For an attribute, if "ReadOnly" is false, you will provide both a getter and a setter function. If it is true, only a getter is allowed. In this case we will be required to provide two functions in the implementation C++ file:
Py::Object AxisPy::getDirection(void) const
and:
void AxisPy::setDirection(Py::Object arg)
Implementation Cplusplus File
The implementation C++ file [YourClass]PyImp.cpp
provides the "glue" that connects the C++ and Python structures together, effectively translating from one language to the other. The FreeCAD C++-to-Python system provides a number of C++ classes that map to their corresponding Python type. The most fundamental of these is the Py::Object
class -- rarely created directly, this class provides the base of the inheritance tree, and is used as the return type for any function that is returning Python data.
Include Files
Your C++ implementation file will include the following files:
#include "PreCompiled.h"
#include "[YourClass].h"
// Inclusion of the generated files (generated out of [YourClass]Py.xml)
#include "[YourClass]Py.h"
#include "[YourClass]Py.cpp"
Of course, you may include whatever other C++ headers your code requires to function as well.
Constructor
Your C++ implementation must contain the definition of the PyInit function: for example, for the Axis class wrapper, this is
int AxisPy::PyInit(PyObject* args, PyObject* /*kwd*/)
Within this function you will most likely need to parse incoming arguments to the constructor: the most important function for this purpose is the Python-provided PyArg_ParseTuple
. It takes in the passed argument list, a descriptor for the expected arguments that it should parse, and type information and storage locations for the parsed results. For example:
PyObject* d;
if (PyArg_ParseTuple(args, "O!O", &(Base::VectorPy::Type), &o,
&(Base::VectorPy::Type), &d)) {
// NOTE: The first parameter defines the base (origin) and the second the direction.
*getAxisPtr() = Base::Axis(static_cast<Base::VectorPy*>(o)->value(),
static_cast<Base::VectorPy*>(d)->value());
return 0;
}
For a complete list of format specifiers see Python C API documentation. Note that several related functions are also defined which allow the use of keywords, etc. The complete set is:
PyAPI_FUNC(int) PyArg_Parse (PyObject *, const char *, ...);
PyAPI_FUNC(int) PyArg_ParseTuple (PyObject *, const char *, ...);
PyAPI_FUNC(int) PyArg_ParseTupleAndKeywords (PyObject *, PyObject *, const char *, char **, ...);
PyAPI_FUNC(int) PyArg_VaParse (PyObject *, const char *, va_list);
PyAPI_FUNC(int) PyArg_VaParseTupleAndKeywords (PyObject *, PyObject *, const char *, char **, va_list);
Links
- FreeCAD scripting: Python, Introduction to Python, Python scripting tutorial, FreeCAD Scripting Basics
- Modules: Builtin modules, Units, Quantity
- Workbenches: Workbench creation, Gui Commands, Commands, Installing more workbenches
- Meshes and Parts: Mesh Scripting, Topological data scripting, Mesh to Part, PythonOCC
- Parametric objects: Scripted objects, Viewproviders (Custom icon in tree view)
- Scenegraph: Coin (Inventor) scenegraph, Pivy
- Graphical interface: Interface creation, Interface creation completely in Python (1, 2, 3, 4, 5), PySide, PySide examples beginner, intermediate, advanced
- Macros: Macros, How to install macros
- Embedding: Embedding FreeCAD, Embedding FreeCADGui
- Other: Expressions, Code snippets, Line drawing function, FreeCAD vector math library (deprecated)
- Hubs: User hub, Power users hub, Developer hub