FreeCAD verwendet ein benutzerdefiniertes XML-basiertes System, um den Python-Wrapper für eine C++-Klasse zu erstellen. Um eine C++-Klasse für die Verwendung in Python zu verpacken, müssen zwei Dateien manuell erstellt werden, und zwei Dateien werden automatisch vom CMake-Build-System generiert (zusätzlich zu den C++-Header- und Implementierungsdateien für die Klasse).
Es muss erstellt werden:
[YourClass]Py.xml[YourClass]PyImp.cppMan bearbeitet die entsprechende Datei CMakeLists.txt, um Verweise auf diese beiden Dateien hinzuzufügen. Aus der XML-Datei erstellt das Build-System dann Folgendes:
[YourClass]Py.cpp[YourClass]Py.h
Die XML-Datei [YourClass]Py.xml enthält Informationen zu den Funktionen und Attributen, die die Python-Klasse implementiert, sowie die Benutzerdokumentation für diese Elemente, die in FreeCAD in der Python-Konsole anzeigt.
Für dieses Beispiel schauen wir uns den Wrapper der Achsen-C++-Klasse an. Die XML-Beschreibung beginnt mit:
<?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>
Im Anschluss an diese Einleitung folgt eine Liste von Methoden und Attributen. Das Format einer Methode lautet:
<Methode Name="move">
<Documentation>
<UserDocu>
move(Vector)
Move the axis base along the vector
</UserDocu>
</Documentation>
</Methode>
Das Format eines Attributes ist:
<Attribute Name="Direction" ReadOnly="false">
<Documentation>
<UserDocu>Direction vector of the Axis</UserDocu>
</Documentation>
<Parameter Name="Direction" Type="Object" />
</Attribute>
Wenn für ein Attribut „ReadOnly” falsch ist, muss man sowohl eine Getter- als auch eine Setter-Funktion bereitstellen. Ist es wahr, ist nur eine Getter-Funktion zulässig. In diesem Fall müssen wir zwei Funktionen in der C++-Implementierungsdatei bereitstellen:
Py::Object AxisPy::getDirection(void) const
und:
void AxisPy::setDirection(Py::Object arg)
Die Implementierungsdatei [YourClass]PyImp.cpp in C++ fungiert als "Klebstoff", der die C++- und Python-Strukturen miteinander verbindet und so eine effektive Übersetzung von einer Sprache in die andere ermöglicht. Das FreeCAD-System für die Übersetzung von C++ nach Python stellt eine Reihe von C++-Klassen bereit, die ihren entsprechenden Python-Typen zugeordnet sind. Die grundlegendste davon ist die Klasse Py::Object – diese Klasse wird selten direkt erstellt, bildet jedoch die Basis des Vererbungsbaums und wird als Rückgabetyp für alle Funktionen verwendet, die Python-Daten zurückgeben.
Die C++_Implementationsdatei enthält die folgenden Dateien:
#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"
Selbstverständlich kann man auch alle anderen C++-Header einbinden, die der Code benötigt, um zu funktionieren.
Die C++-Implementierung muss die Definition der PyInit-Funktion enthalten: Für den Axis-Klassen-Wrapper lautet diese beispielsweise
int AxisPy::PyInit(PyObject* args, PyObject* /*kwd*/)
Innerhalb dieser Funktion muss man höchstwahrscheinlich eingehende Argumente für den Konstruktor analysieren: Die wichtigste Funktion für diesen Zweck ist die von Python bereitgestellte PyArg_ParseTuple. Sie nimmt die übergebene Argumentliste, einen Deskriptor für die zu analysierenden erwarteten Argumente sowie Typinformationen und Speicherorte für die analysierten Ergebnisse entgegen. Beispiel:
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;
}
Eine vollständige Liste der Formatbezeichner findet man in der Python C API-Dokumentation. Man beachte, dass auch mehrere verwandte Funktionen definiert sind, die die Verwendung von Schlüsselwörtern usw. ermöglichen. Der vollständige Satz lautet:
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);