Scripted objects/de: Difference between revisions

From FreeCAD Documentation
(Created page with "Sie erzeugen einfach einen SoFCSelection Knoten. Dann fügen Sie Ihre Geometrieknoten diesem hinzu und fügen dann den SoFCSelection Knoten dem Hauptknoten hinzu anstatt die G...")
No edit summary
Line 418: Line 418:
self.wireframe.addChild(selectionNode)
self.wireframe.addChild(selectionNode)
}}
}}
Sie erzeugen einfach einen SoFCSelection Knoten. Dann fügen Sie Ihre Geometrieknoten diesem hinzu und fügen dann den SoFCSelection Knoten dem Hauptknoten hinzu anstatt die Gemoetrieknoen direkt hinzuzufügen.
Sie erzeugen einfach einen SoFCSelection Knoten. Dann fügen Sie Ihre Geometrieknoten diesem hinzu und fügen dann den SoFCSelection Knoten dem Hauptknoten hinzu anstatt die Gemoetrieknoten direkt hinzuzufügen.


== Arbeiten mit einfachen Formen ==
== Arbeiten mit einfachen Formen ==

Revision as of 12:45, 2 January 2016

Außer den Standardobjektarten wie Anmerkungen, Mesh-und Teile-Objekten, bietet FreeCAD auch die erstaunliche Möglichkeit an, 100%-ige python-Skript-Objekte zu erstellen, genannt Python Features. Diese Objekte verhalten sich genau so wie jedes andere Objekt in FreeCAD und werden automatisch in Dateien gespeichert und wiederhergestellt, wenn laden / speichern ausgeführt wird.

Eine Besonderheit muss verstanden werden, jene Gegenstände werden in FreeCAD FcStd Dateien gespeichert mit python's cPickle-modul. Das Modul bildet aus einem Python-Objekt einen String, der dann einer gespeicherten Datei hinzugefügt werden kann. Beim Dateiöffnen, verwendet das cPickle Modul diese Zeichenfolge , um das ursprüngliche Objekt neu zu erstellen, vorausgesetzt, es hat den Zugriff auf den Quellcode, welcher das Objekt erstellt hat. Dies bedeutet, dass wenn Sie ein solches benutzerdefiniertes Objekt speichern und öffnen es auf einer Maschine, wo der Python-Code, der das Objekt erzeugt hat, ist nicht vorhanden, wird das Objekt nicht neu erstellt werden.

Python-Features folgen der gleichen Regeln wie alle FreeCAD Features: sie werden in App und GUI Teile getrennt. Der App Teil, das Document Object, definiert die Geometrie unseres Objektes, während der GUI-Teil, das View-Objekt, definiert, wie das Objekt auf dem Bildschirm gezeichnet wird. Das View-Objekt, ist wie jedes andere FreeCAD Feature nur verfügbar, wenn Sie FreeCAD in seiner eigenen GUI ausführen. Es gibt mehrere Eigenschaften und Methoden, die verfügbar sind, um Ihr Objekt zu erstellen. Eigenschaften muss von einem der in FreeCAD vordefinierten Typen sein, um im Eigenschafts-View-Fenster zu erscheinen, wo sie vom Benutzer editiert werden können.

Auf diese Weise sind FeaturePython-Objekte wirklich und völlig parametrisch. Sie können die Eigenschaften für den Gegenstand und sein ViewObjekt getrennt definieren.

Grundlegendes Beispiel

Das folgende Beispiel kann in der src/Mod/TemplatePyMod/FeaturePython.py-Datei gefunden werden, zusammen mit einigen anderen Beispielen:

"Examples for a feature class and its view provider."

import FreeCAD, FreeCADGui
from pivy import coin

class Box:
    def __init__(self, obj):
        "'''Add some custom properties to our box feature'''"
        obj.addProperty("App::PropertyLength","Length","Box","Length of the box").Length=1.0
        obj.addProperty("App::PropertyLength","Width","Box","Width of the box").Width=1.0
        obj.addProperty("App::PropertyLength","Height","Box", "Height of the box").Height=1.0
        obj.Proxy = self
    
    def onChanged(self, fp, prop):
        "'''Do something when a property has changed'''"
        FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n")
 
    def execute(self, fp):
        "'''Do something when doing a recomputation, this method is mandatory'''"
        FreeCAD.Console.PrintMessage("Recompute Python Box feature\n")
 
class ViewProviderBox:
    def __init__(self, obj):
        "'''Set this object to the proxy object of the actual view provider'''"
        obj.addProperty("App::PropertyColor","Color","Box","Color of the box").Color=(1.0,0.0,0.0)
        obj.Proxy = self
 
    def attach(self, obj):
        "'''Setup the scene sub-graph of the view provider, this method is mandatory'''"
        self.shaded = coin.SoGroup()
        self.wireframe = coin.SoGroup()
        self.scale = coin.SoScale()
        self.color = coin.SoBaseColor()
        
        data=coin.SoCube()
        self.shaded.addChild(self.scale)
        self.shaded.addChild(self.color)
        self.shaded.addChild(data)
        obj.addDisplayMode(self.shaded,"Shaded");
        style=coin.SoDrawStyle()
        style.style = coin.SoDrawStyle.LINES
        self.wireframe.addChild(style)
        self.wireframe.addChild(self.scale)
        self.wireframe.addChild(self.color)
        self.wireframe.addChild(data)
        obj.addDisplayMode(self.wireframe,"Wireframe");
        self.onChanged(obj,"Color")
 
    def updateData(self, fp, prop):
        "'''If a property of the handled feature has changed we have the chance to handle this here'''"
        # fp is the handled feature, prop is the name of the property that has changed
        l = fp.getPropertyByName("Length")
        w = fp.getPropertyByName("Width")
        h = fp.getPropertyByName("Height")
        self.scale.scaleFactor.setValue(l,w,h)
        pass
 
    def getDisplayModes(self,obj):
        "'''Return a list of display modes.'''"
        modes=[]
        modes.append("Shaded")
        modes.append("Wireframe")
        return modes
 
    def getDefaultDisplayMode(self):
        "'''Return the name of the default display mode. It must be defined in getDisplayModes.'''"
        return "Shaded"
 
    def setDisplayMode(self,mode):
        "'''Map the display mode defined in attach with those defined in getDisplayModes.\'''
                '''Since they have the same names nothing needs to be done. This method is optional'''"
        return mode
 
    def onChanged(self, vp, prop):
        "'''Here we can do something when a single property got changed'''"
        FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n")
        if prop == "Color":
            c = vp.getPropertyByName("Color")
            self.color.rgb.setValue(c[0],c[1],c[2])
 
    def getIcon(self):
        "'''Return the icon in XPM format which will appear in the tree view. This method is\'''
                '''optional and if not defined a default icon is shown.'''"
        return """
            /* XPM */
            static const char * ViewProviderBox_xpm[] = {
            "16 16 6 1",
            "   c None",
            ".  c #141010",
            "+  c #615BD2",
            "@  c #C39D55",
            "#  c #000000",
            "$  c #57C355",
            "        ........",
            "   ......++..+..",
            "   .@@@@.++..++.",
            "   .@@@@.++..++.",
            "   .@@  .++++++.",
            "  ..@@  .++..++.",
            "###@@@@ .++..++.",
            "##$.@@$#.++++++.",
            "#$#$.$$$........",
            "#$$#######      ",
            "#$$#$$$$$#      ",
            "#$$#$$$$$#      ",
            "#$$#$$$$$#      ",
            " #$#$$$$$#      ",
            "  ##$$$$$#      ",
            "   #######      "};
            """
 
    def __getstate__(self):
        "'''When saving the document this object gets stored using Python's json module.\'''
                '''Since we have some un-serializable parts here -- the Coin stuff -- we must define this method\'''
                '''to return a tuple of all serializable objects or None.'''"
        return None
 
    def __setstate__(self,state):
        "'''When restoring the serialized object from document we have the chance to set some internals here.\'''
                '''Since no data were serialized nothing needs to be done here.'''"
        return None
 
 
def makeBox():
    FreeCAD.newDocument()
    a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box")
    Box(a)
    ViewProviderBox(a.ViewObject)

Verfügbare Eigenschaften

Eigenschaften sind die wahren Bausteine von FeaturePython-Gegenständen. Durch ist der Benutzer im Stande, mit einem Objekt zu interagieren und es zu ändern. Nach dem Erstellen eines neuen FeaturePython-Objekts in Ihrem Dokument( a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box") ), können eine Liste der verfügbaren Eigenschaften bekommen, indem Sie folgendes eingeben:

obj.supportedProperties()

Sie werden eine Liste von verfügbaren Eigenschaften bekommen mit:

App::PropertyBool
App::PropertyBoolList
App::PropertyFloat
App::PropertyFloatList
App::PropertyFloatConstraint
App::PropertyQuantity
App::PropertyQuantityConstraint
App::PropertyAngle
App::PropertyDistance
App::PropertyLength
App::PropertySpeed
App::PropertyAcceleration
App::PropertyForce
App::PropertyPressure
App::PropertyInteger
App::PropertyIntegerConstraint
App::PropertyPercent
App::PropertyEnumeration
App::PropertyIntegerList
App::PropertyIntegerSet
App::PropertyMap
App::PropertyString
App::PropertyUUID
App::PropertyFont
App::PropertyStringList
App::PropertyLink
App::PropertyLinkSub
App::PropertyLinkList
App::PropertyLinkSubList
App::PropertyMatrix
App::PropertyVector
App::PropertyVectorList
App::PropertyPlacement
App::PropertyPlacementLink
App::PropertyColor
App::PropertyColorList
App::PropertyMaterial
App::PropertyPath
App::PropertyFile
App::PropertyFileIncluded
App::PropertyPythonObject
Part::PropertyPartShape
Part::PropertyGeometryList
Part::PropertyShapeHistory
Part::PropertyFilletEdges
Sketcher::PropertyConstraintList

Beim Hinzufügen von Eigenschaften zu benutzerdefinierten Objekte, achten Sie bitte auf folgendes:

  • Verwenden Sie keine Zeichen "<" oder ">" in den Eigenschaftes-Beschreibungen (das würde den XML-Teil in der .Fcstd-Datei zerbrechen)
  • Eigenschaften werden alphabetisch in einer .fcstd Datei gespeichert. Wenn Sie eine Form("Shape") in Ihren Eigenschaften haben, wird jede Eigenschaft, deren Name in alphabetischen Reihenfolge nach "Shape" kommt, auch nach der Form geladen, was zu seltsamen Verhaltensweisen führen kann.

Andere komplexere Beispiele

Dieses Beispiel macht von Part Module Gebrauch, um ein Oktaeder zu schaffen, erstellt dann seine coin-Darstellung mit pivy. Zuerst erstellen wir das Document-Objekt selbst:

obj.setEditorMode("MyPropertyName", mode)

wobei der mode als short int gesetzt werden kann als:

0 -- Standardmodus, Lesen und Schreiben
1 -- Nur-Lesen
2 -- Versteckt

Andere komplexere Beispiele

Dieses Beispiel macht von Part Module Gebrauch, um ein Oktaeder zu schaffen, erstellt dann seine coin-Darstellung mit pivy. Zuerst erstellen wir das Document-Objekt selbst:

Dann haben wir das Darstellungs-Objekt,

import FreeCAD, FreeCADGui, Part

   class Octahedron:
      def __init__(self, obj):
         "Add some custom properties to our box feature"
         obj.addProperty("App::PropertyLength","Length","Octahedron","Length of the octahedron").Length=1.0
         obj.addProperty("App::PropertyLength","Width","Octahedron","Width of the octahedron").Width=1.0
         obj.addProperty("App::PropertyLength","Height","Octahedron", "Height of the octahedron").Height=1.0
         obj.addProperty("Part::PropertyPartShape","Shape","Octahedron", "Shape of the octahedron")
         obj.Proxy = self

      def execute(self, fp):
         # Define six vetices for the shape
         v1 = FreeCAD.Vector(0,0,0)
         v2 = FreeCAD.Vector(fp.Length,0,0)
         v3 = FreeCAD.Vector(0,fp.Width,0)
         v4 = FreeCAD.Vector(fp.Length,fp.Width,0)
         v5 = FreeCAD.Vector(fp.Length/2,fp.Width/2,fp.Height/2)
         v6 = FreeCAD.Vector(fp.Length/2,fp.Width/2,-fp.Height/2)
         
         # Make the wires/faces
         f1 = self.make_face(v1,v2,v5)
         f2 = self.make_face(v2,v4,v5)
         f3 = self.make_face(v4,v3,v5)
         f4 = self.make_face(v3,v1,v5)
         f5 = self.make_face(v2,v1,v6)
         f6 = self.make_face(v4,v2,v6)
         f7 = self.make_face(v3,v4,v6)
         f8 = self.make_face(v1,v3,v6)
         shell=Part.makeShell([f1,f2,f3,f4,f5,f6,f7,f8])
         solid=Part.makeSolid(shell)
         fp.Shape = solid

      # helper mehod to create the faces
      def make_face(self,v1,v2,v3):
         wire = Part.makePolygon([v1,v2,v3,v1])
         face = Part.Face(wire)
         return face

verantwortlich für die Ansicht des Objekts in der 3D-Szene:

class ViewProviderOctahedron:
      def __init__(self, obj):
         "Set this object to the proxy object of the actual view provider"
         obj.addProperty("App::PropertyColor","Color","Octahedron","Color of the octahedron").Color=(1.0,0.0,0.0)
         obj.Proxy = self

      def attach(self, obj):
         "Setup the scene sub-graph of the view provider, this method is mandatory"
         self.shaded = coin.SoGroup()
         self.wireframe = coin.SoGroup()
         self.scale = coin.SoScale()
         self.color = coin.SoBaseColor()

         self.data=coin.SoCoordinate3()
         self.face=coin.SoIndexedLineSet()

         self.shaded.addChild(self.scale)
         self.shaded.addChild(self.color)
         self.shaded.addChild(self.data)
         self.shaded.addChild(self.face)
         obj.addDisplayMode(self.shaded,"Shaded");
         style=coin.SoDrawStyle()
         style.style = coin.SoDrawStyle.LINES
         self.wireframe.addChild(style)
         self.wireframe.addChild(self.scale)
         self.wireframe.addChild(self.color)
         self.wireframe.addChild(self.data)
         self.wireframe.addChild(self.face)
         obj.addDisplayMode(self.wireframe,"Wireframe");
         self.onChanged(obj,"Color")

      def updateData(self, fp, prop):
         "If a property of the handled feature has changed we have the chance to handle this here"
         # fp is the handled feature, prop is the name of the property that has changed
         if prop == "Shape":
            s = fp.getPropertyByName("Shape")
            self.data.point.setNum(6)
            cnt=0
            for i in s.Vertexes:
               self.data.point.set1Value(cnt,i.X,i.Y,i.Z)
               cnt=cnt+1
            
            self.face.coordIndex.set1Value(0,0)
            self.face.coordIndex.set1Value(1,1)
            self.face.coordIndex.set1Value(2,2)
            self.face.coordIndex.set1Value(3,-1)

            self.face.coordIndex.set1Value(4,1)
            self.face.coordIndex.set1Value(5,3)
            self.face.coordIndex.set1Value(6,2)
            self.face.coordIndex.set1Value(7,-1)

            self.face.coordIndex.set1Value(8,3)
            self.face.coordIndex.set1Value(9,4)
            self.face.coordIndex.set1Value(10,2)
            self.face.coordIndex.set1Value(11,-1)

            self.face.coordIndex.set1Value(12,4)
            self.face.coordIndex.set1Value(13,0)
            self.face.coordIndex.set1Value(14,2)
            self.face.coordIndex.set1Value(15,-1)

            self.face.coordIndex.set1Value(16,1)
            self.face.coordIndex.set1Value(17,0)
            self.face.coordIndex.set1Value(18,5)
            self.face.coordIndex.set1Value(19,-1)

            self.face.coordIndex.set1Value(20,3)
            self.face.coordIndex.set1Value(21,1)
            self.face.coordIndex.set1Value(22,5)
            self.face.coordIndex.set1Value(23,-1)

            self.face.coordIndex.set1Value(24,4)
            self.face.coordIndex.set1Value(25,3)
            self.face.coordIndex.set1Value(26,5)
            self.face.coordIndex.set1Value(27,-1)

            self.face.coordIndex.set1Value(28,0)
            self.face.coordIndex.set1Value(29,4)
            self.face.coordIndex.set1Value(30,5)
            self.face.coordIndex.set1Value(31,-1)

      def getDisplayModes(self,obj):
         "Return a list of display modes."
         modes=[]
         modes.append("Shaded")
         modes.append("Wireframe")
         return modes

      def getDefaultDisplayMode(self):
         "Return the name of the default display mode. It must be defined in getDisplayModes."
         return "Shaded"

      def setDisplayMode(self,mode):
         return mode

      def onChanged(self, vp, prop):
         "Here we can do something when a single property got changed"
         FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n")
         if prop == "Color":
            c = vp.getPropertyByName("Color")
            self.color.rgb.setValue(c[0],c[1],c[2])

      def getIcon(self):
         return """
            /* XPM */
            static const char * ViewProviderBox_xpm[] = {
            "16 16 6 1",
            "    c None",
            ".   c #141010",
            "+   c #615BD2",
            "@   c #C39D55",
            "#   c #000000",
            "$   c #57C355",
            "        ........",
            "   ......++..+..",
            "   .@@@@.++..++.",
            "   .@@@@.++..++.",
            "   .@@  .++++++.",
            "  ..@@  .++..++.",
            "###@@@@ .++..++.",
            "##$.@@$#.++++++.",
            "#$#$.$$$........",
            "#$$#######      ",
            "#$$#$$$$$#      ",
            "#$$#$$$$$#      ",
            "#$$#$$$$$#      ",
            " #$#$$$$$#      ",
            "  ##$$$$$#      ",
            "   #######      "};
            """

      def __getstate__(self):
         return None

      def __setstate__(self,state):
         return None

Schließlich, sobald unser Objekt und sein Darstellungs-Objekt definiert sind, müssen wir sie nur noch aufrufen:

FreeCAD.newDocument()
      a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Octahedron")
      Octahedron(a)
      ViewProviderOctahedron(a.ViewObject)

Objekte wählbar machen

Wollen Sie Ihr Objekt wählbar machen, oder zumindest ein Teil davon, indem Sie im Editor darauf klicken, müssen Sie seine coin-Geometrie in einen SoFCSelection-Knoten enibinden. Verfügt Ihr Objekt über komplexe Darstellung, mit Widgets, Anmerkungen, etc., möchten Sie vielleicht nur einen Teil davon in einem SoFCSelection einschliessen. Alles, was ein SoFCSelection ist, wird ständig durch FreeCAD gescannt, um eine Auswahl/Vorwahl zu entdecken, der Sinn dabei ist, zu versuchen, es nicht mit unnötigen Abtastungen zu überlasten. Folgendes würden Sie tun, um einen self.face vom Beispiel oben einzuschließen:

selectionNode = coin.SoType.fromName("SoFCSelection").createInstance()
selectionNode.documentName.setValue(FreeCAD.ActiveDocument.Name)
selectionNode.objectName.setValue(obj.Object.Name) # here obj is the ViewObject, we need its associated App Object
selectionNode.subElementName.setValue("Face")
selectNode.addChild(self.face)
...
self.shaded.addChild(selectionNode)
self.wireframe.addChild(selectionNode)

Sie erzeugen einfach einen SoFCSelection Knoten. Dann fügen Sie Ihre Geometrieknoten diesem hinzu und fügen dann den SoFCSelection Knoten dem Hauptknoten hinzu anstatt die Gemoetrieknoten direkt hinzuzufügen.

Arbeiten mit einfachen Formen

Erstellen Sie einfach einen SoFCSelection Knoten, dann fügen Sie Ihre Geometrie-Knoten dazu hinzu, dann fügen Sie alles zu Ihrem Hauptknoten hinzu, anstatt Ihre Geometrie-Knoten direkt einzufügen. Die Form wird mittels der FreeCAD Standard-Form-Darstellung angezeigt:

class Line:
    def __init__(self, obj):
        '''"App two point properties" '''
        obj.addProperty("App::PropertyVector","p1","Line","Start point")
        obj.addProperty("App::PropertyVector","p2","Line","End point").p2=FreeCAD.Vector(1,0,0)
        obj.Proxy = self

    def execute(self, fp):
        '''"Print a short message when doing a recomputation, this method is mandatory" '''
        fp.Shape = Part.makeLine(fp.p1,fp.p2)

a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line")
Line(a)
a.ViewObject.Proxy=0 # just set it to something different from None (this assignment is needed to run an internal notification)
FreeCAD.ActiveDocument.recompute()

Same code with use ViewProviderLine

import FreeCAD as App
import FreeCADGui
import FreeCAD
import Part

class Line:
    def __init__(self, obj):
         '''"App two point properties" '''
         obj.addProperty("App::PropertyVector","p1","Line","Start point")
         obj.addProperty("App::PropertyVector","p2","Line","End point").p2=FreeCAD.Vector(100,0,0)
         obj.Proxy = self
   
    def execute(self, fp):
        '''"Print a short message when doing a recomputation, this method is mandatory" '''
        fp.Shape = Part.makeLine(fp.p1,fp.p2)

class ViewProviderLine:
   def __init__(self, obj):
      ''' Set this object to the proxy object of the actual view provider '''
      obj.Proxy = self

   def getDefaultDisplayMode(self):
      ''' Return the name of the default display mode. It must be defined in getDisplayModes. '''
      return "Flat Lines"

a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line")
Line(a)
ViewProviderLine(a.ViewObject)
App.ActiveDocument.recompute()
PySide
Embedding FreeCAD