How can I convert a pad to an equivalent shape using the Python bindings

Hello!

I want to plot a drawing that has the same shape as a pad on a non-copper layer.

I can do this just fine for pads that are simple rectangles using

drawing = pcbnew.PCB_SHAPE(board)
drawing.SetShape(pad.GetShape())

And then setting the start and end based on the values returned by pad.GetSize()

But problems arise when I try copying pads of different shapes, namely, the ones where pad.GetShape() gives me SH_SIMPLE. I don’t really know how SH_SIMPLE shapes work, so I’m having trouble setting the parameters so that they match the original pad.

Is there a simpler way of copying pad shapes to non-copper layers? Am I going about this the right way?

Try GetEffectiveShape() method of the pad.

Thanks for you reply!

GetEffectiveShape() seems promising, but I don’t really know where to go from there. It looks like next I’ll need to call GetIndexableSubshapes() on the shape returned, but that requires an argument called aSubshapes and I’m a little stumped on what to pass in.

That method returns different types of SHAPE descendant based on attributes of a pad. You can learn the innards from this but if you just call Cast() on the resulting object it should transform it into correct python type that you should be able to inspect further.

For SHAPE_COMPOUND which is the most common case, you can convert it to SHAPE_SIMPLE using ConvertToSimplePolygon() (only works on kicad 7, I think).

I’m a little confused on the exact sequence of code:

drawing = pcbnew.PCB_SHAPE(board)
shape = pad.GetEffectiveShape().Cast()

# I'm unsure from here on out
# ConvertToSimplePolygon() seems to require an 'aOut' parameter
simple_shape = pcbnew.SHAPE_SIMPLE()
shape.ConvertToSimplePolygon(simple_shape) # returns False so I'm assuming it failed

drawing.SetShape(simple_shape) # Seems to require a parameter of SHAPE_T

And after I do this, how would I set the dimensions to ensure it’s the same size as the original pad?

Sorry for the trouble

I looked more into it and it’s more complicated than I remembered, sorry for misleading you. ConvertToSimplePolygon() is not implemented on compound shape actually, it will always return False.

But you can still get geometry of a pad, although it’s not straightforward.

You can use this code as reference, it handles all pad types and kicad versions up to v7

To transfer that geometry to a DRAWING you need to recreate the polygons (see SHAPE_POLY_LINE class).

Why are you doing this though? What is the end goal of copying the shape to another layer?

Oh wow that’s certainly a lot.

I want to basically display pads and tracks that fulfil a certain condition. And I thought the best way to do that was to plot them on a user layer, so as to not interfere with the copper layers. But it may be simpler to just create a new board and plot them on copper layers there.

Thanks for your help!

Hey sorry, but would you mind going into a little more detail on how exactly to transfer the geometry? And by SHAPE_POLY_LINE do you mean SHAPE_POLY_SET?

You might find useful information in Plugin howtos · Wiki · eelik-kicad / KiCad Documentation · GitLab, I have some examples and explanation for these classes there.

Yes, SHAPE_POLY_SET. Eelik’s link has good info, the gist is that SHAPE_POLY_SET consists of one or more polygon outlines which are just chains of coordinates of it’s vertices. Create a PCB_SHAPE with type S_POLYGON and set it’s shape with SetPolyShape().

But if your pad is not a simple rectangle but something like a chamfered or rounded rect or if you have to account for pad holes then building the polygon shape will be the hardest part.

Thank you both @qu1ck and @eelik!

I ended up using GetEffectivePolygon() to get the SHAPE_POLY_SET, it works in 7.0.10.

I wanted to ask one more thing, is it possible to recreate the hole in a pad in the resulting polygon? Could I somehow get the polygon of the hole and combine that with the pad polygon?

Hole is not stored as a polygon, you have to build one yourself based on it’s size

Once you build the approximation of the circle/oblong shape you should add it to the POLY_SET as second outline. If that doesn’t automatically interpret it as a hole you can Fracture() it but I’m not sure if the enum needed as a parameter for that method is exposed in swig.

Looks like SHAPE_POLY_SET has a AddHole() function too. But how exactly do I get the position of the hole on the pad?

Edit:
Looks I might be able to call GetEffectiveHoleShape().Centre()

Edit 2:
But I am having trouble creating the hole. I’m creating a PCB_SHAPE that’s a SHAPE_T_CIRCLE, but for some reason there’s a GetRadius() function but no SetRadius()

Use SetStart() and SetEnd().
But that will only cover circular holes.

But you can’t pass PCB_SHAPE to AddHole() it only accepts SHAPE_LINE_CHAIN
https://docs.kicad.org/doxygen-python-7.0/classpcbnew_1_1SHAPE__POLY__SET.html#a735fcd90e29c671a68d404e1267b70bd

I think I can create the shape and then chain .GetPolyShape().Outline() to get a shape line chain.

So is there a way to create a more general solution? GetEffectiveHoleShape() seems to return a SHAPE_SEGMENT and Im not really sure what to do with that

You can’t. GetPolyShape only works on shape of type SHAPE_T_POLY.
SHAPE_SEGMENT is a segment, it has a start, an end and a width.

Hmm then I’m not really sure what to do then,

pad.GetDrillShape() returns an int and I’m not sure what to do with it,
pad.GetEffectiveHoleShape() returns does a SHAPE_SEGMENT, but it doesn’t seem to have GetStart or GetEnd

Let me teach you how to fish then. Open the scripting console and explore. Here is what I did after selecting a random THT footprint on the board

s = pcbnew.GetCurrentSelection()[0]
s
<pcbnew.BOARD_ITEM; proxy of <Swig Object of type 'BOARD_ITEM *' at 0x0000016D8DAC4720> >
s = s.Cast()
s
<pcbnew.FOOTPRINT; proxy of <Swig Object of type 'FOOTPRINT *' at 0x0000016D8DAC4630> >
p = s.Pads()[0]
p
<pcbnew.PAD; proxy of <Swig Object of type 'PAD *' at 0x0000016D8DAC4810> >
h = p.GetEffectiveHoleShape()
h
<pcbnew.SHAPE_SEGMENT; proxy of <Swig Object of type 'std::shared_ptr< SHAPE_SEGMENT > *' at 0x0000016D8DAC46F0> >
dir(h)
['BBox', 'Cast', 'Centre', 'Clone', 'Collide', 'Format', 'GetClearance', 'GetIndexableSubshapeCount', 'GetIndexableSubshapes', 'GetSeg', 'GetWidth', 'HasIndexableSubshapes', 'Is45Degree', 'IsNull', 'IsSolid', 'MIN_PRECISION_IU', 'Move', 'Parse', 'Rotate', 'SetSeg', 'SetWidth', 'Type', 'TypeName', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__weakref__', 'this', 'thisown']
seg = h.GetSeg()
seg
<pcbnew.SEG; proxy of <Swig Object of type 'SEG *' at 0x0000016D8DAC48A0> >
dir(seg)
['A', 'Angle', 'ApproxCollinear', 'ApproxParallel', 'ApproxPerpendicular', 'B', 'CanonicalCoefs', 'Center', 'Collide', 'Collinear', 'Contains', 'Distance', 'Index', 'Intersect', 'IntersectLines', 'Intersects', 'Length', 'LineDistance', 'LineProject', 'NearestPoint', 'Overlaps', 'ParallelSeg', 'PerpendicularSeg', 'ReflectPoint', 'Reverse', 'Reversed', 'Side', 'Square', 'SquaredDistance', 'SquaredLength', 'TCoef', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__weakref__', 'this', 'thisown']
seg.Center()
VECTOR2I(74190000, 72000000)
seg.Length()
0
h.GetWidth()
900000

It’s not clear from the methods of the SHAPE_SEGMENT or SEG how to get the endpoint so I open the header files and looky here, SEG has A and B points:

seg.A
VECTOR2I(99000000, 77000000)
seg.B
VECTOR2I(101000000, 77000000)

I see. The hole you were looking at was oblong right? I suppose the challenge now is finding some way to translate that shape into a polygon. It looks like there isn’t a simple way to transform a PCB_SHAPE into a polygon without it being of type S_POLYGON. I guess I’ll generate a bunch of points and append them.

Thank you for all you help! I really appreciate it.

Yeah, it’s just a rectangle shape with two half circles on it’s ends, shouldn’t be too hard to generate approximate shape. Kicad doesn’t expose this logic as far as I can tell.