Page 1 of 2

Parametric attachment of scripted object

Posted: Sun Oct 08, 2017 2:55 pm
by clarkitect
Hi,

I have created my own python feature (code and macro to execute below) to create a very simple 4 sided cabinet. I want to use Part > Attachment to control its placement parametrically linked to other objects. I see in this thread https://forum.freecadweb.org/viewtopic.php?f=10&t=18978 that there is Part::AttachExtension. Can I add this to my code somehow to do this?

At the moment I can workaround this by cloning the object, as clones are parametrically attachable.

Thanks
Ben

Carcass.py

Code: Select all

class CarcassShape:

 def __init__(self,obj):
   obj.Proxy = self
   obj.addProperty("App::PropertyFloat","thickness")
   obj.addProperty("App::PropertyFloat","width")
   obj.addProperty("App::PropertyFloat","height")
   obj.addProperty("App::PropertyFloat","depth")

 def execute(self,obj):
   # we need to import the FreeCAD module here too, because we might be running out of the Console
   # (in a macro, for example) where the FreeCAD module has not been imported automatically
   import Part,FreeCAD

   # first we need to make sure the values of properties are not 0
   # otherwise the Part.Line will complain that both points are equal
   if (obj.thickness == 0) or (obj.width == 0) or (obj.height == 0) or (obj.depth == 0):
     # if yes, exit this method without doing anything
     return

   #make the vertices
   v1 = FreeCAD.Vector(0,0,0)
   v2 = FreeCAD.Vector(0,obj.depth,0)
   v3 = FreeCAD.Vector(obj.thickness,obj.depth,0)
   v4 = FreeCAD.Vector(obj.thickness,0,0)
   v5 = FreeCAD.Vector(0,0,obj.height)
   v6 = FreeCAD.Vector(0,obj.depth,obj.height)
   v7 = FreeCAD.Vector(obj.thickness,obj.depth,obj.height)
   v8 = FreeCAD.Vector(obj.thickness,0,obj.height)

   v9 = FreeCAD.Vector(obj.width-obj.thickness,0,0)
   v10 = FreeCAD.Vector(obj.width-obj.thickness,obj.depth,0)
   v11 = FreeCAD.Vector(obj.width,obj.depth,0)
   v12 = FreeCAD.Vector(obj.width,0,0)
   v13 = FreeCAD.Vector(obj.width-obj.thickness,0,obj.height)
   v14 = FreeCAD.Vector(obj.width-obj.thickness,obj.depth,obj.height)
   v15 = FreeCAD.Vector(obj.width,obj.depth,obj.height)
   v16 = FreeCAD.Vector(obj.width,0,obj.height)

   v17 = FreeCAD.Vector(obj.thickness,obj.depth,obj.thickness)
   v18 = FreeCAD.Vector(obj.thickness,0,obj.thickness)
   v19 = FreeCAD.Vector(obj.width-obj.thickness,0,obj.thickness)
   v20 = FreeCAD.Vector(obj.width-obj.thickness,obj.depth,obj.thickness)

   v21 = FreeCAD.Vector(obj.thickness,obj.depth,obj.height-obj.thickness)
   v22 = FreeCAD.Vector(obj.thickness,0,obj.height-obj.thickness)
   v23 = FreeCAD.Vector(obj.width-obj.thickness,0,obj.height-obj.thickness)
   v24 = FreeCAD.Vector(obj.width-obj.thickness,obj.depth,obj.height-obj.thickness)

   # Make the edges
   w1 = Part.makePolygon([v1,v2,v3,v4,v1])
   w2 = Part.makePolygon([v1,v5,v6,v2,v1])
   w3 = Part.makePolygon([v4,v8,v7,v3,v4])
   w4 = Part.makePolygon([v1,v5,v8,v4,v1])
   w5 = Part.makePolygon([v2,v6,v7,v3,v2])
   w6 = Part.makePolygon([v5,v6,v7,v8,v5])

   w7 = Part.makePolygon([v9,v10,v11,v12,v9])
   w8 = Part.makePolygon([v13,v14,v15,v16,v13])
   w9 = Part.makePolygon([v9,v13,v14,v10,v9])
   w10 = Part.makePolygon([v10,v14,v15,v11,v10])
   w11 = Part.makePolygon([v12,v16,v15,v11,v12])
   w12 = Part.makePolygon([v9,v13,v16,v12,v9])

   w13 = Part.makePolygon([v3,v4,v9,v10,v3])
   w14 = Part.makePolygon([v17,v18,v19,v20,v17])
   w15 = Part.makePolygon([v4,v18,v17,v3,v4])
   w16 = Part.makePolygon([v3,v17,v20,v10,v3])
   w17 = Part.makePolygon([v10,v20,v19,v9,v10])
   w18 = Part.makePolygon([v4,v18,v19,v9,v4])

   w19 = Part.makePolygon([v21,v22,v23,v24,v21])
   w20 = Part.makePolygon([v7,v8,v13,v14,v7])
   w21 = Part.makePolygon([v22,v8,v7,v21,v22])
   w22 = Part.makePolygon([v21,v7,v14,v24,v21])
   w23 = Part.makePolygon([v14,v24,v23,v13,v14])
   w24 = Part.makePolygon([v23,v13,v8,v22,v23])

#make the faces

   f1 = Part.Face(w1)
   f2 = Part.Face(w2)
   f3 = Part.Face(w3)
   f4 = Part.Face(w4)
   f5 = Part.Face(w5)
   f6 = Part.Face(w6)

   f7 = Part.Face(w7)
   f8 = Part.Face(w8)
   f9 = Part.Face(w9)
   f10 = Part.Face(w10)
   f11 = Part.Face(w11)
   f12 = Part.Face(w12)

   f13 = Part.Face(w13)
   f14 = Part.Face(w14)
   f15 = Part.Face(w15)
   f16 = Part.Face(w16)
   f17 = Part.Face(w17)
   f18 = Part.Face(w18)

   f19 = Part.Face(w19)
   f20 = Part.Face(w20)
   f21 = Part.Face(w21)
   f22 = Part.Face(w22)
   f23 = Part.Face(w23)
   f24 = Part.Face(w24)

#make the shells
   shell1=Part.makeShell([f1,f2,f3,f4,f5,f6])
   shell2=Part.makeShell([f7,f8,f9,f10,f11,f12])
   shell3=Part.makeShell([f13,f14,f15,f16,f17,f18])
   shell4=Part.makeShell([f19,f20,f21,f22,f23,f24])

#make the solids
   leftcheek=Part.makeSolid(shell1)
   rightcheek=Part.makeSolid(shell2)
   base=Part.makeSolid(shell3)
   top=Part.makeSolid(shell4)

#make compound from solids
   comp1 = Part.makeCompound([leftcheek,rightcheek,base,top])

   # All shapes have a Placement too. We give our shape the value of the placement
   # set by the user. This will move/rotate the face automatically.
   comp1.Placement = obj.Placement

   # all done, we can attribute our shape to the object!
   obj.Shape = comp1
Add Carcass.FCMacro

Code: Select all

import Carcass 
myObj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Carcass")
Carcass.CarcassShape(myObj)
myObj.ViewObject.Proxy = 0 # this is mandatory unless we code the ViewProvider too
FreeCAD.ActiveDocument.recompute()
OS: Ubuntu 16.04.3 LTS
Word size of OS: 32-bit
Word size of FreeCAD: 32-bit
Version: 0.17.12323 (Git)
Build type: None
Branch: master
Hash: 30379bc1ce309fcc1e0f780378babfd4104bee4c
Python version: 2.7.12
Qt version: 4.8.7
Coin version: 4.0.0a
OCC version: 7.1.0
Locale: English/UnitedKingdom (en_GB)

Re: Parametric attachment of scripted object

Posted: Sun Oct 08, 2017 3:52 pm
by ickby
you can add the extension in the constructor, wight where you add the properties:

Code: Select all

obj.addExtension('Part::AttachExtensionPython')
Note: Did not test it, so there might be some slight syntax error.

Re: Parametric attachment of scripted object

Posted: Sun Oct 08, 2017 4:48 pm
by clarkitect
Hi ickby, thanks for response. Tried a few different permutations. I'm getting

Code: Select all

Traceback (most recent call last):
  File "/home/ben/.FreeCAD/Add Carcass.FCMacro", line 3, in <module>
    Carcass.CarcassShape(myObj)
  File "/home/ben/.FreeCAD/Carcass.py", line 9, in __init__
    obj.addExtension("Part::AttachExtensionPython")
<type 'exceptions.TypeError'>: function takes exactly 2 arguments (1 given)
Any ideas? I also tried

Code: Select all

    obj.addExtension("Part::AttachExtension")
and

Code: Select all

   obj.addExtension("Part::AttachableObjectPython")
and with single quote marks...

Re: Parametric attachment of scripted object

Posted: Sun Oct 08, 2017 5:24 pm
by clarkitect
ickby wrote: Sun Oct 08, 2017 3:52 pm
Ok making progress :) this doesn't throw up a syntax error...

Code: Select all

   obj.addExtension("Part::AttachExtensionPython","obj")
But if I change the position of the first object, the attached object won't update unless I click on the detailed attachment submenu.
What do you think could be causing this?

Re: Parametric attachment of scripted object

Posted: Mon Oct 09, 2017 12:52 pm
by ickby
Ah forgot the second paramter... it is the proxy object, which is used to override the extensions functions. In your case I think you should do:

Code: Select all

   obj.addExtension("Part::AttachExtensionPython", obj)
(without the quotes ob obj)

Normally to recompute the placement of the attached object you need to call document.recompute(). Did you do that?

Re: Parametric attachment of scripted object

Posted: Tue Oct 10, 2017 8:51 am
by clarkitect
ickby wrote: Mon Oct 09, 2017 12:52 pm
Hi, unfortunately recompute has no effect. But part primitives or clones do not need to be recomputed when their fixing point is changed, so would not expect that to be necessary anyway... Going back into the attachment submenu is the only thing that works. Could it be something to do with view provider not updating? Thanks
B

Re: Parametric attachment of scripted object

Posted: Tue Oct 10, 2017 12:22 pm
by ickby
Ok. Unfortunately I can't dig in the code right now, so I'm going on guessing: It could be that you need to explicitly request the placement calculation. So in your objects execute method, try to call "positionBySupport" of the object to be positioned. This function was added to the object when adding the extension.

Re: Parametric attachment of scripted object

Posted: Tue Oct 10, 2017 8:28 pm
by clarkitect
ickby wrote: Tue Oct 10, 2017 12:22 pm I'm going on guessing
Great guess! ;) I added

Code: Select all

   obj.positionBySupport()
and that works exactly as desired.

For those who also want to make their scripted objects parametrically attachable, here is the complete definition:

Code: Select all

class CarcassShape:

 def __init__(self,obj):
   obj.Proxy = self
   obj.addProperty("App::PropertyFloat","thickness")
   obj.addProperty("App::PropertyFloat","width")
   obj.addProperty("App::PropertyFloat","height")
   obj.addProperty("App::PropertyFloat","depth")
   obj.addExtension("Part::AttachExtensionPython", obj)

 def execute(self,obj):
   # we need to import the FreeCAD module here too, because we might be running out of the Console
   # (in a macro, for example) where the FreeCAD module has not been imported automatically
   import Part,FreeCAD
   obj.positionBySupport()

   # first we need to make sure the values of properties are not 0
   # otherwise the Part.Line will complain that both points are equal
   if (obj.thickness == 0) or (obj.width == 0) or (obj.height == 0) or (obj.depth == 0):
     # if yes, exit this method without doing anything
     return

   #make the vertices
   v1 = FreeCAD.Vector(0,0,0)
   v2 = FreeCAD.Vector(0,obj.depth,0)
   v3 = FreeCAD.Vector(obj.thickness,obj.depth,0)
   v4 = FreeCAD.Vector(obj.thickness,0,0)
   v5 = FreeCAD.Vector(0,0,obj.height)
   v6 = FreeCAD.Vector(0,obj.depth,obj.height)
   v7 = FreeCAD.Vector(obj.thickness,obj.depth,obj.height)
   v8 = FreeCAD.Vector(obj.thickness,0,obj.height)

   v9 = FreeCAD.Vector(obj.width-obj.thickness,0,0)
   v10 = FreeCAD.Vector(obj.width-obj.thickness,obj.depth,0)
   v11 = FreeCAD.Vector(obj.width,obj.depth,0)
   v12 = FreeCAD.Vector(obj.width,0,0)
   v13 = FreeCAD.Vector(obj.width-obj.thickness,0,obj.height)
   v14 = FreeCAD.Vector(obj.width-obj.thickness,obj.depth,obj.height)
   v15 = FreeCAD.Vector(obj.width,obj.depth,obj.height)
   v16 = FreeCAD.Vector(obj.width,0,obj.height)

   v17 = FreeCAD.Vector(obj.thickness,obj.depth,obj.thickness)
   v18 = FreeCAD.Vector(obj.thickness,0,obj.thickness)
   v19 = FreeCAD.Vector(obj.width-obj.thickness,0,obj.thickness)
   v20 = FreeCAD.Vector(obj.width-obj.thickness,obj.depth,obj.thickness)

   v21 = FreeCAD.Vector(obj.thickness,obj.depth,obj.height-obj.thickness)
   v22 = FreeCAD.Vector(obj.thickness,0,obj.height-obj.thickness)
   v23 = FreeCAD.Vector(obj.width-obj.thickness,0,obj.height-obj.thickness)
   v24 = FreeCAD.Vector(obj.width-obj.thickness,obj.depth,obj.height-obj.thickness)

   # Make the edges
   w1 = Part.makePolygon([v1,v2,v3,v4,v1])
   w2 = Part.makePolygon([v1,v5,v6,v2,v1])
   w3 = Part.makePolygon([v4,v8,v7,v3,v4])
   w4 = Part.makePolygon([v1,v5,v8,v4,v1])
   w5 = Part.makePolygon([v2,v6,v7,v3,v2])
   w6 = Part.makePolygon([v5,v6,v7,v8,v5])

   w7 = Part.makePolygon([v9,v10,v11,v12,v9])
   w8 = Part.makePolygon([v13,v14,v15,v16,v13])
   w9 = Part.makePolygon([v9,v13,v14,v10,v9])
   w10 = Part.makePolygon([v10,v14,v15,v11,v10])
   w11 = Part.makePolygon([v12,v16,v15,v11,v12])
   w12 = Part.makePolygon([v9,v13,v16,v12,v9])

   w13 = Part.makePolygon([v3,v4,v9,v10,v3])
   w14 = Part.makePolygon([v17,v18,v19,v20,v17])
   w15 = Part.makePolygon([v4,v18,v17,v3,v4])
   w16 = Part.makePolygon([v3,v17,v20,v10,v3])
   w17 = Part.makePolygon([v10,v20,v19,v9,v10])
   w18 = Part.makePolygon([v4,v18,v19,v9,v4])

   w19 = Part.makePolygon([v21,v22,v23,v24,v21])
   w20 = Part.makePolygon([v7,v8,v13,v14,v7])
   w21 = Part.makePolygon([v22,v8,v7,v21,v22])
   w22 = Part.makePolygon([v21,v7,v14,v24,v21])
   w23 = Part.makePolygon([v14,v24,v23,v13,v14])
   w24 = Part.makePolygon([v23,v13,v8,v22,v23])

#make the faces

   f1 = Part.Face(w1)
   f2 = Part.Face(w2)
   f3 = Part.Face(w3)
   f4 = Part.Face(w4)
   f5 = Part.Face(w5)
   f6 = Part.Face(w6)

   f7 = Part.Face(w7)
   f8 = Part.Face(w8)
   f9 = Part.Face(w9)
   f10 = Part.Face(w10)
   f11 = Part.Face(w11)
   f12 = Part.Face(w12)

   f13 = Part.Face(w13)
   f14 = Part.Face(w14)
   f15 = Part.Face(w15)
   f16 = Part.Face(w16)
   f17 = Part.Face(w17)
   f18 = Part.Face(w18)

   f19 = Part.Face(w19)
   f20 = Part.Face(w20)
   f21 = Part.Face(w21)
   f22 = Part.Face(w22)
   f23 = Part.Face(w23)
   f24 = Part.Face(w24)

#make the shells
   shell1=Part.makeShell([f1,f2,f3,f4,f5,f6])
   shell2=Part.makeShell([f7,f8,f9,f10,f11,f12])
   shell3=Part.makeShell([f13,f14,f15,f16,f17,f18])
   shell4=Part.makeShell([f19,f20,f21,f22,f23,f24])

#make the solids
   leftcheek=Part.makeSolid(shell1)
   rightcheek=Part.makeSolid(shell2)
   base=Part.makeSolid(shell3)
   top=Part.makeSolid(shell4)

#make compound from solids
   comp1 = Part.makeCompound([leftcheek,rightcheek,base,top])

   # All shapes have a Placement too. We give our shape the value of the placement
   # set by the user. This will move/rotate the face automatically.
   comp1.Placement = obj.Placement

   # all done, we can attribute our shape to the object!
   obj.Shape = comp1

Cheers
Ben

Re: Parametric attachment of scripted object

Posted: Thu Aug 02, 2018 11:21 pm
by paullee
Thanks to this 'Tutorial' (also thanks Oddtopus direct me here), I am testing adding Attachment Extension to ArchWall

https://forum.freecadweb.org/viewtopic.php?f=22&t=30037
Scripted Objects [ArchWall Testing] - Add MapMode + Attachment Offset?

Re: Parametric attachment of scripted object

Posted: Sun Feb 10, 2019 8:29 pm
by MarkkuTM
Carcass.py is what I have been looking for to create complex compound shapes that can be resized by as a unit without having to go into resizing every part individually. Secondly, it allows creation of multiple instances which are each individually resizable, and movable without breaking other parts.

I have been experimenting with the Carcass.py script. I have been able to predefine the width, depth, height, and thickness attributes by creating a list of dimensions, <obj.depth = 600> , and so on, at the end of the <def __init__(self,obj):> block. Then when I use the calling macro, it creates the predefined size of carcass on my active document. (If I put the same list of dimensions inside of the <def execute(self,obj):> block near the beginning it does not work, although you would think it should because there is a test for variables == 0 before the vertices are defined)

I am not well versed in how Python classes work, so probably what I have done above is not quite correct. My aim is to be able to call Carcass.py from a script that passes variables to it so that I do not have to write them explicitly into the code. I want to read a list of variables into my calling script and pass them into the Carcass.py class instance.

I would appreciate any guidance on what would be the proper procedure.