KiCad IPC API script to move a footprint is not working. Is it me or is it a bug?

I am running the latest nightly build and trying to use Python to control the PCB editor.

My script is able to locate the footprint and print its current values. However, when I try to update the position by changing the position property and calling update_items, no change happens to the board.

Since documentation on the new API seems sparse I am not sure if I’m just doing it wrong, or if there is actually a bug in the API. I also don’t want to invest a ton of time in learning the SWIG API if it is to be deprecated in less than a year anyway.

EDIT: I’ve dug a bit into the kicad-python library and I don’t see any issues there. This code:

        return [
            unwrap(result.item)
            for result in self._kicad.send(command, UpdateItemsResponse).updated_items
        ]

seems to be taking the response from the API and extracting the updated items list. When I modified this code and checked the items inside of updated_items, the reference to J1 in there still has its original position.

I also tried printing out the proto for the item before it goes into the API call, and it does have the position section in that code. I did notice that if I set the position to 0,0 it doesn’t include it, so I tried changing it to 1,1 and the position section is included in the proto. However the update still does not change the position and the position remains the original position after the board is re-queried. So I’m starting to think this may be a bug in KiCad.

EDIT 2: I tried tracing the API calls using the instructions here and I get this error (and only this error) on the console when my Python script runs:

Trace: (KICAD_API) Any message type type.googleapis.com/kiapi.board.types.Footprint3DModel is not known

Here is my Python script:

from typing import Dict, List
from kipy import KiCad
from kipy.board import Board, FootprintInstance
from kipy.geometry import Vector2
from kipy.board_types import TextBox

def get_duplicate_strings(strings: List[str]) -> List[str]:
    seen = set()
    duplicates = set()
    
    for s in strings:
        if s in seen:
            duplicates.add(s)
        else:
            seen.add(s)
    
    return list(duplicates)

def get_footprints(b: Board) -> Dict[str, FootprintInstance]:
    all_footprints = b.get_footprints()
    # verify that all footprint IDs are unique
    footprint_id_dupes = get_duplicate_strings([i.reference_field.text.value for i in all_footprints])
    # if "REF**" appears in the dupe list, remove it.
    if 'REF**' in footprint_id_dupes:
        footprint_id_dupes.remove('REF**')
    assert len(footprint_id_dupes) == 0
    footprints = {str(i.reference_field.text.value): i for i in all_footprints}
    if 'REF**' in footprints:
        del footprints['REF**']
    return footprints

def main():
    k = KiCad()
    print(f"Connected to KiCad {k.get_version()}")
    # look for the PCB
    pcb = k.get_board()
    if pcb is None:
        print('No PCB is open.')
        return

    # TESTING CODE TO DEMONSTRATE CRASH:

    # Find item J1 in the PCB.
    items = get_footprints(pcb)
    j1 = items.get('J1')
    if j1 is None:
        print('No footprint J1 found.')
        return
    
    # Print the position of J1
    print("J1 is at: ",end='')
    print(j1.position)

    # Position J1 
    print("Moving J1 to 0,0")
    j1.position = Vector2.from_xy(0, 0)
    print("J1 is now at: ",end='')
    print(j1.position)
    
    print("Sending update to KiCad")
    try:
        pcb.update_items([j1])
        # Save the PCB
        pcb.save()
    except Exception as e:
        print("Could not update the PCB: " + str(e))
    
    print("PCB update sent.")

    items = get_footprints(pcb)
    j1 = items.get('J1')

    print("After update J1 is at: ",end='')
    print(j1.position)

if __name__ == '__main__':
    main()

A run of the code:

[fmillion@d178 script]$ poetry run python script.py 
Connected to KiCad 9.99.0-701-g151cb01795-dirty
J1 is at: Vector2(23262500, 69862500)
Moving J1 to 0,0
J1 is now at: Vector2(0, 0)
Sending update to KiCad
PCB update sent.
After update J1 is at: Vector2(23262500, 69862500)

Version:

Application: KiCad PCB Editor x86_64 on x86_64
Version: 9.99.0-701-g151cb01795-dirty, release build
Libraries:
    wxWidgets 3.2.6
    FreeType 2.13.3
    HarfBuzz 10.4.0
    FontConfig 2.16.0
    libcurl/8.12.1 OpenSSL/3.4.1 zlib/1.3.1 brotli/1.1.0 zstd/1.5.7 libidn2/2.3.7 libpsl/0.21.5 libssh2/1.11.1 nghttp2/1.65.0 nghttp3/1.8.0
Platform: Arch Linux, 64 bit, Little endian, wxGTK, X11, KDE, x11
OpenGL: Intel, Mesa Intel(R) UHD Graphics 630 (CFL GT2), 4.6 (Compatibility Profile) Mesa 24.3.4-arch1.1
Build Info:
    Date: Mar 28 2025 04:12:43
    wxWidgets: 3.2.6 (wchar_t,wx containers) GTK+ 3.24
    Boost: 1.87.0
    OCC: 7.8.1
    Curl: 8.12.1
    ngspice: 44.2
    Compiler: GCC 14.2.1 with C++ ABI 1019
Build settings:
    KICAD_USE_EGL=ON
    KICAD_IPC_API=ON
Locale: 
    Lang: en_US
    Enc: UTF-8
    Num: 1234.5
    Encoded кΩ丈: D0BACEA9E4B888 (sys), D0BACEA9E4B888 (utf8)

(I am on Nightly because Arch doesn’t seem to package RC or beta versions, and the packaged stable 9.0.0 crashes with a segfault whenever you call update_items.)

Also having the same problem.

I was trying to test the move example.

It seems to correctly read the footprints, I can change the positions, but cannot get it to go back to the actual PCB via update.

I’m running it externally, not installed as a plugin.

Hi, I got it working. This thread helped point me in the right direction.

The problem is that there are new features and fixes that are not yet available in the stable production builds.

For example the pypi module, at this time, is 0.3.0

The git report is at 0.4.0

With the change history note “Fix ability to move and rotate footprints”

I also saw some other fixes related to KiCad. So if you need to do this now, you need to install the latest test build of KiCad (9.0.1.99 as of now)

…and download and compile the Python package from the git repo, instructions are here

After that, I got it working, and can move components. But for completeness, here is my code example.

from kipy import KiCad
from kipy.geometry import Vector2

board = KiCad().get_board()
comm = board.begin_commit()
footprints = board.get_footprints()

# put them all on top of each other.
for footprint in footprints:
    footprint.position = Vector2.from_xy_mm(100, 100)

try:
    board.update_items(footprints)
    board.push_commit(comm)
except Exception as e:
    print("Could not update the PCB: " + str(e))