Report that lists STEP models

With KiCad 6, is there any way to get a report on missing 3D models for a design?

Can we run a report from the PCB (like a BOM) that lists all the parts and STEP models? (We do something similar in the schematic to find missing footprint fields)

1 Like

If there was such a thing, I would expect it to be in:
Pcb Editor / File / Board Setup / Design Rules / Violation Severity / Miscellaneous

A possible snatch is that it’s common to add links to 3D symbols in the footprint to non existing 3D models. The idea is that 3D models can be added by just copying them to the right directory and without having to update the footprints themselves.

If it doesn’t already exist, this feature might be something that could be done in a python plugin. I don’t know the API, but I imagine it would be easy enough to cycle through all the footprints and capture the defined 3D file(s) taking note of which footprints don’t have file names specified. Then cycle through the list of collected 3D filenames and see if they can be found in the computer’s filesystem taking note of ones that can’t be found. Then generate a report based on what wasn’t found. The script should be able to handle footprints that have multiple 3D filenames.

I’m just thinking through the theory of operation, not volunteering to write any lines of code. :wink:


We have a script that does it on a library level. This could be a starting point.

I have however not run this in ages so i have no idea if it still works.


Interesting, I would use this

Her you go guys. The script needs a couple of changes, but it works for my projects on 5.1.x interpreter. Might still contain bugs though.

import pcbnew
import os.path

board = pcbnew.LoadBoard('archived_test_project/archive_test_project.kicad_pcb')

# if running standalone (outside of pcbnew)
if os.getenv("KISYS3DMOD") is None:
    os.environ["KISYS3DMOD"] = os.path.normpath("D://Mitja//Plate//Kicad_libs//official_libs//Packages3D")
if os.getenv("KIPRJMOD") is None:
    os.environ["KIPRJMOD"] = os.path.abspath(os.path.dirname(board.GetFileName()))

# prepare folder for 3Dmodels
proj_path = os.path.dirname(os.path.abspath(board.GetFileName()))

# get all footprints
footprints = board.GetModules()
fp_without_models = []

# go through all the footprints
for fp in footprints:
    fp_ref = fp.GetReference()

    # for each footprint get all 3D models
    fp_models = fp.Models()

    # for each 3D model find it's path
    for model in fp_models:
        model_path = model.m_Filename

        # check if path is encoded with variables
        if "${" in model_path or "$(" in model_path:
            # get environment variable name
            start_index = model_path.find("${") + 2 or model_path.find("$(") + 2
            end_index = model_path.find("}") or model_path.find(")")
            env_var = model_path[start_index:end_index]

            # check if variable is defined
            path = os.getenv(env_var)

            # if variable is defined, get absolute path
            if path is not None:
                clean_model_path = os.path.normpath(path + model_path[end_index + 1:])
            # if variable is not defined, we can not find the model. Thus don't put it on the list
                print("Can not find model defined with enviroment variable:\n" + model_path)
                fp_without_models.append((fp_ref, model_path))
        # check if path is absolute or relative
        elif model_path == os.path.basename(model_path):
            clean_model_path = os.path.normpath(proj_path + "//" + model_path)
        # check if model is given with absolute path
        elif os.path.exists(model_path):
            clean_model_path = model_path
        # otherwise we don't know how to parse the path
            print("Ambiguios path for the model: " + model_path)
            # test default 3D_library location "KISYS3DMOD"
            if os.path.exists(os.path.normpath(os.path.join(os.getenv("KISYS3DMOD"), model_path))):
                clean_model_path = os.path.normpath(os.path.join(os.getenv("KISYS3DMOD"), model_path))
                print("Going with: " + clean_model_path)
            # test in project folder location
            elif os.path.exists(os.path.normpath(os.path.join(proj_path, model_path))):
                clean_model_path = os.path.normpath(os.path.join(proj_path, model_path))
                print("Going with: " + clean_model_path)
                print("Can not find model defined with path:\n" + model_path)
                fp_without_models.append((fp_ref, model_path))
                clean_model_path = None

        model_path_without_extension = clean_model_path.rsplit('.', 1)[0]

        found_at_least_one = False
        if clean_model_path:
            model_without_extension = clean_model_path.rsplit('.', 1)[0]
            for ext in ['.stp', '.step']:
                if os.path.exists(model_without_extension + ext):
                    found_at_least_one = True
        if not found_at_least_one:
            fp_without_models.append((fp.GetReference(), clean_model_path))


put it on github, please :smiley:


I am tempted to wrap this around a wxpython GUI and finish it as an action plugin, but I cannot even find the time to support my current plugins, so I’d prefer is somebody else takes the torch with this plugin. And also with V6 around the corner I don’t see a sense to develop a plugin that I’ll have to port to V6.

So at the moment this will (sadly) stay as it is. Thanks for understanding

1 Like

At least upload it somewhere and I suggest that you declare a suitable license if you want others to take it up


Hi @MitjaN
for the moment I’ve added your code to my tools…


This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.