Kicad Python bindings: Proper way to create or copy FP_TEXT to add to FOOTPRINT?

What is the proper way to instantiate a new FP_TEXT and add it to a FOOTPRINT (presumably into the GraphicalItems collection.) I’m anticipating that an FP_TEXT may have references to parent or other objects that need to be filled in correctly? Is there a factory function for new FP_TEXTs or some other procedure to follow?

And similar question, but how to copy an existing FP_TEXT. So for example, creating a copy of, say, the FP_TEXT returned by footprint.Value(), and, possibly with some modification like layer or position, putting it into the GraphicalObjects collection?

Thanks!

How to create one: KiCad Pcbnew Python Scripting: pcbnew.FP_TEXT Class Reference

Pass the footprint as parameter to constructor.

There is no easy way to copy, you have to create a new one and then set the params same as another one.

To add graphical item you should call Add(item) on the footprint.

1 Like

Excellent, thanks, I will try that.

For text, drawings and zones I used pcbnew.BOARD_ITEM.Duplicate() for creating a copy, thus requiring only further position/angle adjustments.

You do need to add the duplicate to board though (e.g. save_restore_layout.py#L1136)

1 Like

Huh, I assumed duplicate returns a BOARD_ITEM that you have to cast but apparently it does it for you.

Hmmm, Duplicate() seems very useful, but I don’t understand exactly how to use it – several questions arise:

As an example, suppose that on specific footprints, I want to “copy” FP_Text and FP_SHAPE items from the F.Fab layer to layer User.1. So, in concept I want to iterate through footprint.GraphicalItems(), find each item that has GetLayer() == pcbnew.F_Fab, then:

newitem = item.Duplicate()
newitem.SetLayer(pcbnew.User_1)
footprint.Add(newitem)

Question 1a: Parent: At this stage, does newitem have its parent set correctly? Considering that FP_TEXT and FP_SHAPE’s constructors require a parent argument:

__init__(FP_SHAPE self, FOOTPRINT parent, SHAPE_T aShape=SEGMENT) -> FP_SHAPE

… but Duplicate has no such argument.

Question 1b. Indeed, is parenting the same as or different from membership in the collection accessed via footprint.GraphicalObjects() and footprint.Add()?

Question 2: “Adding to the board”: @MitjaN says that newitem needs to be added “to the board”:
self.board.Add(new_text)

Is board.Add(newitem) required for my example? Or does @MitjaN’s example function deal with some text objects in a collection at the board level, and that’s analogous to using footprint.Add(newitem) for text or graphics objects subsidiary to a footprint object?

Question 3: Inheritance from BOARD_ITEM. As @qu1ck noted, an FP_TEXT (or FP_SHAPE) has a Duplicate() function by way of inheritance from BOARD_ITEM. This being Python, I guess it’s feasible for Duplicate to return an object of the derived type. But of more concern, does BOARD_ITEM.Duplicate() know how to duplicate the extra fields (and their values!) of the derived types? I guess those derived types may have virtual implementations of Duplicate() that may be protected and thus not directly visible to the Python interface?

Thanks.

Most of KiCad’s api is not python, it’s c++ wrapped into python.

Duplicate calls Clone() internally which is c++ function that has overrides on EDA_ITEM children that need it so you don’t have to worry about duplicate missing things because of type erosion.

Regarding Add() calls you have to only do it on the parent container. For footprint shapes and texts it’s footprint, for things that are directly on board (not pard of footprint) it’s the board.

1 Like

Yes I realize that Kicad is written in C++, and hence the Python API is a layer in front of the C++ API. My point was just that the Python interface could have the machinery to avoid requiring casting of the Python objects returned by Duplicate().

Thanks for your other comments. They narrow down the alternatives I need to experiment with to figure out what works, or the ambiguities to which to ascribe unexpected results :-).

And it does. It casts them for you.

Duplicate() etc almost does the right thing, but I think I’m short one clue.

Basically, after footprint.Add()-ing FP_TEXT and FP_SHAPE items created by Duplicate(), the data is present in PCBNew, but does not become visible unless I save the file and then reload it. PCBNew’s Refresh function does not fix this.

Details:

I’m using the following segment of code:

        # "Move" Value to selected annotation layer
        fp_value_PF_TEXT = fp.Value()
        fp_value_PF_TEXT.SetLayer(layer_for_annot)

        [...]
        # Gather copy-candidate items to a list
        for gritem in fp.GraphicalItems():
            lyr = gritem.GetLayer()
            if lyr == pcbnew.F_Fab:
                gritem_list.append(gritem)                            
            
        # Duplicate listed items to annotation layer
        for gritem in gritem_list:
            newitem = gritem.Duplicate()
            newitem.SetLayer(layer_for_annot)            
            fp.Add(newitem) 

I’m running this from the Tools > External Plugins menu.
For what it’s worth, layer_for_annot is the constant for User.2, or 3, or 4.

After running, I observe the following in PCBNew:

  • The change of layer for fp.Value is visible in the layout immediately
  • The addition of items to fp is NOT visible in the layout, and Refresh does not change that.
  • The additional items on fp ARE visible in the Properties panel for fp, and have the correct content, layer and so on
  • If I save the file, close PCBNew, then reopen it, the changes appear on the layout as intended.

So I appear to be lacking a command that will prompt PCBNew to do what’s necessary to render the changes “live”

Thanks for any suggestions!.

@MitjaN can assist you more here as his plugins deal more with editing the board than what I usually do. But I wouldn’t be surprised if pcbnew is just not updating all of it’s internal data correctly after plugin runs. You can try removing and readding the footprint that you just modified.

Yeah the KiCad’s backend code is not really taking into account what action plugin can/could do. There are some cases which are not supported. And I think that Duplicate() results should not refresh in the viewport (issue connected with this). And in my case (Save/Restore layout plugin) I’ve always used Add() and it was always for zones, tracks, text and drawing items added directly to board. I’ve never dealt with text added to footprint. So I’d try what Qu1ck is proposing.

Thanks again for your suggestions @qu1ck and @MitjaN. Still no success, but more clues:

Recap: In all cases the goal is to get a FOOTPRINT’s added FP_TEXT and FP_SHAPE items to display visually in PCBNew’s layout view, following running the External Plugin. In general:

  • The various modifications I tried do appear immediately in each component’s Properties dialog.
  • I tried many combinations of enabling/disabling layers and the Refresh function, with no impact.
  • Saving the board, and reloading it into PCBNew causes the changes to display as expected.

New observations:

  1. In the layout window, when one or more components are selected, PCBNew displays more layers and changes some texts to white (as a way of highlighting them). For a modified part, that causes the added FP_TEXTs to display (in white). They disappear again on deselecting the component.

  2. I tried removing and re-adding the modified footprint, as you guys suggested, but that did not prompt PCBNew to show the modifications.

  3. Same, but with a time.sleep(5) between Remove and Add (thinking that maybe there was some event that needed to happen). No improvement.

  4. I previously noted that changing the layer of one of the required FP_TEXTs (Value) does get updated in the layout view. So maybe the problem concerns the “non-required” items accessible only via footprint.GraphicalItems()? But…

  5. Library footprints come with a “non-required” FP_TEXT “$(REFERENCE)” for the RefId in the F.Fab layer. That’s accessible via GraphicalItems(). Using the plugin to change the layer of that item becomes visible immediately. So it’s not the GraphicalItems() membership per se that’s the issue.

  6. Thinking that maybe Duplicate was the key factor, I tried creating a FP_TEXT from scratch (not fully knowing how many attributes I would need to set for a working new FP_TEXT):

            newitem = pcbnew.FP_TEXT(fp)
            newitem.SetText("TestTest")
            newitem.SetPosition(fp_refid_posn)
            newitem.SetLayer(layer_for_annot)
            newitem.SetVisible(True)
            fp.Add(newitem)

Once again, this new FP_TEXT does not become visible in the layout window, but as with the previous cases, the text does appear when the component is selected (and disappears on deselect) and also appears in the component’s Properties dialog, and if the board is saved and reloaded, the new FP_TEXT is now visible.

Tentative conclusion

In fp.Add-ing new or duplicated FP_xxx’s, there seems to be some per-FP_xxx flag that’s not getting set, or some list that the new FP_xxx’s are not getting added to. Apparently saving and reloading works around that problem. But it sure would be nice to get this to work live!

1 Like

I looked at the source and it is like I suspected: it doesn’t pick up changes within footprints properly. All you have to do is replace the footprint with a new one with your changes, trick is that it has to be a new instance that you add, not the same one you removed.

So: duplicate, edit, add new one, remove old one.

1 Like

Wow, thanks for your research on this. I will try that a little later this evening.

I must say that having to remove and replace all the footprints on a PCB feels a bit brittle, but it that’s the path forward I may have to add some kind of check to confirm the process completed.

Thanks for the detailed report. I’d recommend that you raise an issue on GitLab for this case. Maybe the issue gets resolved but, even if it doesn’t at least the lead developers will become aware what the plugin authors require of the python API.

Thanks

Not at all! I thank you and qu1ck for your detailed help so I could make some progress!

raise an issue on Github

Github or Gitlab? I have indeed created issue:

PCBNew API: Adding FP_TEXTs of FP_SHAPEs to existing footprints fails to update display

Thanks again!

1 Like

Further update on the Duplicate() function:

I have come to the conclusion that newitem = item.Duplicate() creates a copy that is in some way dangerously shallow. That is to say, the newitem is a duplicate of the original item, but it appears to have copies of pointers to subsidiary objects that it now shares with the original.

So, for example, something like:

item = fp.Reference()
newitem = item.Duplicate()
fp.Add(newitem)
newitem.SetText("ABCD")

results in both the newitem and also the Reference() field both showing “ABCD”.

From the Python API, it’s not clear how this happens, though of course a dive into the actual C++ data structure behind (Reference()) and the Duplicate() code would reveal all.

Given this issue, the Python API doesn’t give any better confidence on any of the other “properties” that have Get and Set functions, as who knows what data structure and implementation is behind them.

My tentative conclusion is that Duplicate() on FP_TEXT (and similar) is not really usable for the purposes that its name suggests.

So instead you have to create, in effect, your own Duplicate function, which requires knowing what fields you will have to set in order to have a functioning FP_TEXT (or similar object).

So far, I’ve identified the following fields as necessary (in brief note form):

newitem = pcbnew.FP_TEXT(footprint)
SetText(newstr)
...
Get/Set Position()
Get/Set TextAngle()
Get/Set Layer()
Get/Set Visible()
Get/Set TextHeight()
Get/Set TextWidth()
Get/Set TextThickness()
Get/Set HorizJustify()
Get/Set VertJustify ()
footprint.Add(newitem)

… your Duplicate function could copy any or all of the fields from the existing item, or selectively set new values as suits your application.

1 Like

Thanks for the report. I really appreciate it.

The footprint class also behaves strangely when using SetBrightened() and ClearBrightened() methods in order to highlight the footprint in drawing canvas. While both methods work as intended on tracks, zones, free text and drawing items, but fails on footprints. In order to highlight footprint one has to manually highlight all the footprint pads, text and drawing items (ReplicateLayout/replicate_layout.py at 6c91224463db883810d2f9a56ab85936121609cf · MitjaNemec/ReplicateLayout · GitHub).

As for the Duplicate() method, I am only using it on tracks, zones and free text and drawing items without any issues.

1 Like

[Later Edit: The observations below were made on Kicad 6.0.7, and with less than full understanding of what aspects of the layout window PDBNew does or does not update following plugin changes to data. Later testing with PCBNew 6.0.10 did not show these same issues, or I simply misinterpreted results previously.]

While we’re on the topic of gotchas in this area, here’s one involving Position:

You might think you can do this to FP_TEXT items (here, olditem and newitem):

posn = olditem.GetPosition()
posn.x += offsetx
posn.y += offsety
newitem.SetPosition(posn)

… but what happens is that the code here modifies the position of olditem, and then gives newitem the same position.

What appears to happen here is that posn is the actual wxPoint object attached to olditem. Ie: GetPosition appears to return-value-by-reference rather than return-by-value, and SetPosition’s arguments may pass-by-reference also.

Instead you have to create a new wxPoint object:

newpos = pcbnew.wxPoint(posn.x, posn.y)
newpos.x += offsetx
newpos.y += offsety
gritem.SetPosition(newpos)

If my observations here are accurate, I think it’s a bit surprising that modifying posn changes the position of the olditem, given that olditem has an explicit SetPosition(wxPoint), which suggests that SetPosition is required to change the position.

Maybe this same kind of issue occurs throughout Kicad’s API, and I don’t know if it’s “as designed”, or a surprise outcome of firing SWIG at the C++ API.