FreeCAD - Scripting 06 - Komut Dosyasıyla Yazılmış Nesneler

Komut Dosyasıyla Yazılmış Nesneler

Takdim

Açıklamalar, ağlar ve parça nesneleri gibi standart nesne türlerinin yanı sıra, FreeCAD ayrıca Python Özellikleri adı verilen %100 python komut dosyasıyla oluşturulmuş parametrik nesneler oluşturmak için harika bir olanak sunar. Bu nesneler, tam olarak diğer herhangi bir FreeCAD nesnesi gibi davranacak ve dosya kaydetme/yükleme sırasında otomatik olarak kaydedilecek ve geri yüklenecektir.

Anlaşılması, bilinmesi gereken bir husus var, o da şudur: FreeCAD dosyaları hiçbir zaman gömülü kod taşımaz, bu güvenlik nedenleriyledir. Parametrik nesneler oluşturmak için yazdığınız Python kodu, hiçbir zaman bir dosyanın içine kaydedilmez. Bu, başka bir makinede (bilgisayarda) böyle bir nesne içeren bir dosyayı açarsanız, bu python kodu o makinede (bilgisayarda) mevcut değilse, nesnenin tamamen yeniden oluşturulmayacağı anlamına gelir. Bu tür nesneleri başkalarına dağıtırsanız (gönderirseniz), Python komut dosyanızı da örneğin bir Makro olarak dağıtmanız, göndermeniz gerekir.

Not: Python kodunu bir App::PropertyPythonObject ile jsonserileştirmeyi kullanarak bir FreeCAD dosyası içinde paketlemek (yerleştirmek) mümkündür, ancak bu kod hiçbir zaman doğrudan çalıştırılamaz ve bu nedenle buradaki amacımız için çok kullanışlı değildir.

Python Özellikleri, tüm FreeCAD özellikleriyle aynı kuralı izler: Appve GUIbölümlerine ayrılırlar. App bölümü, Belge Nesnesi, nesnemizin geometrisini tanımlarken, GUI bölümü, Görünüm Sağlayıcı Nesnesi, nesnenin ekranda nasıl çizileceğini tanımlar. Görünüm Sağlayıcı Nesnesi, diğer FreeCAD özellikleri gibi, yalnızca FreeCAD'i kendi GUI'sinde çalıştırdığınızda kullanılabilir. Nesnenizi oluşturmak için kullanılabilecek birkaç özellik ve yöntem vardır. Özellikler, FreeCAD'in sunduğu önceden tanımlanmış özellik türlerinden herhangi biri olmalıdır ve kullanıcı tarafından düzenlenebilmeleri için özellik görünümü penceresinde görünecektir. Bu şekilde, FeaturePython nesneleri gerçekten ve tamamen parametriktir. Object ve ViewObject için özellikleri ayrı ayrı tanımlayabilirsiniz.

Basit / Temel Örnek

Aşağıdaki örnek, diğer birkaç örnekle birlikte src/Mod/TemplatePyMod/FeaturePython.py dosyasında bulunabilir:

"""
Examples for a feature class and its view provider.
(c) 2009 Werner Mayer LGPL
"""

__author__ = "Werner Mayer <wmayer@users.sourceforge.net>"

import FreeCAD, Part, math
from FreeCAD import Base
from pivy import coin

class PartFeature:
    def __init__(self, obj):
        obj.Proxy = self

class Box(PartFeature):
    def __init__(self, obj):
        PartFeature.__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

    def onChanged(self, fp, prop):
        ''' Print the name of the property that has changed '''
        FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n")

    def execute(self, fp):
        ''' Print a short message when doing a recomputation, this method is mandatory '''
        FreeCAD.Console.PrintMessage("Recompute Python Box feature\n")
        fp.Shape = Part.makeBox(fp.Length,fp.Width,fp.Height)

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

    def attach(self, obj):
        ''' Setup the scene sub-graph of the view provider, this method is mandatory '''
        return

    def updateData(self, fp, prop):
        ''' If a property of the handled feature has changed we have the chance to handle this here '''
        return

    def getDisplayModes(self,obj):
        ''' Return a list of display modes. '''
        modes=[]
        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):
        ''' Print the name of the property that has changed '''
        FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n")

    def getIcon(self):
        ''' Return the icon in XMP 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 cPickle module.
        Since we have some un-pickable here -- the Coin stuff -- we must define this method
        to return a tuple of all pickable objects or None.
        '''
        return None

    def __setstate__(self,state):
        ''' When restoring the pickled object from document we have the chance to set some
        internals here. Since no data were pickled nothing needs to be done here.
        '''
        return None


def makeBox():
    doc=FreeCAD.newDocument()
    a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Box")
    Box(a)
    ViewProviderBox(a.ViewObject)
    doc.recompute()

# -----------------------------------------------------------------------------

class Line:
    def __init__(self, obj):
        ''' Add 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)

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"

def makeLine():
    doc=FreeCAD.newDocument()
    a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line")
    Line(a)
    #ViewProviderLine(a.ViewObject)
    a.ViewObject.Proxy=0 # just set it to something different from None
    doc.recompute()

# -----------------------------------------------------------------------------

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(v2,v1,v5)
        f2 = self.make_face(v4,v2,v5)
        f3 = self.make_face(v3,v4,v5)
        f4 = self.make_face(v1,v3,v5)
        f5 = self.make_face(v1,v2,v6)
        f6 = self.make_face(v2,v4,v6)
        f7 = self.make_face(v4,v3,v6)
        f8 = self.make_face(v3,v1,v6)
        shell=Part.makeShell([f1,f2,f3,f4,f5,f6,f7,f8])
        solid=Part.makeSolid(shell)
        fp.Shape = solid
    # helper method to create the faces
    def make_face(self,v1,v2,v3):
        wire = Part.makePolygon([v1,v2,v3,v1])
        face = Part.Face(wire)
        return face

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.color = coin.SoBaseColor()

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

        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.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,2)
            self.face.coordIndex.set1Value(2,1)
            self.face.coordIndex.set1Value(3,-1)

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

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

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

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

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

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

            self.face.coordIndex.set1Value(28,4)
            self.face.coordIndex.set1Value(29,5)
            self.face.coordIndex.set1Value(30,1)
            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

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

# -----------------------------------------------------------------------------

class PointFeature:
    def __init__(self, obj):
        obj.Proxy = self

    def onChanged(self, fp, prop):
        ''' Print the name of the property that has changed '''
        return

    def execute(self, fp):
        ''' Print a short message when doing a recomputation, this method is mandatory '''
        return

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

    def attach(self, obj):
        ''' Setup the scene sub-graph of the view provider, this method is mandatory '''
        return

    def updateData(self, fp, prop):
        ''' If a property of the handled feature has changed we have the chance to handle this here '''
        return

    def getDisplayModes(self,obj):
        ''' Return a list of display modes. '''
        modes=[]
        return modes

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

    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):
        ''' Print the name of the property that has changed '''
        return

    def getIcon(self):
        ''' Return the icon in XMP 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 cPickle module.
        Since we have some un-pickable here -- the Coin stuff -- we must define this method
        to return a tuple of all pickable objects or None.
        '''
        return None

    def __setstate__(self,state):
        ''' When restoring the pickled object from document we have the chance to set some
        internals here. Since no data were pickled nothing needs to be done here.
        '''
        return None


def makePoints():
    doc=FreeCAD.newDocument()
    import Mesh
    m=Mesh.createSphere(5.0).Points
    import Points
    p=Points.Points()

    l=[]
    for s in m:
        l.append(s.Vector)

    p.addPoints(l)


    a=FreeCAD.ActiveDocument.addObject("Points::FeaturePython","Points")
    a.Points=p
    PointFeature(a)
    ViewProviderPoints(a.ViewObject)
    doc.recompute()

# -----------------------------------------------------------------------------

class MeshFeature:
    def __init__(self, obj):
        obj.Proxy = self

    def onChanged(self, fp, prop):
        ''' Print the name of the property that has changed '''
        return

    def execute(self, fp):
        ''' Print a short message when doing a recomputation, this method is mandatory '''
        return

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

    def attach(self, obj):
        ''' Setup the scene sub-graph of the view provider, this method is mandatory '''
        return

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

    def getIcon(self):
        ''' Return the icon in XMP 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 cPickle module.
        Since we have some un-pickable here -- the Coin stuff -- we must define this method
        to return a tuple of all pickable objects or None.
        '''
        return None

    def __setstate__(self,state):
        ''' When restoring the pickled object from document we have the chance to set some
        internals here. Since no data were pickled nothing needs to be done here.
        '''
        return None


def makeMesh():
    doc=FreeCAD.newDocument()
    import Mesh

    a=FreeCAD.ActiveDocument.addObject("Mesh::FeaturePython","Mesh")
    a.Mesh=Mesh.createSphere(5.0)
    MeshFeature(a)
    ViewProviderMesh(a.ViewObject)
    doc.recompute()

# -----------------------------------------------------------------------------

class Molecule:
    def __init__(self, obj):
        ''' Add two point properties '''
        obj.addProperty("App::PropertyVector","p1","Line","Start point")
        obj.addProperty("App::PropertyVector","p2","Line","End point").p2=FreeCAD.Vector(5,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 ViewProviderMolecule:
    def __init__(self, obj):
        ''' Set this object to the proxy object of the actual view provider '''
        sep1=coin.SoSeparator()
        self.trl1=coin.SoTranslation()
        sep1.addChild(self.trl1)
        sep1.addChild(coin.SoSphere())
        sep2=coin.SoSeparator()
        self.trl2=coin.SoTranslation()
        sep2.addChild(self.trl2)
        sep2.addChild(coin.SoSphere())
        obj.RootNode.addChild(sep1)
        obj.RootNode.addChild(sep2)
        # triggers an updateData call so the assignment at the end
        obj.Proxy = self

    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 == "p1":
            p = fp.getPropertyByName("p1")
            self.trl1.translation=(p.x,p.y,p.z)
        elif prop == "p2":
            p = fp.getPropertyByName("p2")
            self.trl2.translation=(p.x,p.y,p.z)

    def __getstate__(self):
        return None

    def __setstate__(self,state):
        return None

def makeMolecule():
    doc=FreeCAD.newDocument()
    a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Molecule")
    Molecule(a)
    ViewProviderMolecule(a.ViewObject)
    doc.recompute()

# -----------------------------------------------------------------------------

class CircleSet:
    def __init__(self, obj):
        obj.addProperty("Part::PropertyPartShape","Shape","Circle","Shape")
        obj.Proxy = self

    def execute(self, fp):
        pass


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

    def attach(self, obj):
        self.coords=coin.SoCoordinate3()
        self.lines=coin.SoLineSet()
        obj.RootNode.addChild(self.coords)
        obj.RootNode.addChild(self.lines)

    def updateData(self, fp, prop):
            if prop == "Shape":
                edges = fp.getPropertyByName("Shape").Edges
                pts=[]
                ver=[]
                for i in edges:
                    length=i.Length
                    ver.append(10)
                    for j in range(10):
                        v=i.valueAt(j/9.0*length)
                        pts.append((v.x,v.y,v.z))

                self.coords.point.setValues(pts)
                self.lines.numVertices.setValues(ver)

    def __getstate__(self):
        return None

    def __setstate__(self,state):
        return None

def makeCircleSet():
    x=0.5
    comp=Part.Compound([])
    for j in range (630):
        y=0.5
        for i in range (630):
            c = Part.makeCircle(0.1, Base.Vector(x,y,0), Base.Vector(0,0,1))
            #Part.show(c)
            comp.add(c)
            y=y+0.5
        x=x+0.5

    doc=FreeCAD.newDocument()
    a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Circles")
    CircleSet(a)
    ViewProviderCircleSet(a.ViewObject)
    a.Shape=comp
    doc.recompute()

# -----------------------------------------------------------------------------

class EnumTest:
    def __init__(self, obj):
        ''' Add enum properties '''
        obj.addProperty("App::PropertyEnumeration","Enum","","Enumeration").Enum=["One","Two","Three"]
        obj.addProperty("App::PropertyEnumeration","Enum2","","Enumeration2").Enum2=["One","Two","Three"]
        obj.Proxy = self

    def execute(self, fp):
        return

class ViewProviderEnumTest:
    def __init__(self, obj):
        ''' Set this object to the proxy object of the actual view provider '''
        obj.addProperty("App::PropertyEnumeration","Enum3","","Enumeration3").Enum3=["One","Two","Three"]
        obj.addProperty("App::PropertyEnumeration","Enum4","","Enumeration4").Enum4=["One","Two","Three"]
        obj.Proxy = self

    def updateData(self, fp, prop):
        print("prop updated:",prop)

    def __getstate__(self):
        return None

    def __setstate__(self,state):
        return None

def makeEnumTest():
    FreeCAD.newDocument()
    a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Enum")
    EnumTest(a)
    ViewProviderEnumTest(a.ViewObject)

# -----------------------------------------------------------------------------

class DistanceBolt:
    def __init__(self, obj):
        ''' Add the properties: Length, Edges, Radius, Height '''
        obj.addProperty("App::PropertyInteger","Edges","Bolt","Number of edges of the outline").Edges=6
        obj.addProperty("App::PropertyLength","Length","Bolt","Length of the edges of the outline").Length=10.0
        obj.addProperty("App::PropertyLength","Radius","Bolt","Radius of the inner circle").Radius=4.0
        obj.addProperty("App::PropertyLength","Height","Bolt","Height of the extrusion").Height=20.0
        obj.Proxy = self

    def onChanged(self, fp, prop):
        if prop == "Edges" or prop == "Length" or prop == "Radius" or prop == "Height":
            self.execute(fp)

    def execute(self, fp):
        edges = fp.Edges
        if edges < 3:
            edges = 3
        length = fp.Length
        radius = fp.Radius
        height = fp.Height

        m=Base.Matrix()
        m.rotateZ(math.radians(360.0/edges))

        # create polygon
        polygon = []
        v=Base.Vector(length,0,0)
        for i in range(edges):
            polygon.append(v)
            v = m.multiply(v)
        polygon.append(v)
        wire = Part.makePolygon(polygon)

        # create circle
        circ=Part.makeCircle(radius)

        # Create the face with the polygon as outline and the circle as hole
        face=Part.Face([wire,Part.Wire(circ)])

        # Extrude in z to create the final solid
        extrude=face.extrude(Base.Vector(0,0,height))
        fp.Shape = extrude

def makeDistanceBolt():
    doc=FreeCAD.newDocument()
    bolt=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Distance_Bolt")
    bolt.Label = "Distance bolt"
    DistanceBolt(bolt)
    bolt.ViewObject.Proxy=0
    doc.recompute()

Dikkat Edilmesi Gerekenler

Nesneniz oluşturulur oluşturulmaz yeniden hesaplanması gerekiyorsa, otomatik olarak çağrılmadığı için bunu __init__ işlevinde manuel olarak yapmanız gerekir. Boxsınıfının onChanged yöntemi, execute (çalıştır) fonksiyonuyla aynı etkiye sahip olduğu için bu örnek buna gerek duymaz, ancak aşağıdaki örnekler, 3B görünümde herhangi bir şey görüntülenmeden önce yeniden hesaplanma mantığına dayanır. Örneklerde bu, ActiveDocument.recompute() ile manuel olarak yapılır, ancak daha karmaşık senaryolarda, tüm belgenin veya FeaturePython nesnesinin nerede yeniden hesaplanacağına karar vermeniz gerekir.

Bu örnek, rapor görünümü penceresinde bir dizi istisna yığını izi üretir. Bunun nedeni, __init__ içine her özellik eklendiğinde Box sınıfının onChanged yönteminin çağrılmasıdır. İlki eklendiğinde, Genişlik ve Yükseklik özellikleri henüz mevcut değildir ve bu nedenle bunlara erişme girişimi başarısız olur.

__getstate__ ve __setstate__ için bir açıklama obj.Proxy.Type is a dict, not a string forum başlığında bulunmaktadır.

Mevcut Yöntemler / Metotlar

PythonÖzelliği Yöntemleri tüm referans kaynak için sayfaya bakın.

Kullanılabilir Özellikler

Özellikler, FeaturePython nesnelerinin gerçek yapı taşlarıdır. Bunlar aracılığıyla, kullanıcı etkileşimde bulunabilecek ve nesnenizi değiştirebilecektir. Belgenizde yeni bir FeaturePython nesnesi oluşturduktan sonra;

(obj=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box")), aşağıdaki kodu yazarak, kullanılabilir özelliklerin bir listesini elde edebilirsiniz:

obj.supportedProperties()

FeaturePython Özel Özellikler sayfasında daha ayrıntılı olarak açıklanan mevcut özelliklerin bir listesini elde edeceksiniz:

Özel nesnelerinize özellikler eklerken şunlara dikkat edin:

Özellik özniteliklerinin tam listesi PropertyStandard C++ başlık dosyasında görülebilir. Örneğin, kullanıcının yalnız sınırlı bir değer aralığı girmesine izin vermek istiyorsanız (örneğin PropertyIntegerConstraint kullanarak), Python'da yalnız özellik değerini değil, aynı zamanda alt ve üst sınırı ve adım boyutunu da içeren bir tanımlama grubu atayacaksınız, aşağıda olduğu gibi:

prop = (value, lower, upper, stepsize)

Özellik Türü

Varsayılan olarak özellikler güncellenebilir. Örneğin bir yöntemin, sonucunu göstermek istemesi durumunda, özellikleri salt okunur yapmak mümkündür. Ayrıca özelliği gizlemek de mümkündür. Özellik türü aşağıdakiler kullanılarak ayarlanabilir:

obj.setEditorMode("MyPropertyName", mode)

mod, şu şekilde ayarlanabilen kısa bir tamsayıdır (int):

0 -- varsayılan mod, okuma ve yazma (default mode, read and write)
1 -- Salt okunur (read-only)
2 -- gizli (hidden)

EditörModları, FreeCAD dosyasının yeniden yüklenmesine ayarlanmamıştır. Bu, __setstate__ fonksiyonu tarafından yapılabilir. Eigenmode frequency always 0? - Page 2 - FreeCAD Forum adresine bakın. setEditorMode kullanılarak, özellikler yalnızca PropertyEditor'de okunur. Hala python'dan değiştirilebilirler. Bunları gerçekten okumalarını sağlamak için, ayarın doğrudan addProperty fonksiyonunun içine iletilmesi gerekir. Örnek için Eigenmode frequency always 0? - Page 3 - FreeCAD Forum adresine bakın.

AddProperty fonksiyonundaki doğrudan ayarı kullanarak, daha fazla olanağınız da olur. Özellikle, bir özelliği çıktı özelliği olarak işaretlemek ilginçtir. Bu şekilde FreeCAD, özelliği değiştirirken dokunulduğunda işaretlemeyecektir (böylece yeniden hesaplamaya gerek yoktur).

Çıktı özelliği örneği (ayrıca bkz. [Solved] Skip excution for some properties - FreeCAD Forum):

obj.addProperty("App::PropertyString","MyCustomProperty","","",8)

addProperty işlevinin son parametresinde ayarlanabilen özellik türleri şunlardır:

0 -- Prop_None, Özel bir özellik türü yok
1 -- Prop_ReadOnly, Özellik düzenleyicide salt okunurdur
2 -- Prop_Transient, Özellik dosyaya kaydedilmeyecek
4 -- Prop_Hidden, Özellik düzenleyicide görünmeyecek
8 -- Prop_Output, Değiştirilmiş özellik, üst kapsayıcısına (konteyner) dokunmuyor
16 -- Prop_NoRecompute, Değiştirilmiş özellik, yeniden hesaplama için kapsayıcısına (konteyner) dokunmuyor

PropertyContainer için C++ kaynak kodu başlığında tanımlanan bu farklı özellik türlerini bulabilirsiniz.

Diğer Daha Karmaşık Örnek

Bu örnek, bir oktahedron (sekizyüzlü / sekiz yüzeyli katı bir şekil) oluşturmak için Part modülünü kullanır, ardından pivy ile onun coin gösterimini oluşturur.

Kodun birinci bölümü, Document nesnesinin kendisidir:

import FreeCAD, FreeCADGui, Part
import pivy
from pivy import coin

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

Ardından, nesneyi 3B sahnesinde göstermekten sorumlu olan görünüm sağlayıcı nesnesine sahip kodları yazıyoruz:

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

Son olarak, nesnemiz ve onun viewobject'i tanımlandıktan sonra, onları çağırmamız (komutu çalıştırmamız) yeterlidir (Octahedron sınıfı ve viewprovider sınıf kodu doğrudan FreeCAD python konsolunda kopyalanabilir):

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

Nesneleri Seçilebilir Yapma

Eğer nesnenizi veya en azından bir kısmını görünüm alanında tıklayarak seçilebilir yapmak istiyorsanız, onun coin geometrisini bir SoFCSelection düğümüne (node) dahil etmeniz gerekir. Nesnenizin widget'lar, açıklamalar vb. ile karmaşık bir temsili varsa, bunun yalnızca bir bölümünü SoFCSelection'a dahil etmek isteyebilirsiniz. SoFCSelection olan her şey, seçimi/ön seçimi algılamak için FreeCAD tarafından sürekli olarak taranır, bu nedenle gereksiz tarama ile aşırı yüklemeden kaçınmak mantıklıdır.

Sahne grafiğinin seçilebilir olan kısımları SoFCSelection düğümlerinin içinde olduğunda, seçim yolunu işlemek için iki yöntem sağlamanız gerekir. Seçim yolu, yoldaki her bir öğenin veya bir dizi sahne grafiği nesnesinin adlarını veren bir dize (string) biçimini alabilir. Sağladığınız iki yöntem, bir dize yolundan bir dizi sahne grafiği nesnesine dönüştüren getDetailPath ve sahne grafiğinde tıklanan bir öğeyi alan ve dize adını döndüren getElementPicked'dir (dize yolunu değil, not edin).

Molekülün öğelerini seçilebilir hale getirmek için uyarlanmış yukarıdaki molekül örneği:

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

        obj.Proxy = self

    def onChanged(self, fp, prop):
        if prop == "p1" or prop == "p2":
            ''' Print the name of the property that has changed '''
            fp.Shape = Part.makeLine(fp.p1,fp.p2)

    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 ViewProviderMolecule:
    def __init__(self, obj):
        ''' Set this object to the proxy object of the actual view provider '''
        obj.Proxy = self
        self.ViewObject = obj
        sep1=coin.SoSeparator()
        sel1 = coin.SoType.fromName('SoFCSelection').createInstance()
        # sel1.policy.setValue(coin.SoSelection.SHIFT)
        sel1.ref()
        sep1.addChild(sel1)
        self.trl1=coin.SoTranslation()
        sel1.addChild(self.trl1)
        sel1.addChild(coin.SoSphere())
        sep2=coin.SoSeparator()
        sel2 = coin.SoType.fromName('SoFCSelection').createInstance()
        sel2.ref()
        sep2.addChild(sel2)
        self.trl2=coin.SoTranslation()
        sel2.addChild(self.trl2)
        sel2.addChild(coin.SoSphere())
        obj.RootNode.addChild(sep1)
        obj.RootNode.addChild(sep2)
        self.updateData(obj.Object, 'p2')
        self.sel1 = sel1
        self.sel2 = sel2

    def getDetailPath(self, subname, path, append):
        vobj = self.ViewObject
        if append:
            path.append(vobj.RootNode)
            path.append(vobj.SwitchNode)

            mode = vobj.SwitchNode.whichChild.getValue()
            if mode >= 0:
                mode = vobj.SwitchNode.getChild(mode)
                path.append(mode)
                sub = Part.splitSubname(subname)[-1]
                if sub == 'Atom1':
                    path.append(self.sel1)
                elif sub == 'Atom2':
                    path.append(self.sel2)
                else:
                    path.append(mode.getChild(0))
        return True

    def getElementPicked(self, pp):
        path = pp.getPath()
        if path.findNode(self.sel1) >= 0:
            return 'Atom1'
        if path.findNode(self.sel2) >= 0:
            return 'Atom2'
        raise NotImplementedError

    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 == "p1":
            p = fp.getPropertyByName("p1")
            self.trl1.translation=(p.x,p.y,p.z)
        elif prop == "p2":
            p = fp.getPropertyByName("p2")
            self.trl2.translation=(p.x,p.y,p.z)

    def __getstate__(self):
        return None

    def __setstate__(self,state):
        return None

def makeMolecule():
    FreeCAD.newDocument()
    a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Molecule")
    Molecule(a)
    ViewProviderMolecule(a.ViewObject)
    FreeCAD.ActiveDocument.recompute()

Basit Şekillerle Çalışmak

Parametrik nesneniz basitçe bir şekil veriyorsa, bir görünüm sağlayıcı nesnesi kullanmanıza gerek yoktur. Şekil, FreeCAD'in standart şekil gösterimi kullanılarak görüntülenecektir:

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(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()

ViewProviderLine kullanımı ile aynı kod

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()

Sahne Yapısı

Yukarıdaki örneklerin sahne grafiklerini biraz farklı şekillerde oluşturduğunu fark etmiş olabilirsiniz. Bazıları obj.addDisplayMode(node, "modename") kullanırken diğerleri obj.SwitchNode.getChild(x).addChild(y) kullanır.

Bir FreeCAD belgesindeki her özellik aşağıdaki sahne grafiği yapısını temel alır:

RootNode
 \- SwitchNode
     \- Shaded
      - Wireframe
      - etc

SwitchNode, FreeCAD'de hangi görüntüleme modunun seçildiğine bağlı olarak, alt öğelerinden yalnızca birini görüntüler.

addDisplayMode kullanan örnekler, sahne grafiklerini yalnızca coin3d sahne grafiği öğelerinden oluşturuyor. Kapakların altında, addDisplayMode, SwitchNode'a yeni bir alt öğe ekler; o düğümün adı, geçtiği görüntüleme moduyla eşleşir.

SwitchNode.getChild(x).addChild kullanan örnekler ayrıca, fp.Shape = Part.makeLine(fp.p1,fp.p2) gibi Part (Parça) tezgahındaki işlevleri kullanarak geometrilerinin bir bölümünü oluşturur. Bu, SwitchNode altında farklı görüntüleme modu sahne grafiklerini oluşturur; Daha sonra sahne grafiğine coin3d öğeleri eklemeye geldiğimizde, bunları SwitchNode'un yeni bir alt öğesini oluşturmak yerine addChild kullanarak mevcut görüntüleme modu sahne grafiklerine eklememiz gerekir.

Sahne grafiğine geometri eklemek için addDisplayMode() kullanılırken, her görüntüleme modunun addDisplayMode() öğesine geçirilen kendi düğümü olmalıdır; bunun için aynı düğümü tekrar kullanmayın. Bunu yapmak, seçim mekanizmasını karıştıracaktır. Her görüntüleme modunun düğümünün altına aynı geometri düğümleri eklenmişse sorun değil, yalnızca her görüntüleme modunun kökünün farklı olması gerekir.

Part (Parça) tezgahından nesneler kullanmak yerine yalnızca Coin3D sahne grafiği nesneleriyle çizilmek üzere uyarlanmış yukarıdaki molekül örneği:

import Part
from pivy import coin

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

        obj.Proxy = self

    def onChanged(self, fp, prop):
        pass

    def execute(self, fp):
        ''' Print a short message when doing a recomputation, this method is mandatory '''
        pass

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

    def attach(self, obj):
        material = coin.SoMaterial()
        material.diffuseColor = (1.0, 0.0, 0.0)
        material.emissiveColor = (1.0, 0.0, 0.0)
        drawStyle = coin.SoDrawStyle()
        drawStyle.pointSize.setValue(10)
        drawStyle.style = coin.SoDrawStyle.LINES
        wireframe = coin.SoGroup()
        shaded = coin.SoGroup()
        self.wireframe = wireframe
        self.shaded = shaded

        self.coords = coin.SoCoordinate3()
        self.coords.point.setValues(0, 2, [FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(1, 0, 0)])
        wireframe += self.coords
        wireframe += drawStyle
        wireframe += material
        shaded += self.coords
        shaded += drawStyle
        shaded += material

        g = coin.SoGroup()
        sel1 = coin.SoType.fromName('SoFCSelection').createInstance()
        sel1.style = 'EMISSIVE_DIFFUSE'
        p1 = coin.SoType.fromName('SoIndexedPointSet').createInstance()
        p1.coordIndex.set1Value(0, 0)
        sel1 += p1
        g += sel1
        wireframe += g
        shaded += g

        g = coin.SoGroup()
        sel2 = coin.SoType.fromName('SoFCSelection').createInstance()
        sel2.style = 'EMISSIVE_DIFFUSE'
        p2 = coin.SoType.fromName('SoIndexedPointSet').createInstance()
        p2.coordIndex.set1Value(0, 1)
        sel2 += p2
        g += sel2
        wireframe += g
        shaded += g

        g = coin.SoGroup()
        sel3 = coin.SoType.fromName('SoFCSelection').createInstance()
        sel3.style = 'EMISSIVE_DIFFUSE'
        p3 = coin.SoType.fromName('SoIndexedLineSet').createInstance()
        p3.coordIndex.setValues(0, 2, [0, 1])
        sel3 += p3
        g += sel3
        wireframe += g
        shaded += g

        obj.addDisplayMode(wireframe, 'Wireframe')
        obj.addDisplayMode(shaded, 'Shaded')

        self.sel1 = sel1
        self.sel2 = sel2
        self.sel3 = sel3
        self.constructed = True
        self.updateData(obj.Object, 'p2')

    def getDetailPath(self, subname, path, append):
        vobj = self.ViewObject
        if append:
            path.append(vobj.RootNode)
            path.append(vobj.SwitchNode)

            mode = vobj.SwitchNode.whichChild.getValue()
            FreeCAD.Console.PrintWarning("getDetailPath: mode {} is active\n".format(mode))
            if mode >= 0:
                mode = vobj.SwitchNode.getChild(mode)
                path.append(mode)
                sub = Part.splitSubname(subname)[-1]
                print(sub)
                if sub == 'Atom1':
                    path.append(self.sel1)
                elif sub == 'Atom2':
                    path.append(self.sel2)
                elif sub == 'Line':
                    path.append(self.sel3)
                else:
                    path.append(mode.getChild(0))
        return True

    def getElementPicked(self, pp):
        path = pp.getPath()
        if path.findNode(self.sel1) >= 0:
            return 'Atom1'
        if path.findNode(self.sel2) >= 0:
            return 'Atom2'
        if path.findNode(self.sel3) >= 0:
            return 'Line'
        raise NotImplementedError

    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 not self.constructed:
            return
        if prop == "p1":
            p = fp.getPropertyByName("p1")
            self.coords.point.set1Value(0, p)
        elif prop == "p2":
            p = fp.getPropertyByName("p2")
            self.coords.point.set1Value(1, p)

    def getDisplayModes(self, obj):
        return ['Wireframe', 'Shaded']

    def getDefaultDisplayMode(self):
        return 'Shaded'

    def setDisplayMode(self, mode):
        return mode

    def __getstate__(self):
        return None

    def __setstate__(self,state):
        return None

def makeMolecule():
    FreeCAD.newDocument()
    a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Molecule")
    Molecule(a)
    b=ViewProviderMolecule(a.ViewObject)
    a.touch()
    FreeCAD.ActiveDocument.recompute()
    return a,b

a,b = makeMolecule()

ParçaTasarımı (Part Design) Komut Dosyasıyla Yazılmış Nesneler

Parça Tasarımında kodlanmış nesneler oluştururken, süreç yukarıda tartışılan komut dosyası oluşturulmuş nesnelere benzer, ancak birkaç ek husus vardır. Biri 3B görünümde gördüğümüz şekil için, diğeri ise polar desen özellikleri gibi desen araçları tarafından kullanılan şekil için olmak üzere 2 şekil özelliğini ele almalıyız. Nesne şekillerinin ayrıca, halihazırda Gövdede bulunan herhangi bir mevcut malzemeyle kaynaştırılması (veya Çıkarma özellikleri durumunda ondan kesilmesi) gerekir. Nesnelerimizin yerleştirilmesini ve bağlanmasını biraz farklı hesaba katmalıyız.

Parça Tasarımı komut dosyasıyla yazılmış katı nesne özellikleri, Part::FeaturePython yerine PartDesign::FeaturePython, PartDesign::FeatureAdditivePython veya PartDesign::FeatureSubtractivePython'a dayanmalıdır. Çoğaltma özelliklerinde yalnız Toplama ve Çıkarma varyantları kullanılabilir ve Part::FeaturePython'a dayalıysa, kullanıcı nesneyi bir Parça Tasarım Gövdesine bıraktığında, Gövde tarafından yerel bir Parça Tasarım nesnesi olarak ele alınmak yerine bir BaseFeature olur. Not: bunların hepsinin katı olması beklenir, bu nedenle katı olmayan bir özellik yapıyorsanız bunun Part::FeaturePython'a dayanması gerekir, yoksa ağaçtaki bir sonraki özellik katı olarak birleşmeye çalışır ve başarısız olur .

Burada, Parça Çalışma Tezgahındaki (Part Workbench'teki) Boru (Tube) temel şekline (primitifine) benzer bir Boru temel şekil oluşturmanın basit bir örneği verilmiştir, ancak bu bir Parça Tasarımı (Part Design) katı özellik nesnesi olacaktır. Bunun için 2 ayrı dosya oluşturacağız: pdtube.FCMacro ve pdtube.py. .FCMacro dosyası, nesneyi oluşturmak için kullanıcı tarafından çalıştırılacaktır. .py dosyası, .FCMacro tarafından içe aktarılan sınıf tanımlarını tutacaktır. Bunu bu şekilde yapmanın nedeni, FreeCAD'i yeniden başlattıktan ve Borularımızdan birini içeren bir belgeyi açtıktan sonra nesnenin parametrik yapısını korumaktır.

İlk olarak, sınıf tanım dosyasını oluşturuyoruz:

# -*- coding: utf-8 -*-
#classes should go in pdtube.py
import FreeCAD, FreeCADGui, Part
class PDTube:
    def __init__(self,obj):
        obj.addProperty("App::PropertyLength","Radius1","Tube","Radius1").Radius1 = 5
        obj.addProperty("App::PropertyLength","Radius2","Tube","Radius2").Radius2 = 10
        obj.addProperty("App::PropertyLength","Height","Tube","Height of tube").Height = 10
        self.makeAttachable(obj)
        obj.Proxy = self

    def makeAttachable(self, obj):

        if int(FreeCAD.Version()[1]) >= 19:
            obj.addExtension('Part::AttachExtensionPython')
        else:
            obj.addExtension('Part::AttachExtensionPython', obj)

        obj.setEditorMode('Placement', 0) #non-readonly non-hidden

    def execute(self,fp):
        outer_cylinder = Part.makeCylinder(fp.Radius2, fp.Height)
        inner_cylinder = Part.makeCylinder(fp.Radius1, fp.Height)
        if fp.Radius1 == fp.Radius2: #just make cylinder
            tube_shape = outer_cylinder
        elif fp.Radius1 < fp.Radius2:
            tube_shape = outer_cylinder.cut(inner_cylinder)
        else: #invert rather than error out
            tube_shape = inner_cylinder.cut(outer_cylinder)

        if not hasattr(fp, "positionBySupport"):
            self.makeAttachable(fp)
        fp.positionBySupport()
        tube_shape.Placement = fp.Placement

        #BaseFeature (shape property of type Part::PropertyPartShape) is provided for us
        #with the PartDesign::FeaturePython and related classes, but it might be empty
        #if our object is the first object in the tree.  it's a good idea to check
        #for its existence in case we want to make type Part::FeaturePython, which won't have it

        if hasattr(fp, "BaseFeature") and fp.BaseFeature != None:
            if "Subtractive" in fp.TypeId:
                full_shape = fp.BaseFeature.Shape.cut(tube_shape)
            else:
                full_shape = fp.BaseFeature.Shape.fuse(tube_shape)
            full_shape.transformShape(fp.Placement.inverse().toMatrix(), True) #borrowed from gears workbench
            fp.Shape = full_shape
        else:
            fp.Shape = tube_shape
        if hasattr(fp,"AddSubShape"): #PartDesign::FeatureAdditivePython and
                                      #PartDesign::FeatureSubtractivePython have this
                                      #property but PartDesign::FeaturePython does not
                                      #It is the shape used for copying in pattern features
                                      #for example in making a polar pattern
            tube_shape.transformShape(fp.Placement.inverse().toMatrix(), True)
            fp.AddSubShape = tube_shape

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

    def attach(self,vobj):
        self.vobj = vobj

    def updateData(self, fp, prop):
        '''If a property of the handled feature has changed we have the chance to handle this here'''
        pass

    def getDisplayModes(self,obj):
        '''Return a list of display modes.'''
        modes=[]
        modes.append("Flat Lines")
        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 "Flat Lines"

    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")
        pass

    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

Ve şimdi nesneyi oluşturmak için makro dosyası içeriğini yazıyoruz:

# -*- coding: utf-8 -*-

#pdtube.FCMacro
import pdtube
#above line needed if the class definitions above are place in another file: PDTube.py
#this is needed if the tube object is to remain parametric after restarting FreeCAD and loading
#a document containing the object

body = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("pdbody")
if not body:
    FreeCAD.Console.PrintError("No active body.\n")
else:
    from PySide import QtGui
    window = FreeCADGui.getMainWindow()
    items = ["Additive","Subtractive","Neither additive nor subtractive"]
    item,ok =QtGui.QInputDialog.getItem(window,"Select tube type","Select whether you want additive, subtractive, or neither:",items,0,False)
    if ok:
        if item == items[0]:
            className = "PartDesign::FeatureAdditivePython"
        elif item == items[1]:
            className = "PartDesign::FeatureSubtractivePython"
        else:
            className = "PartDesign::FeaturePython" #not usable in pattern features, such as polar pattern

        tube = FreeCAD.ActiveDocument.addObject(className,"Tube")
        pdtube.PDTube(tube)
        pdtube.PDTubeVP(tube.ViewObject)
        body.addObject(tube) #optionally we can also use body.insertObject() for placing at particular place in tree

Daha Fazla Bilgi için

Ek Sayfalar:

Komut dosyasıyla yazılmış nesneler hakkında ilginç forum konuları:

Burada sunulan örneklere ek olarak, daha fazla örnek için FreeCAD kaynak kodu src/Mod/TemplatePyMod/FeaturePython.py'ye bakın.

Kaynak: Scripted objects - FreeCAD Documentation

← Önceki Bölüm