Diese Seite zeigt, wie erweiterte Funktionalität einfach in Python erstellt werden kann. In dieser Übung erstellen wir ein neues Werkzeug, das eine Linie zeichnet. Dieses Werkzeug kann dann mit einem FreeCAD-Befehl verknüpft werden und dieser Befehl kann von jedem Element der Oberfläche aufgerufen werden, wie z.B. von einem Menüeintrag oder von einer Schaltfläche in einer Symbolleiste .
Zuerst werden wir ein Skript schreiben, das unsere gesamte Funktionalität enthält. Dann werden wir dieses in einer Datei speichern und in FreeCAD importieren, so dass alle von uns geschriebenen Klassen und Funktionen in FreeCAD zur Verfügung stehen. In unserem bevorzugten Codeeditor geben wir folgende Zeilen ein:
import FreeCADGui, Part
from pivy.coin import *
class line:
"""This class will create a line after the user clicked 2 points on the screen"""
def __init__(self):
self.view = FreeCADGui.ActiveDocument.ActiveView
self.stack = []
self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.getpoint)
def getpoint(self, event_cb):
event = event_cb.getEvent()
if event.getState() == SoMouseButtonEvent.DOWN:
pos = event.getPosition()
point = self.view.getPoint(pos[0], pos[1])
self.stack.append(point)
if len(self.stack) == 2:
l = Part.LineSegment(self.stack[0], self.stack[1])
shape = l.toShape()
Part.show(shape)
self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.callback)
import Part, FreeCADGui
from pivy.coin import *
Sollen in Python Funktionen aus einem anderen Modul verwendet werden, musst es importiert werden. In unserem Fall benötigen wir Funktionen aus dem Modul Part, um die Linie zu erstellen, und aus dem GUI-Modul FreeCADGui
, um auf die 3D-Ansicht zuzugreifen. Außerdem benötigen wir die kompletten Inhalte der Coin-Bibliothek, damit wir alle Coin-Objekte wie SoMouseButtonEvent
, usw. direkt verwenden können.
class line:
Hier definieren wir unsere Hauptklasse. Warum verwenden wir eine Klasse und keine Funktion? Der Grund dafür ist, dass unser Werkzeug "lebendig" bleiben muss, während wir darauf warten, dass der Benutzer auf den Bildschirm klickt. Eine Funktion endet, wenn ihre Aufgabe erledigt ist, aber ein Objekt (eine Klasse definiert ein Objekt) bleibt lebendig, bis es zerstört wird.
"""This class will create a line after the user clicked 2 points on the screen"""
In Python kann jede Klasse oder Funktion einen Dokumentationszeichenkette (docstring) besitzen. Dies ist in FreeCAD besonders nützlich, denn wird diese Klasse im Interpreter aufgerufen, wird die Beschreibung in dieser Zeichenkette als QuickInfo (Tooltip) angezeigt.
def __init__(self):
Python-Klassen können immer eine Funktion __init__
enthalten, die beim Aufruf der Klasse zur Erzeugung eines Objekts ausgeführt wird. Wir werden hier also alles ablegen, was sich ereignen soll, wenn unser Linienwerkzeug seine Arbeit aufnimmt.
self.view = FreeCADGui.ActiveDocument.ActiveView
In einer Klasse wird normalerweise self.
einem Variablennamen vorangestellt, damit sie für alle Funktionen innerhalb und außerhalb dieser Klasse leicht zugänglich ist. Hier werden wir self.view
verwenden, um auf die aktive 3D-Ansicht zuzugreifen und sie zu bearbeiten.
self.stack = []
Hier erstellen wir eine leere Liste, die die 3D-Punkte aufnimmt, die von der Funktion getpoint()
zurückgegeben werden.
self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.getpoint)
Dies ist der wichtige Teil: Da es sich eigentlich um eine coin3D-Szene handelt, verwendet FreeCAD einen Coin-Rückrufmechanismus (callback mechanism), der ermöglicht, dass eine Funktion aufgerufen wird, wann immer ein bestimmtes Szenenereignis eintritt. In unserem Fall erstellen wir eine Rückruffunktion für SoMouseButtonEvent-Ereignisse und verbinden sie mit der Funktion getpoint()
. Ab jetzt wird jedes Mal, wenn eine Maustaste gedrückt oder losgelassen wird, die Funktion getpoint()
ausgeführt.
Man beachte, dass es auch eine Alternative zu addEventCallbackPivy()
namens addEventCallback()
gibt, die nicht auf pivy angewiesen ist. Aber da pivy eine sehr effiziente und natürliche Weise ist, um auf alle Teile einer Coin-Szene zuzugreifen, ist es die bessere Wahl.
def getpoint(self, event_cb):
Nun definieren wir die Funktion getpoint()
, die ausgeführt wird, wenn eine Maustaste in einer 3D-Ansicht gedrückt wird. Diese Funktion empfängt ein Argument, das wir event_cb
nennen. Von diesem Ereignisrückruf (event callback) aus können wir auf das Ereignisobjekt zugreifen, das verschiedene Informationen enthält (weitere Infos findet man hier).
if event.getState() == SoMouseButtonEvent.DOWN:
Die getpoint()
Funktion wird aufgerufen, wenn eine Maustaste gedrückt oder losgelassen wird. Aber wir wollen einen 3D-Punkt nur dann auswählen, wenn eine Maustaste gedrückt wird, andernfalls würden wir zwei 3D-Punkte sehr nahe beieinander erhalten. Daher müssen wir das an dieser Stelle überprüfen.
pos = event.getPosition()
Hier erhalten wir die Bildschirmkoordinaten des Mauszeigers.
point = self.view.getPoint(pos[0], pos[1])
Diese Funktion liefert uns einen FreeCAD-Vektor (x,y,z), der den 3D-Punkt enthält, der auf der fokussierten Ebene direkt unter unserem Mauszeiger liegt. Befindet man sich in der Kameraansicht befindest, kann man sich einen Strahl vorstellen, der von der Kamera kommt, durch den Mauszeiger hindurchgeht und auf die fokussierte Ebene trifft. Das ist die Position unseres 3D-Punktes. Wenn wir uns in der orthogonalen Ansicht befinden, ist der Strahl parallel zur Blickrichtung.
self.stack.append(point)
Wir fügen unseren neuen Punkt dem Stapel hinzu.
if len(self.stack) == 2:
Haben wir schon genug Punkte? Wenn ja, dann zeichnen wir jetzt die Linie!
l = Part.LineSegment(self.stack[0], self.stack[1])
Hier verwenden wir die Funktion LineSegment()
aus dem Modul Part, die eine Linie aus zwei FreeCAD-Vektoren erstellt. Die Linie ist nicht an irgendein Objekt unseres aktiven Dokuments gebunden, so dass nichts auf dem Bildschirm erscheint.
shape = l.toShape()
Das FreeCAD-Dokument kann nur Formen (Shapes) aus dem Modul Part übernehmen. Formen sind der grundlegendste Typ des Moduls Part. Daher müssen wir unsere Linie in eine Form konvertieren, bevor wir sie dem Dokument hinzufügen.
Part.show(shape)
Das Modul Part hat eine sehr nützliche Funktion show()
, die ein neues Objekt im Dokument erzeugt und eine Form daran bindet. Wir hätten auch zuerst ein neues Objekt im Dokument erstellen können und dann die Form manuell an dieses Objekt binden.
self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.callback)
Da wir mit unserer Linie fertig sind, entfernen wir den Rückrufmechanismus an dieser Stelle.
Nun speichern wir unser Skript an einem Ort, an dem FreeCADs Python-Interpreter es finden kann. Beim Import von Modulen wird der Interpreter an folgenden Stellen suchen: die Python-Installationspfade, FreeCADs bin-Verzeichnis und alle FreeCAD Mod-Verzeichnisse (Modulverzeichnisse). Die beste Lösung ist also, ein neues Verzeichnis in einem von FreeCADs Mod-Verzeichnissen anzulegen. Erstellen wir also ein Verzeichnis MyScripts und sichern darin unser Skript unter dem Namen exercise.py.
Jetzt, da alles bereit ist, starten wir FreeCAD, erstellen ein neues Dokument und geben im Python-Interpreter ein:
import exercise
Wenn keine Fehlermeldung erscheint, wurde unser Übungsskript erfolgreich geladen. Wir können nun seinen Inhalt überprüfen mit:
dir(exercise)
Der Befehl dir()
ist ein eingebauter Python-Befehl, der den Inhalt eines Moduls auflistet. Wir können prüfen, dass unsere Klasse line()
vorhanden ist mit:
'line' in dir(exercise)
Nun wollen wir es testen:
exercise.line()
Zwei Mal in die 3D-Ansicht klicken und Bingo: Hier ist unsere Linie! Um es zu wiederholen, einfach erneut exercise.line()
eintippen.
Damit unser neues Linienwerkzeug wirklich nützlich ist und um lästiges Tippen zu vermeiden, sollte es eine Schaltfläche auf der Oberfläche haben. Eine Möglichkeit ist, unseren neuen Ordner MyScripts in einen vollständigen FreeCAD-Arbeitsbereich umzuwandeln. Dies ist einfach: Es muss nur eine Datei InitGui.py im Ordner MyScripts abgelegt werden. InitGui.py enthält die Anweisungen zum Erstellen eines neuen Arbeitsbereiches und fügt unser neues Werkzeug hinzu. Außerdem müssen wir auch unseren Übungscode ein wenig umwandeln, damit das Werkzeug line()
als offizieller FreeCAD Befehl erkannt wird. Beginnen wir damit, eine Datei InitGui.py zu erstellen und den folgenden Code hineinzuschreiben:
class MyWorkbench (Workbench):
MenuText = "MyScripts"
def Initialize(self):
import exercise
commandslist = ["line"]
self.appendToolbar("My Scripts", commandslist)
Gui.addWorkbench(MyWorkbench())
Inzwischen sollten wir das obige Skript schon verstehen. Wir erstellen eine neue Klasse, die wir MyWorkbench
nennen, wir geben ihr den Namen MenuText
und wir definieren eine Funktion Initialize()
, die ausgeführt wird, wenn der Arbeitsbereich in FreeCAD geladen wird. Mit dieser Funktion laden wir den Inhalt unserer Übungsdatei und erweitern eine Befehlsliste mit den darin enthaltenen FreeCAD-Anweisungen. Dann erstellen wir eine Symbolleiste "My Scripts" und weisen ihr unsere Befehlsliste zu. Derzeit haben wir natürlich nur ein Werkzeug, so dass unsere Befehlsliste nur ein Element enthält. Und sobald unser Arbeitsbereich fertig ist, fügen wir ihn zur Hauptoberfläche hinzu.
Aber dies wird noch nicht funktionieren, da ein FreeCAD-Befehl auf eine bestimmte Weise formatiert werden muss. Damit er funktioniert, müssen wir unser Werkzeug line()
anpassen. Unser neues Skript exercise.py sollte nun so aussehen:
import FreeCADGui, Part
from pivy.coin import *
class line:
"""This class will create a line after the user clicked 2 points on the screen"""
def Activated(self):
self.view = FreeCADGui.ActiveDocument.ActiveView
self.stack = []
self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.getpoint)
def getpoint(self, event_cb):
event = event_cb.getEvent()
if event.getState() == SoMouseButtonEvent.DOWN:
pos = event.getPosition()
point = self.view.getPoint(pos[0], pos[1])
self.stack.append(point)
if len(self.stack) == 2:
l = Part.LineSegment(self.stack[0], self.stack[1])
shape = l.toShape()
Part.show(shape)
self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(), self.callback)
def GetResources(self):
return {'Pixmap': 'path_to_an_icon/line_icon.png', 'MenuText': 'Line', 'ToolTip': 'Creates a line by clicking 2 points on the screen'}
FreeCADGui.addCommand('line', line())
Was wir hier gemacht haben, ist unsere Funktion __init__()
in eine Funktion Activated()
umzuwandeln, denn wenn FreeCAD-Befehle gestartet werden, führen sie automatisch die Funktion Activated()
aus. Wir haben auch eine Funktion GetResources()
hinzugefügt, die FreeCAD darüber informiert, wo ein Symbol für das Werkzeug zu finden ist und wie sein Name sowie die zugehörige Quickinfo lautet. Jedes jpg-, png- oder svg-Bild funktioniert als Symbol und kann eine beliebige Größe haben, aber am besten ist es, eine Größe zu verwenden, die dem endgültigen Seitenverhältnis nahe kommt, wie 16x16, 24x24 oder 32x32.
Dann fügen wir die Klasse line()
als offiziellen FreeCAD-Befehl mit der Methode addCommand()
hinzu.
Das war's; anschließend müssen wir nur noch FreeCAD neu starten und schon haben wir einen schönen neuen Arbeitsbereich mit unserem nagelneuen Linienwerkzeug!
Wenn Dir diese Übung gefallen hat, warum versuchst Du nicht, dieses kleine Werkzeug zu verbessern? Es gibt viele Dinge, die man tun kann, wie zum Beispiel
Zögere nicht, deine Fragen oder Ideen im Forum zu teilen!