Skip to content

Add set_net_color: set/clear an individual net's display color override - #376

Open
JMcordobamendez wants to merge 1 commit into
mixelpixx:mainfrom
JMcordobamendez:feature/set-net-color
Open

Add set_net_color: set/clear an individual net's display color override#376
JMcordobamendez wants to merge 1 commit into
mixelpixx:mainfrom
JMcordobamendez:feature/set-net-color

Conversation

@JMcordobamendez

@JMcordobamendez JMcordobamendez commented Aug 12, 2026

Copy link
Copy Markdown

Closes #375.

What

set_net_color(net, color?, clear?) - set or clear an individual net's
display color override (the PCB editor's "Net colors" panel). Cosmetic
only, doesn't touch routing or design rules.

Why this shape

net_settings.net_colors lives only in <project>.kicad_pro, as a flat
{net_name: "rgb(r, g, b)"} map. There's no SWIG counterpart to keep in
sync - NETINFO_ITEM has no color getter/setter at all (checked the full
class in pcbnew.py) - so this is pure JSON persistence, following the
same read/modify/write-atomically shape assign_net_to_class already
uses for net-class membership.

  • color: a #RRGGBB hex string, translated to KiCad's own
    "rgb(r, g, b)" format.
  • clear: true removes the override entirely (rather than writing some
    empty/sentinel color), reverting the net to its automatic/class color
    • same behavior as the native "Clear color" action.
  • Validates the net exists on the board before writing, same check
    assign_net_to_class uses.

Why this hard-fails without a .kicad_pro, where assign_net_to_class warns

The two look inconsistent on purpose, and it is worth stating so the
asymmetry reads as designed.

assign_net_to_class mirrors its change into SWIG state as well as the
project file. By the time it discovers the project file is missing, the
in-memory board has already been updated - the operation genuinely half
succeeded, and failing outright would misreport what happened. A warning
is the accurate answer there.

set_net_color has no SWIG half. NETINFO_ITEM exposes no color
accessor, so net_settings.net_colors in the .kicad_pro is the
entire operation. With no project file there is nothing updated
anywhere, and returning a warning would tell the caller the color was
set when nothing was. So it fails.

Where it's wired

  • src/tools/routing.ts - Zod schema.
  • src/tools/registry.ts - routing category entry, so search_tools
    can discover it.
  • python/commands/routing.py - apply_net_color_to_project_settings /
    persist_net_color_to_project (pure functions, no SWIG) +
    RoutingCommands.set_net_color.
  • python/kicad_interface.py - dispatch table entry.
  • python/schemas/tool_schemas.py - matching entry for the Python-side
    schema registry (create_netclass/assign_net_to_class both have one
    there too).
  • README.md / docs/TOOL_INVENTORY.md - counts and listings.

I added the kicad_interface.py dispatch entry deliberately carefully:
tests/test_assign_net_to_class.py's docstring documents that tool
shipping registered in TS/the router but missing from the dispatch
table, silently returning "Unknown command" for every call. Wanted to
make sure this one didn't repeat that.

Testing

  • tests/test_set_net_color.py (28 tests, new): hex parsing, the pure
    JSON transform (add/overwrite/clear, null-safety on a fresh project's
    net_colors: null), atomic file round-trip, and
    RoutingCommands.set_net_color wiring against a mocked board.
  • Full Python suite on the rebased branch: 2189 passed, 35 skipped, 1
    failed
    . The failure is
    test_ipc_open_board_path.py::test_returns_full_path_composed_from_project_and_filename,
    which is Windows-only and pre-existing - it asserts a POSIX path
    against an os.path.join result. Confirmed it fails identically on a
    clean aa53d52 worktree without this branch; it passes on CI's Linux
    runners.
  • tsc builds clean, 73/73 vitest pass (including
    registry-completeness and readme-counts).
  • Formatting/lint verified against the versions CI pins - Black 26.3.1,
    isort 8.0.1, flake8 7.3.0 over python/ and tests/, plus prettier
    and eslint on the changed files.
  • Manually verified against a real KiCad 10.0.5 install (Windows, SWIG
    backend) on a real multi-sheet board: opened the project, set a color
    on an uncolored net, overwrote an existing net's color, cleared
    another, hit a nonexistent net and an invalid hex - all behaved as
    expected, and the .kicad_pro round-trips correctly with unrelated
    content untouched.

Happy to adjust scope or naming if you'd rather this look different -
net-class-level color (NETCLASS.SetPcbColor, which does have a SWIG
binding) would be a reasonable follow-up but felt like a separate PR.

@mixelpixx

Copy link
Copy Markdown
Owner

Reviewed — the design is right and the diligence shows (the atomic .kicad_pro write, clear:true deleting the key rather than writing a sentinel, validating the net against NetsByName before touching the file, and cross-checking the dispatch entry against the failure mode documented in test_assign_net_to_class). NETINFO_ITEM genuinely has no color accessor, so pure JSON persistence in net_settings.net_colors is the correct shape, and this closes #375.

Two changes needed before it can merge, the first of which will fail CI as filed:

  1. Add "set_net_color" to the routing category in src/tools/registry.ts. The registry-completeness ratchet (tests-ts/registry-completeness.test.ts) fails any new server.tool() registration that is neither in the registry nor in the frozen KNOWN_UNREGISTERED set — and the test's message will tell you not to add it to the frozen set, which only shrinks. Then update both README occurrences of the tool count to match getRegistryStats() (readme-counts.test.ts pins them; main is currently at 169 tools / 15 categories after this week's merges, so re-derive after rebasing rather than trusting these numbers).
  2. Minor: the lazy '_HEX_COLOR_RE = None' + import re inside the function + global dance is unnecessary — re is imported module-level across this codebase and a compiled module constant costs nothing.

One behavioral question to settle in the PR description rather than change: set_net_color hard-fails when no .kicad_pro exists next to the board, where assign_net_to_class (which this mirrors) degrades to a warning because it still updates SWIG state. Here there is no SWIG state to update, so hard-failing is defensible — just say so explicitly so the asymmetry reads as designed rather than accidental.

CI was approved to run on this PR today; expect the registry failure above until item 1 lands.

Closes mixelpixx#375. No tool currently exposes the PCB editor's "Net colors"
panel. net_settings.net_colors lives only in <project>.kicad_pro, with
no SWIG counterpart (NETINFO_ITEM has no color getter/setter), so this
is pure JSON persistence following the same read/modify/write-atomically
shape already used by assign_net_to_class.

- set_net_color(net, color?, clear?): color is a #RRGGBB hex string,
  translated to KiCad's "rgb(r, g, b)" net_colors format. clear: true
  removes the override, reverting the net to its automatic/class color.
- Validates the net exists on the board before writing (same check as
  assign_net_to_class).
- Wired in all three places a tool needs to reach the router: TS schema
  (src/tools/routing.ts), the Python dispatch table
  (kicad_interface.py), and the Python-side tool_schemas.py registry —
  the last one because assign_net_to_class previously shipped without a
  dispatch-table entry (see test_assign_net_to_class.py's docstring) and
  I didn't want to repeat that gap for a new tool.
- 25 new tests (tests/test_set_net_color.py) mirroring
  test_assign_net_to_class.py: hex parsing, the pure JSON transform,
  atomic file round-trip, and RoutingCommands.set_net_color wiring with
  a mocked board.

Verified against a real KiCad 10.0.5 install (Windows, SWIG backend):
opened a real multi-sheet board, set/overwrote/cleared colors on real
nets, confirmed the .kicad_pro round-trips correctly and unrelated
project content is untouched. Full test suite: 1806 passed (9
pre-existing failures unrelated to this change — Freerouting needs Java,
which isn't installed in this environment, plus one Windows path-
separator test).
@JMcordobamendez

Copy link
Copy Markdown
Author

Thanks for the review — all three points addressed. Rebased onto aa53d52
(v2.7.0); the only conflict was CHANGELOG.md, where my entry had been sitting
under [Unreleased] and the release had since claimed that space. Moved it
back up under [Unreleased], 2.7.0 untouched.

1. Registry. set_net_color added to the routing category, and the
category description extended to mention net display colors. Re-derived the
counts after rebasing rather than trusting the old ones, as you said:
getRegistryStats() now reports 170 tools / 15 categories, so the three
README occurrences of 169 are now 170 (headline, the search_tools line in
the release notes, and the "Available Tools" paragraph — I found three, not
two). Also bumped the README's ### Routing (13 tools) list to 14 and added
the tool to it.

While there, docs/TOOL_INVENTORY.md had the new row marked Additional,
which registering it made wrong — it is now Routed (routing), alongside
add_via and add_copper_pour.

2. The lazy regex. Gone. re is now a module-level import in
routing.py (it genuinely was not imported there before, so this adds the
import rather than just using an existing one) and _HEX_COLOR_RE is a
compiled module constant. You were right that it cost nothing.

3. The .kicad_pro asymmetry — added to the PR description above, and
the short version: assign_net_to_class degrades to a warning because it
has already updated SWIG state by that point, so the operation half-succeeded
and refusing it outright would misreport what happened. set_net_color has
no SWIG state at all — NETINFO_ITEM exposes no color accessor — so the
project file is the entire operation. If it is not there, nothing happened,
and a warning would claim otherwise. Hard-failing is the honest report, not
an oversight.

CI

The three failures were two causes, both fixed:

  • registry-completeness (the macOS job that failed first; the rest were
    fail-fast cancellations) — item 1 above.
  • Black, which failed both Code Quality and Python Tests (3.12): it wanted
    the ternary in the result dict wrapped in parens. Applied its diff verbatim.

Verified locally with the same versions CI pins (Black 26.3.1, isort 8.0.1,
flake8 7.3.0): all clean over python/ and tests/, plus prettier and
eslint on the changed files. tsc builds, 73/73 vitest pass, and the Python
suite is 2189 passed / 1 failed — that one being
test_ipc_open_board_path.py::test_returns_full_path_composed_from_project_and_filename,
which is Windows-only and pre-existing: it asserts a POSIX path against an
os.path.join result, so it reads pcb\Dankro... on Windows. I confirmed it
fails identically on a clean aa53d52 worktree with my branch nowhere in
sight. It passes on CI's Linux runners.

Two things I found but deliberately did not change

  • sort_keys=True vs create_project writes a non-conformant .kicad_pro stub (missing entire project structure) #220. persist_net_color_to_project writes with
    json.dump(..., sort_keys=True) because that is what
    persist_net_assignment_to_project and the create_netclass path directly
    above it do (routing.py lines 124 and 197 on main). But
    project_settings_guard.py deliberately does not sort, and says why:
    "create_project writes a non-conformant .kicad_pro stub (missing entire project structure) #220 made the .kicad_pro byte-faithful to KiCad's own output, and
    alphabetising every key would undo that on the first restore." So the first
    call to any of these three tools reorders the user's whole project file.
    It is cosmetic churn rather than a functional break — the guard restores
    net_settings from the on-disk snapshot, so a colour set here survives a
    later board save correctly either way — but it does undo create_project writes a non-conformant .kicad_pro stub (missing entire project structure) #220's intent. I
    left mine consistent with its two siblings rather than making it the odd
    one out; happy to drop sort_keys from all three in a follow-up if you
    agree that is the direction.

  • README line 473 ("22 direct tools / 113 routed tools / 14 categories",
    and a category list missing symbol_library) is stale independently of
    this PR — actual figures are 32 direct, 145 routed, 15 categories. Fixing
    it here would have widened the diff into something this PR has no business
    touching, so I left it. Worth its own commit.

Re-verified the behaviour end-to-end after the rebase against a realistic
.kicad_pro: set on an uncoloured net, overwrite an existing one, clear
(key removed, not blanked), unrelated keys and net_settings.classes
untouched, net_colors: null handled, invalid hex rejected, and a missing
project file reported rather than raised.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature idea: net color support (net classes currently have no color field)

2 participants