Line drawing function/de: Difference between revisions

From FreeCAD Documentation
(Created page with "Diese Seite zeigt, wie einfach erweiterte Funktionalität in Python gebaut werden kann. In dieser Übung werden wir ein neues Werkzeug bauen, das eine Linie zeichnet. Dieses W...")
(Updating to match new version of source page)
(32 intermediate revisions by one other user not shown)
Line 4: Line 4:
Diese Seite zeigt, wie einfach erweiterte Funktionalität in Python gebaut werden kann. In dieser Übung werden wir ein neues Werkzeug bauen, das eine Linie zeichnet. Dieses Werkzeug kann dann mit einem FreeCAD Befehl verknüpft werden, und dieser Befehl kann von jedem Element der Schnittstelle, wie z.B. einem Menüpunkt oder einer Schaltfläche in der Werkzeugleiste, aufgerufen werden.
Diese Seite zeigt, wie einfach erweiterte Funktionalität in Python gebaut werden kann. In dieser Übung werden wir ein neues Werkzeug bauen, das eine Linie zeichnet. Dieses Werkzeug kann dann mit einem FreeCAD Befehl verknüpft werden, und dieser Befehl kann von jedem Element der Schnittstelle, wie z.B. einem Menüpunkt oder einer Schaltfläche in der Werkzeugleiste, aufgerufen werden.


==The main script==
==Das Hauptskript==
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. Starte also deinen bevorzugten Texteditor und gib die folgenden Zeilen ein:
First we will write a script containing all our functionality. Then, we will save this in a file, and import it in FreeCAD, so all classes and functions we write will be availible to FreeCAD. So, launch your favorite text editor, and type the following lines:
{{Code|code=
{{Code|code=
import FreeCADGui, Part
import FreeCADGui, Part
Line 24: Line 24:
self.stack.append(point)
self.stack.append(point)
if len(self.stack) == 2:
if len(self.stack) == 2:
l = Part.Line(self.stack[0],self.stack[1])
l = Part.LineSegment(self.stack[0],self.stack[1])
shape = l.toShape()
shape = l.toShape()
Part.show(shape)
Part.show(shape)
Line 30: Line 30:
}}
}}


==Detaillierte Erklärung==
==Detailed explanation==
{{Code|code=
{{Code|code=
import Part, FreeCADGui
import Part, FreeCADGui
from pivy.coin import *
from pivy.coin import *
}}
}}
In Python, when you want to use functions from another module, you need to import it. In our case, we will need functions from the [[Part Module]], for creating the line, and from the Gui module (FreeCADGui), for accessing the 3D view. We also need the complete contents of the coin library, so we can use directly all coin objects like SoMouseButtonEvent, etc...
Wenn Du in Python Funktionen aus einem anderen Modul verwenden willst, musst Du es importieren. In unserem Fall benötigen wir Funktionen aus dem [[Part-Modul]], um die Linie zu erstellen, und aus dem Gui Modul (FreeCADGui), um auf die 3D Ansicht zuzugreifen. Außerdem benötigen wir den kompletten Inhalt der Coin Bibliothek, damit wir alle Coin Objekte wie SoMouseButtonEvent, etc. direkt verwenden können.
{{Code|code=
{{Code|code=
class line:
class line:
}}
}}
Hier definieren wir unsere Hauptklasse. Warum verwenden wir eine Klasse und keine Funktion?
Here we define our main class. Why do we use a class and not a function? The reason is that we need our tool to stay "alive" while we are waiting for the user to click on the screen. A function ends when its task has been done, but an object (a class defines an object) stays alive until it is destroyed.
Der Grund dafür ist, dass wir unser Werkzeug "am Leben" halten müssen, 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 am Leben, bis es zerstört wird.
{{Code|code=
{{Code|code=
"""This class will create a line after the user clicked 2 points on the screen"""
"""This class will create a line after the user clicked 2 points on the screen"""
}}
}}
In Python, every class or function can have a documentation string (docstring). This is particularly useful in FreeCAD, because when you call that class in the interpreter, the description string will be displayed as a tooltip.
In Python kann jede Klasse oder Funktion einen Dokumentationszeichenkette (docstring) haben. Dies ist besonders in FreeCAD nützlich, denn wenn du diese Klasse im Interpreter aufrufst, wird der Beschreibungszeichenkette als Tooltip angezeigt.
{{Code|code=
{{Code|code=
def __init__(self):
def __init__(self):
}}
}}
Python Klassen können immer eine __init__-Funktion 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 beginnt.
Python classes can always contain an __init__ function, which is executed when the class is called to create an object. So, we will put here everything we want to happen when our line tool begins.
{{Code|code=
{{Code|code=
self.view = FreeCADGui.ActiveDocument.ActiveView
self.view = FreeCADGui.ActiveDocument.ActiveView
}}
}}
In einer Klasse möchte man normalerweise ''self.'' vor einem Variablennamen anhängen, 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 verändern.
In a class, you usually want to append ''self.'' before a variable name, so it will be easily accessible to all functions inside and outside that class. Here, we will use self.view to access and manipulate the active 3D view.
{{Code|code=
{{Code|code=
self.stack = []
self.stack = []
}}
}}
Hier erstellen wir eine leere Liste, die die von der Getpoint Funktion gesendeten 3D Punkte enthält.
Here we create an empty list that will contain the 3D points sent by the getpoint function.
{{Code|code=
{{Code|code=
self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.getpoint)
self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.getpoint)
}}
}}
This is the important part: Since it is actually a [http://www.coin3d.org/ coin3D] scene, the FreeCAD uses coin callback mechanism, that allows a function to be called everytime a certain scene event happens. In our case, we are creating a callback for [http://doc.coin3d.org/Coin/group__events.html SoMouseButtonEvent] events, and we bind it to the getpoint function. Now, everytime a mouse button is pressed or released, the getpoint function will be executed.
Das ist der wichtige Teil: Da es sich eigentlich um eine [http://www.coin3d.org/ coin3D] Szene handelt, verwendet FreeCAD einen Coin Rückruf Mechanismus, der es erlaubt, eine Funktion jedes Mal aufzurufen, wenn ein bestimmtes Szene Ereignis eintritt. In unserem Fall erstellen wir einen Rückruf für [http://doc.coin3d.org/Coin/group__events.html SoMouseButtonEvent] Ereignisse, und wir binden ihn an die getpoint Funktion. Nun wird jedes Mal, wenn eine Maustaste gedrückt oder losgelassen wird, die getpoint Funktion ausgeführt.


Note that there is also an alternative to addEventCallbackPivy() called addEventCallback() which dispenses the use of pivy. But since pivy is a very efficient and natural way to access any part of the coin scene, it is much better to use it as much as you can!
Beachte, dass es auch eine Alternative zu addEventCallbackPivy() namens addEventCallback() gibt, welche die Verwendung von pivy überflüssig macht. Aber da pivy ein sehr effizienter und natürlicher Weg ist, um auf jeden Teil der coin Szene zuzugreifen, ist es viel besser, es so oft wie möglich zu verwenden!
{{Code|code=
{{Code|code=
def getpoint(self,event_cb):
def getpoint(self,event_cb):
}}
}}
Now we define the getpoint function, that will be executed when a mouse button is pressed in a 3D view. This function will receive an argument, that we will call event_cb. From this event callback we can access the event object, which contains several pieces of information (mode info [[Code_snippets#Observing_mouse_events_in_the_3D_viewer_via_Python|here]]).
Nun definieren wir die Getpoint Funktion, die beim Drücken einer Maustaste in einer 3D Ansicht ausgeführt wird. Diese Funktion erhält ein Argument, das wir event_cb aufrufen. Von diesem Ereignisrückruf aus können wir auf das Ereignisobjekt zugreifen, das verschiedene Informationen enthält (Mode Info [[Code_snippets#Observing_mouse_events_in_the_3D_viewer_via_Python/de|Hier]]).
{{Code|code=
{{Code|code=
if event.getState() == SoMouseButtonEvent.DOWN:
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 aufnehmen, wenn er gedrückt ist (sonst würden wir zwei 3D Punkte sehr nahe aneinander bekommen). Das müssen wir also hier überprüfen.
The getpoint function will be called when a mouse button is pressed or released. But we want to pick a 3D point only when pressed (otherwise we would get two 3D points very close to each other). So we must check for that here.
{{Code|code=
{{Code|code=
pos = event.getPosition()
pos = event.getPosition()
}}
}}
Hier erhalten wir die Bildschirmkoordinaten des Mauszeigers
Here we get the screen coordinates of the mouse cursor
{{Code|code=
{{Code|code=
point = self.view.getPoint(pos[0],pos[1])
point = self.view.getPoint(pos[0],pos[1])
}}
}}
Diese Funktion gibt uns einen FreeCAD Vektor (x,y,z), der den 3D Punkt enthält, der auf der Brennpunktebene direkt unter unserem Mauszeiger liegt. Wenn Du Dich in der Kameraansicht befindest, stelle dir einen Strahl vor, der von der Kamera kommt, durch den Mauszeiger hindurchgeht und auf die Brennpunktebene trifft. Dort ist unser 3D Punkt. Wenn wir uns in der orthogonalen Ansicht befinden, ist der Strahl parallel zur Blickrichtung.
This function gives us a FreeCAD vector (x,y,z) containing the 3D point that lies on the focal plane, just under our mouse cursor. If you are in camera view, imagine a ray coming from the camera, passing through the mouse cursor, and hitting the focal plane. There is our 3D point. If we are in orthogonal view, the ray is parallel to the view direction.
{{Code|code=
{{Code|code=
self.stack.append(point)
self.stack.append(point)
}}
}}
Wir fügen unseren neuen Punkt dem Stapel hinzu
We add our new point to the stack
{{Code|code=
{{Code|code=
if len(self.stack) == 2:
if len(self.stack) == 2:
}}
}}
Haben wir schon genug Punkte? Wenn ja, dann zeichnen wir die Linie!
Do we have enough points already? if yes, then let's draw the line!
{{Code|code=
{{Code|code=
l = Part.Line(self.stack[0],self.stack[1])
l = Part.LineSegment(self.stack[0],self.stack[1])
}}
}}
Here we use the function Line() from the [[Part Module]] that creates a line from two FreeCAD vectors. Everything we create and modify inside the Part module, stays in the Part module. So, until now, we created a Line Part. It is not bound to any object of our active document, so nothing appears on the screen.
Hier verwenden wir die Funktion Line() aus dem [[Part Module/de]], die eine Linie aus zwei FreeCAD Vektoren erzeugt. Alles, was wir innerhalb des Part Moduls erstellen und modifizieren, bleibt im Part Modul. Bis jetzt haben wir also ein Linien Part erstellt. Es ist nicht an ein Objekt unseres aktiven Dokuments gebunden, so dass nichts auf dem Bildschirm erscheint.
{{Code|code=
{{Code|code=
shape = l.toShape()
shape = l.toShape()
}}
}}
The FreeCAD document can only accept shapes from the Part module. Shapes are the most generic type of the Part module. So, we must convert our line to a shape before adding it to the document.
Das FreeCAD Dokument kann nur Formen aus dem Part Modul übernehmen. Formen sind der grundlegendste Typ des Part Moduls. Daher müssen wir unsere Linie in eine Form konvertieren, bevor wir sie dem Dokument hinzufügen.
{{Code|code=
{{Code|code=
Part.show(shape)
Part.show(shape)
}}
}}
Das Part Modul hat eine sehr nützliche show() Funktion, 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 können.
The Part module has a very handy show() function that creates a new object in the document and binds a shape to it. We could also have created a new object in the document first, then bound the shape to it manually.
{{Code|code=
{{Code|code=
self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.callback)
self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.callback)
}}
}}
Da wir mit unserer Linie fertig sind, lasst uns den Rückrufmechanismus entfernen, der wertvolle CPU Zyklen verbraucht.
Since we are done with our line, let's remove the callback mechanism, that consumes precious CPU cycles.


==Testing & Using the script==
==Test & Anwendung des Skripts==
Now, let's save our script to some place where the FreeCAD python interpreter will find it. When importing modules, the interpreter will look in the following places: the python installation paths, the FreeCAD bin directory, and all FreeCAD modules directories. So, the best solution is to create a new directory in one of the FreeCAD [[Installing_more_workbenches|Mod directories]], and to save our script in it. For example, let's make a "MyScripts" directory, and save our script as "exercise.py".
Nun speichern wir unser Skript an einem Ort, an dem der FreeCAD Python Interpreter es finden kann. Beim Import von Modulen wird der Interpreter an folgenden Stellen suchen: die Python Installationspfade, das FreeCAD bin Verzeichnis und alle FreeCAD Modulverzeichnisse. Die beste Lösung ist also, ein neues Verzeichnis in einem der FreeCAD [[Installing_more_workbenches/de|Mod Verzeichnisse]] anzulegen und unser Skript darin zu speichern. Erstellen wir zum Beispiel ein Verzeichnis "MyScripts" und speichern unser Skript als "exercise.py".


Nun, alles ist bereit, lass uns FreeCAD starten, ein neues Dokument erstellen und im Python Interpreter eingeben:
Now, everything is ready, let's start FreeCAD, create a new document, and, in the python interpreter, issue:
{{Code|code=
{{Code|code=
import exercise
import exercise
}}
}}
Wenn keine Fehlermeldung erscheint, bedeutet das, dass unser Übungsskript geladen wurde. Wir können nun seinen Inhalt überprüfen mit:
If no error message appear, that means our exercise script has been loaded. We can now check its contents with:
{{Code|code=
{{Code|code=
dir(exercise)
dir(exercise)
}}
}}
The command dir() is a built-in python command that lists the contents of a module. We can see that our line() class is there, waiting for us. Now let's test it:
Der Befehl dir() ist ein eingebauter Python Befehl, der den Inhalt eines Moduls auflistet. Wir können sehen, dass unsere line() Klasse da ist und auf uns wartet. Nun wollen wir sie testen:
{{Code|code=
{{Code|code=
exercise.line()
exercise.line()
}}
}}
Then, click two times in the 3D view, and bingo, here is our line! To do it again, just type exercise.line() again, and again, and again... Feels great, no?
Dann klicke zwei Mal in der 3D Ansicht, und Bingo, hier ist unsere Linie! Um es wieder zu tun, tippe einfach exercise.line() wieder ein, und wieder, und wieder... Fühlt sich toll an, oder?


==Registering the script in the FreeCAD interface==
==Registrierung des Skripts in der FreeCAD Oberfläche==
Nun, damit unser neues Linienwerkzeug wirklich cool ist, sollte es einen Knopf auf der Oberfläche haben, damit wir nicht immer all dieses Zeug tippen müssen. Am einfachsten ist es, unser neues MyScripts Verzeichnis in einen vollständige FreeCAD Arbeitsbereich zu verwandeln. Es ist einfach, alles was benötigt wird ist eine Datei namens '''InitGui.py''' in deinem MyScripts Verzeichnis zu speichern. Die Datei InitGui.py enthält die Anweisungen zum Erstellen eines neuen Arbeitsbereichs und fügt unser neues Werkzeug hinzu. Außerdem müssen wir auch unseren Übungscode ein wenig umwandeln, damit das line()-Werkzeug als offizieller FreeCAD Befehl erkannt wird. Beginnen wir damit, eine InitGui.py-Datei zu erstellen, und schreiben wir den folgenden Code hinein:
Now, for our new line tool to be really cool, it should have a button on the interface, so we don't need to type all that stuff everytime. The easiest way is to transform our new MyScripts directory into a full FreeCAD workbench. It is easy, all that is needed is to put a file called '''InitGui.py''' inside your MyScripts directory. The InitGui.py will contain the instructions to create a new workbench, and add our new tool to it. Besides that we will also need to transform a bit our exercise code, so the line() tool is recognized as an official FreeCAD command. Let's start by making an InitGui.py file, and write the following code in it:
{{Code|code=
{{Code|code=
class MyWorkbench (Workbench):
class MyWorkbench (Workbench):
Line 131: Line 132:
Gui.addWorkbench(MyWorkbench())
Gui.addWorkbench(MyWorkbench())
}}
}}
Mittlerweile solltest Du das obige Skript schon selbst verstehen, Ich denke: Wir erstellen eine neue Klasse, die wir MyWorkbench nennen, wir geben ihr einen Titel (MenuText) und wir definieren eine Initialize() Funktion, die ausgeführt wird, wenn der Arbeitsbereich in FreeCAD geladen wird. In dieser Funktion laden wir den Inhalt unserer Übungsdatei ein und hängen die darin enthaltenen FreeCAD Befehle an eine Befehlsliste an. Dann erstellen wir eine Werkzeugleiste namens "Meine Skripte" und weisen ihr unsere Befehlsliste zu. Derzeit haben wir natürlich nur ein Werkzeug, so dass unsere Befehlsliste nur ein Element enthält. Sobald unser Arbeitsbereich fertig ist, fügen wir es zur Hauptoberfläche hinzu.
By now, you should already understand the above script by yourself, I think: We create a new class that we call MyWorkbench, we give it a title (MenuText), and we define an Initialize() function that will be executed when the workbench is loaded into FreeCAD. In that function, we load in the contents of our exercise file, and append the FreeCAD commands found inside to a command list. Then, we make a toolbar called "My Scripts" and we assign our commands list to it. Currently, of course, we have only one tool, so our command list contains only one element. Then, once our workbench is ready, we add it to the main interface.


But this still won't work, because a FreeCAD command must be formatted in a certain way to work. So we will need to transform a bit our line() tool. Our new exercise.py script will now look like this:
Aber das wird trotzdem nicht funktionieren, da ein FreeCAD Befehl auf eine bestimmte Art und Weise formatiert werden muss, um zu funktionieren. Also müssen wir unser line() Werkzeug ein wenig umwandeln. Unser neues exercise.py Skript wird nun so aussehen:
{{Code|code=
{{Code|code=
import FreeCADGui, Part
import FreeCADGui, Part
Line 151: Line 152:
self.stack.append(point)
self.stack.append(point)
if len(self.stack) == 2:
if len(self.stack) == 2:
l = Part.Line(self.stack[0],self.stack[1])
l = Part.LineSegment(self.stack[0],self.stack[1])
shape = l.toShape()
shape = l.toShape()
Part.show(shape)
Part.show(shape)
Line 159: Line 160:
FreeCADGui.addCommand('line', line())
FreeCADGui.addCommand('line', line())
}}
}}
What we did here is transform our __init__() function into an Activated() function, because when FreeCAD commands are run, they automatically execute the Activated() function. We also added a GetResources() function, that informs FreeCAD where it can find an icon for the tool, and what will be the name and tooltip of our tool. Any jpg, png or svg image will work as an icon, it can be any size, but it is best to use a size that is close to the final aspect, like 16x16, 24x24 or 32x32.
Was wir hier gemacht haben, ist die Umwandlung unserer __init__() Funktion in eine Activated() Funktion, denn wenn FreeCAD Befehle ausgeführt werden, führen sie automatisch die Activated() Funktion aus. Wir haben auch eine GetResources() Funktion hinzugefügt, die FreeCAD darüber informiert, wo es ein Icon für das Werkzeug finden kann und wie der Name und der Tooltip unseres Werkzeugs lauten wird. Jedes jpg-, png- oder svg Bild wird als Symbol funktionieren, es kann jede beliebige Größe haben, aber es ist am besten, eine Größe zu verwenden, die dem fertigen Seitenverhältnis nahe kommt, wie 16x16, 24x24 oder 32x32.
Then, we add the line() class as an official FreeCAD command with the addCommand() method.
Dann fügen wir die line() Klasse als offiziellen FreeCAD Befehl mit der addCommand() Methode hinzu.


Das war's, jetzt müssen wir nur noch FreeCAD neu starten und schon haben wir einen schönen neuen Arbeitsbereich mit unserem brandneuen Linienwerkzeug!
That's it, we now just need to restart FreeCAD and we'll have a nice new workbench with our brand new line tool!


==So you want more?==
==Also willst du mehr? ==


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
If you liked this exercise, why not try to improve this little tool? There are many things that can be done, like for example:
* Benutzer Rückmeldungen hinzufügen: Bis jetzt haben wir ein sehr nacktes Tool gemacht, der Benutzer könnte etwas verloren sein, wenn er es benutzt. Wir könnten also eine Rückmeldung hinzufügen, das ihm sagt, was er als nächstes tun soll. Zum Beispiel könntest Du Meldungen an die FreeCAD Konsole ausgeben. Schaue dir das FreeCAD.Console Modul an
* Add user feedback: until now we did a very bare tool, the user might be a bit lost when using it. So we could add some feedback, telling him what to do next. For example, you could issue messages to the FreeCAD console. Have a look in the FreeCAD.Console module
* Add a possibility to type the 3D points coordinates manually. Look at the python input() function, for example
* Füge die Möglichkeit hinzu, die Koordinaten der 3D Punkte manuell einzugeben. Schaue dir die Python input() Funktion an, zum Beispiel
* Füge die Möglichkeit hinzu, mehr als 2 Punkte hinzuzufügen
* Add the possibility to add more than 2 points
* Füge Ereignisse für andere Dinge hinzu: Jetzt überprüfen wir nur noch, ob es Ereignisse für die Maustaste gibt, was wäre, wenn wir auch etwas tun würden, wenn die Maus bewegt wird, wie z.B. die aktuellen Koordinaten anzeigen?
* Add events for other things: Now we just check for Mouse button events, what if we would also do something when the mouse is moved, like displaying current coordinates?
* Gib dem erstellten Objekt einen Namen
* Give a name to the created object
Don't hesitate to write your questions or ideas on the [http://forum.freecadweb.org/ forum]!
Zögere nicht, deine Fragen oder Ideen ins [http://forum.freecadweb.org/ Forum] zu schreiben!


{{docnav/de|[[Code snippets/de|Code-Schnipsel]]|[[Dialog creation/de|Dialogerstellung]]}}
{{docnav/de|[[Code snippets/de|Code-Schnipsel]]|[[Dialog creation/de|Dialogerstellung]]}}

Revision as of 20:54, 13 February 2020

Diese Seite zeigt, wie einfach erweiterte Funktionalität in Python gebaut werden kann. In dieser Übung werden wir ein neues Werkzeug bauen, das eine Linie zeichnet. Dieses Werkzeug kann dann mit einem FreeCAD Befehl verknüpft werden, und dieser Befehl kann von jedem Element der Schnittstelle, wie z.B. einem Menüpunkt oder einer Schaltfläche in der Werkzeugleiste, aufgerufen werden.

Das Hauptskript

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. Starte also deinen bevorzugten Texteditor und gib die folgenden 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)

Detaillierte Erklärung

import Part, FreeCADGui
from pivy.coin import *

Wenn Du in Python Funktionen aus einem anderen Modul verwenden willst, musst Du es importieren. In unserem Fall benötigen wir Funktionen aus dem Part-Modul, um die Linie zu erstellen, und aus dem Gui Modul (FreeCADGui), um auf die 3D Ansicht zuzugreifen. Außerdem benötigen wir den kompletten Inhalt der Coin Bibliothek, damit wir alle Coin Objekte wie SoMouseButtonEvent, etc. 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 wir unser Werkzeug "am Leben" halten müssen, 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 am Leben, 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) haben. Dies ist besonders in FreeCAD nützlich, denn wenn du diese Klasse im Interpreter aufrufst, wird der Beschreibungszeichenkette als Tooltip angezeigt.

def __init__(self):

Python Klassen können immer eine __init__-Funktion 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 beginnt.

self.view = FreeCADGui.ActiveDocument.ActiveView

In einer Klasse möchte man normalerweise self. vor einem Variablennamen anhängen, 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 verändern.

self.stack = []

Hier erstellen wir eine leere Liste, die die von der Getpoint Funktion gesendeten 3D Punkte enthält.

self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.getpoint)

Das ist der wichtige Teil: Da es sich eigentlich um eine coin3D Szene handelt, verwendet FreeCAD einen Coin Rückruf Mechanismus, der es erlaubt, eine Funktion jedes Mal aufzurufen, wenn ein bestimmtes Szene Ereignis eintritt. In unserem Fall erstellen wir einen Rückruf für SoMouseButtonEvent Ereignisse, und wir binden ihn an die getpoint Funktion. Nun wird jedes Mal, wenn eine Maustaste gedrückt oder losgelassen wird, die getpoint Funktion ausgeführt.

Beachte, dass es auch eine Alternative zu addEventCallbackPivy() namens addEventCallback() gibt, welche die Verwendung von pivy überflüssig macht. Aber da pivy ein sehr effizienter und natürlicher Weg ist, um auf jeden Teil der coin Szene zuzugreifen, ist es viel besser, es so oft wie möglich zu verwenden!

def getpoint(self,event_cb):

Nun definieren wir die Getpoint Funktion, die beim Drücken einer Maustaste in einer 3D Ansicht ausgeführt wird. Diese Funktion erhält ein Argument, das wir event_cb aufrufen. Von diesem Ereignisrückruf aus können wir auf das Ereignisobjekt zugreifen, das verschiedene Informationen enthält (Mode Info 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 aufnehmen, wenn er gedrückt ist (sonst würden wir zwei 3D Punkte sehr nahe aneinander bekommen). Das müssen wir also hier überprüfen.

pos = event.getPosition()

Hier erhalten wir die Bildschirmkoordinaten des Mauszeigers

point = self.view.getPoint(pos[0],pos[1])

Diese Funktion gibt uns einen FreeCAD Vektor (x,y,z), der den 3D Punkt enthält, der auf der Brennpunktebene direkt unter unserem Mauszeiger liegt. Wenn Du Dich in der Kameraansicht befindest, stelle dir einen Strahl vor, der von der Kamera kommt, durch den Mauszeiger hindurchgeht und auf die Brennpunktebene trifft. Dort ist unser 3D Punkt. 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 die Linie!

l = Part.LineSegment(self.stack[0],self.stack[1])

Hier verwenden wir die Funktion Line() aus dem Part Module/de, die eine Linie aus zwei FreeCAD Vektoren erzeugt. Alles, was wir innerhalb des Part Moduls erstellen und modifizieren, bleibt im Part Modul. Bis jetzt haben wir also ein Linien Part erstellt. Es ist nicht an ein Objekt unseres aktiven Dokuments gebunden, so dass nichts auf dem Bildschirm erscheint.

shape = l.toShape()

Das FreeCAD Dokument kann nur Formen aus dem Part Modul übernehmen. Formen sind der grundlegendste Typ des Part Moduls. Daher müssen wir unsere Linie in eine Form konvertieren, bevor wir sie dem Dokument hinzufügen.

Part.show(shape)

Das Part Modul hat eine sehr nützliche show() Funktion, 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 können.

self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.callback)

Da wir mit unserer Linie fertig sind, lasst uns den Rückrufmechanismus entfernen, der wertvolle CPU Zyklen verbraucht.

Test & Anwendung des Skripts

Nun speichern wir unser Skript an einem Ort, an dem der FreeCAD Python Interpreter es finden kann. Beim Import von Modulen wird der Interpreter an folgenden Stellen suchen: die Python Installationspfade, das FreeCAD bin Verzeichnis und alle FreeCAD Modulverzeichnisse. Die beste Lösung ist also, ein neues Verzeichnis in einem der FreeCAD Mod Verzeichnisse anzulegen und unser Skript darin zu speichern. Erstellen wir zum Beispiel ein Verzeichnis "MyScripts" und speichern unser Skript als "exercise.py".

Nun, alles ist bereit, lass uns FreeCAD starten, ein neues Dokument erstellen und im Python Interpreter eingeben:

import exercise

Wenn keine Fehlermeldung erscheint, bedeutet das, dass unser Übungsskript geladen wurde. 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 sehen, dass unsere line() Klasse da ist und auf uns wartet. Nun wollen wir sie testen:

exercise.line()

Dann klicke zwei Mal in der 3D Ansicht, und Bingo, hier ist unsere Linie! Um es wieder zu tun, tippe einfach exercise.line() wieder ein, und wieder, und wieder... Fühlt sich toll an, oder?

Registrierung des Skripts in der FreeCAD Oberfläche

Nun, damit unser neues Linienwerkzeug wirklich cool ist, sollte es einen Knopf auf der Oberfläche haben, damit wir nicht immer all dieses Zeug tippen müssen. Am einfachsten ist es, unser neues MyScripts Verzeichnis in einen vollständige FreeCAD Arbeitsbereich zu verwandeln. Es ist einfach, alles was benötigt wird ist eine Datei namens InitGui.py in deinem MyScripts Verzeichnis zu speichern. Die Datei InitGui.py enthält die Anweisungen zum Erstellen eines neuen Arbeitsbereichs und fügt unser neues Werkzeug hinzu. Außerdem müssen wir auch unseren Übungscode ein wenig umwandeln, damit das line()-Werkzeug als offizieller FreeCAD Befehl erkannt wird. Beginnen wir damit, eine InitGui.py-Datei zu erstellen, und schreiben wir den folgenden Code hinein:

class MyWorkbench (Workbench): 
   MenuText = "MyScripts"
   def Initialize(self):
       import exercise
       commandslist = ["line"]
       self.appendToolbar("My Scripts",commandslist)
Gui.addWorkbench(MyWorkbench())

Mittlerweile solltest Du das obige Skript schon selbst verstehen, Ich denke: Wir erstellen eine neue Klasse, die wir MyWorkbench nennen, wir geben ihr einen Titel (MenuText) und wir definieren eine Initialize() Funktion, die ausgeführt wird, wenn der Arbeitsbereich in FreeCAD geladen wird. In dieser Funktion laden wir den Inhalt unserer Übungsdatei ein und hängen die darin enthaltenen FreeCAD Befehle an eine Befehlsliste an. Dann erstellen wir eine Werkzeugleiste namens "Meine Skripte" und weisen ihr unsere Befehlsliste zu. Derzeit haben wir natürlich nur ein Werkzeug, so dass unsere Befehlsliste nur ein Element enthält. Sobald unser Arbeitsbereich fertig ist, fügen wir es zur Hauptoberfläche hinzu.

Aber das wird trotzdem nicht funktionieren, da ein FreeCAD Befehl auf eine bestimmte Art und Weise formatiert werden muss, um zu funktionieren. Also müssen wir unser line() Werkzeug ein wenig umwandeln. Unser neues exercise.py Skript wird 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 die Umwandlung unserer __init__() Funktion in eine Activated() Funktion, denn wenn FreeCAD Befehle ausgeführt werden, führen sie automatisch die Activated() Funktion aus. Wir haben auch eine GetResources() Funktion hinzugefügt, die FreeCAD darüber informiert, wo es ein Icon für das Werkzeug finden kann und wie der Name und der Tooltip unseres Werkzeugs lauten wird. Jedes jpg-, png- oder svg Bild wird als Symbol funktionieren, es kann jede beliebige Größe haben, aber es ist am besten, eine Größe zu verwenden, die dem fertigen Seitenverhältnis nahe kommt, wie 16x16, 24x24 oder 32x32. Dann fügen wir die line() Klasse als offiziellen FreeCAD Befehl mit der addCommand() Methode hinzu.

Das war's, jetzt müssen wir nur noch FreeCAD neu starten und schon haben wir einen schönen neuen Arbeitsbereich mit unserem brandneuen Linienwerkzeug!

Also willst du mehr?

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

  • Benutzer Rückmeldungen hinzufügen: Bis jetzt haben wir ein sehr nacktes Tool gemacht, der Benutzer könnte etwas verloren sein, wenn er es benutzt. Wir könnten also eine Rückmeldung hinzufügen, das ihm sagt, was er als nächstes tun soll. Zum Beispiel könntest Du Meldungen an die FreeCAD Konsole ausgeben. Schaue dir das FreeCAD.Console Modul an
  • Füge die Möglichkeit hinzu, die Koordinaten der 3D Punkte manuell einzugeben. Schaue dir die Python input() Funktion an, zum Beispiel
  • Füge die Möglichkeit hinzu, mehr als 2 Punkte hinzuzufügen
  • Füge Ereignisse für andere Dinge hinzu: Jetzt überprüfen wir nur noch, ob es Ereignisse für die Maustaste gibt, was wäre, wenn wir auch etwas tun würden, wenn die Maus bewegt wird, wie z.B. die aktuellen Koordinaten anzeigen?
  • Gib dem erstellten Objekt einen Namen

Zögere nicht, deine Fragen oder Ideen ins Forum zu schreiben!