Commit Graph

376 Commits

Author SHA1 Message Date
Gavin Colonese
07483623be Fix: preload cairo DLL on Windows for 2D board view rendering (#160)
cairocffi uses cffi's ffi.dlopen('cairo-2') which requires the DLL to
be on the system PATH. On Windows, prepend KiCad's bin directory to
PATH early in kicad_interface.py (before any cairocffi import) so
cairo-2.dll can be found. Checks PYTHONPATH, Python executable dir,
and default KiCad 9/8 install paths.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-18 13:39:30 -04:00
Gavin Colonese
319a65bd0b Fix: IPC handlers now convert inch units to mm for position commands (#158)
The _ipc_move_component and _ipc_place_component handlers were ignoring
the unit field from position parameters, always treating values as mm.
When inches were specified, components would be placed at 1/25.4th of
the intended position. Now reads the unit field and converts to mm
before passing to the IPC backend.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-18 13:39:26 -04:00
Gavin Colonese
d62ff4a7c8 Fix: handle missing library_link attribute on Footprint objects in KiCad 9 (#157)
KiCad 9's IPC API returns Footprint objects where fp.definition lacks
the library_link attribute, causing all component queries to fail with
"'Footprint' object has no attribute 'library_link'" warnings and
returning 0 components. This adds a hasattr check with fallback to
fp.definition.id.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-18 13:39:22 -04:00
Eugene Mikhantyev
8d79f7b801 Merge pull request #175 from mixelpixx/fix/prettier-kicad-server
style: apply prettier formatting to kicad-server.ts
2026-05-13 20:49:09 +01:00
Eugene Mikhantyev
3ea2d03637 style: apply prettier formatting to kicad-server.ts
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 20:45:11 +01:00
Eugene Mikhantyev
eda28dc9bf Merge pull request #155 from rek/fix/linux-script-path
fix: resolve kicad_interface.py path using import.meta.url (Linux/macOS)
2026-05-13 20:43:57 +01:00
Eugene Mikhantyev
403aafc6cf Merge pull request #151 from inktomi/feat/auto-save-guard
feat: guard SWIG auto-save against external file changes
2026-05-13 20:39:31 +01:00
Eugene Mikhantyev
7cbf5a1e49 Merge pull request #149 from Kulitorum/fix/project-sym-lib-table-scope
Make project-scope sym-lib-table visible to symbol-discovery tools
2026-05-13 20:30:11 +01:00
Adam Tombleson
9f77d47c6e fix: use fileURLToPath for cross-platform compatibility
import.meta.url.pathname returns /C:/... on Windows (leading slash),
breaking path.join. fileURLToPath handles Windows paths correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 21:26:48 +05:45
Adam Tombleson
f71ec03e79 fix: use import.meta.url for cross-platform script path resolution
Hardcoded Windows path broke Linux/macOS startup. Use import.meta.url to
derive the script path relative to the compiled JS, with KICAD_SCRIPT_PATH
env override for custom installs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-05 21:23:38 +05:45
Eugene Mikhantyev
216d3f189d Merge pull request #152 from mixelpixx/fix/post-pr145-test-fallout
Align pin transform with eeschema (rotation, mirror, angle)
2026-05-03 22:45:41 +01:00
Eugene Mikhantyev
496be317e9 test: close mirror-fixture gap exposed by post-fix audit
The eeschema-ground-truth fixture (_build_mirror_case) passed
'mirror': 'x' to ComponentManager.add_component, which silently drops
the kwarg — so the resulting .kicad_sch had no (mirror x|y) token,
eeschema rendered an unmirrored symbol, and our pin coords (also
unmirrored) tautologically matched. The mirror tests were GREEN both
before and after the rotation/mirror fix in 7e67cb9, providing zero
regression coverage for the mirror semantics.

Fix:
  - _build_mirror_case now applies the mirror via the same low-level
    helper (WireDragger.update_symbol_rotation_mirror) that
    rotate_schematic_component uses, with a guard assertion that the
    written file actually contains (mirror x|y).
  - Two new pin-down unit tests in test_add_schematic_component.py
    document and lock down ComponentManager.add_component's silent-drop
    behavior for mirror, so the next person to touch that path knows to
    update the eeschema-truth fixture if they grow real mirror support.

Verified: with the production fix at 7e67cb9 reverted, the kicad-cli
mirror tests now go RED (previously they stayed GREEN regardless).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 22:40:34 +01:00
Eugene Mikhantyev
7e67cb91c4 fix(pin_world_xy): align rotation direction and mirror axis with eeschema
Two bugs in WireDragger.pin_world_xy (and corresponding bugs in
PinLocator.get_pin_angle) caused pin coordinates and angles to land on
the wrong pin in 4 of 8 polarized cases (rot=90, rot=270, mirror x on a
vertical part, mirror y on a vertical part). Verified end-to-end against
`kicad-cli sch export netlist`.

(1) Rotation direction. After PR #145's `-ly` Y-flip, calling the
standard math (Y-up CCW) `_rotate` is effectively CW in screen Y-down.
eeschema's TRANSFORM(0,1,-1,0) for rot=90 is screen-CCW. They agreed at
0° and 180° (where the rotation matrices coincide) but disagreed at 90°
and 270°.

(2) Mirror axis semantics swapped. Per eeschema symbol.h:43-44,
SYM_MIRROR_X = TRANSFORM(1,0,0,-1) negates Y, and SYM_MIRROR_Y =
TRANSFORM(-1,0,0,1) negates X. Our code did the inverse: `mirror_x`
negated the X component and `mirror_y` negated the Y component.

Fix shape for `_rotate`: chose option (b) — leave `_rotate` as standard
math and negate the angle at the call site (`_rotate(lx, ly, -rotation)`).
This converts math-CCW to screen-CCW without disturbing
`TestRotatePoint`'s direct expectations of `_rotate`.

Final composition order in `pin_world_xy` matches eeschema's parser
(rotation set first into m_transform, then mirror composed via
`new = old * temp` so the mirror is applied first to the coordinate):
  1. Y-flip:    ly = -ly                    (lib Y-up → screen Y-down)
  2. Mirror:    if mirror_x: ly = -ly       (negate screen-Y)
                if mirror_y: lx = -lx       (negate screen-X)
  3. Rotate:    _rotate(lx, ly, -rotation)  (screen-CCW)
  4. Translate: add (sym_x, sym_y)

Verified by hand for {rot=90, rot=270} × {none, mirror_x, mirror_y}
against the TRANSFORM matrices in transform.cpp:44 and symbol.h:43-44.

`PinLocator.get_pin_angle` mirrors the same composition in angle space.
For an angle, Y-flip and mirror_x both negate the angle; mirror_y maps
to (180 - angle). The screen-CCW rotation in `pin_world_xy` corresponds
to subtracting (not adding) the symbol rotation in standard atan2
convention — fixed accordingly. Geometry test
(`test_get_pin_angle.py::test_get_pin_angle_matches_geometric_expectation`)
derives expected angles from `pin_world_xy` itself, so it pins the two
together.

`tests/test_rotate_schematic_mirror.py::test_pin_positions_mirror_x_flips_x`
encoded the OLD inverted semantics and is updated/renamed to
`test_pin_positions_mirror_x_flips_y` with a pin that has non-zero Y so
the assertion is meaningful under the corrected semantics.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 22:30:17 +01:00
Eugene Mikhantyev
f7660e15ad test: encode eeschema kicad-cli ground truth (currently RED)
Update existing rotation/mirror assertions and add a kicad-cli-grounded
regression suite. eeschema's pin transform is verified against:
  - rotation: TRANSFORM(0,1,-1,0) for rot=90 — CCW in screen Y-down
  - mirror x: SYM_MIRROR_X = TRANSFORM(1,0,0,-1) — negates internal Y
  - mirror y: SYM_MIRROR_Y = TRANSFORM(-1,0,0,1) — negates internal X

Updated assertions:
  - test_pin_locator_y_flip: rotated cap pin 1 X 153.81 → 146.19
  - test_move_with_wire_preservation::test_resistor_rotated_90:
    pin 1 X 103.81 → 96.19
  - test_hierarchical_pad_net_map::test_90_degree_rotation:
    swapped UP_NET / DN_NET label coords

New file tests/test_pin_world_xy_eeschema_truth.py:
  - 4 parametrized diode tests via kicad-cli netlist (oracle = eeschema)
  - 2 parametrized resistor mirror tests via kicad-cli netlist
  - 3 pure-math pin_world_xy assertions for rot=90, mirror_x, mirror_y

Currently RED on rotation 90/270 (rotation direction) and mirror_x/y
(axis semantics swapped). Production fix in WireDragger.pin_world_xy
to follow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 22:24:14 +01:00
Eugene Mikhantyev
118318c2f3 test(rotate_schematic_mirror): isolate sys.modules stubs to fix test pollution
`test_rotate_handler_no_crash` permanently replaced
`sys.modules["schemas.tool_schemas"].TOOL_SCHEMAS` with `[]`, leaking into
later tests. When test_wire_connectivity (or any test) ran after this one
and did `from schemas.tool_schemas import TOOL_SCHEMAS`, it got the empty
list and `TOOL_SCHEMAS["get_wire_connections"]` raised `TypeError: list
indices must be integers or slices, not str`.

Save the original sys.modules entries and restore them in a `finally`
block so the stubs are scoped to the test body. Whole suite now passes
(678 tests, previously 4 failed in TestSchema when run in suite order).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 22:02:22 +01:00
Eugene Mikhantyev
9101130423 fix: tighten _build_sch_with_instances default arg
Use None default + 'instances or []' to keep mutable-default lint happy
and satisfy the Iterable annotation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 22:00:08 +01:00
Eugene Mikhantyev
b4cbf606dc test(hierarchical_pad_net_map): write symbol instances to disk for sexp transform read
All 10 failing tests in test_hierarchical_pad_net_map.py share the same root cause:
broken test fixture, not a production bug. PR #88 (commit 7cafbda, "fix:
get_schematic_pin_locations now accounts for mirror flags") introduced
PinLocator._get_symbol_transform, which reads symbol position/rotation/lib_id
directly from the .kicad_sch file via sexpdata + WireDragger.find_symbol — it
deliberately bypasses the kicad-skip cache so mirror/rotation mutations are
authoritative. The hierarchical-pad-net-map tests, however, only mocked
skip.Schematic and wrote a lib_symbols-only stub to disk with no (symbol ...)
instance, so _get_symbol_transform returned None and every pin lookup failed
with "Could not read transform for R1/R2". The tests last passed at 8a42812
(introduction) and broke at 7cafbda; PR #145 did not touch this code path.

Per-test classification (all 10): broken fixture.
- TestLabelAtPin::test_global_label_pin1 — fixture
- TestLabelAtPin::test_global_label_pin2 — fixture
- TestLabelAtPin::test_both_pins_mapped — fixture
- TestLabelAtPin::test_local_label_also_works — fixture
- TestLabelAtPin::test_hierarchical_label_also_works — fixture
- TestLabelViaWire::test_label_one_hop_away — fixture
- TestLabelViaWire::test_label_two_hops_away — fixture
- TestMultipleSubsheets::test_components_in_subsheet_collected — fixture
- TestMultipleSubsheets::test_top_and_sub_components_merged — fixture
- TestRotatedSymbol::test_90_degree_rotation — fixture (rotation expected
  values already match the post-PR-145 convention; only fixture needed)

Fix: add _build_sch_with_instances() helper that emits a real (symbol ...)
block alongside the existing TestLib:R lib_symbols, so sexpdata can resolve
the transform. The skip.Schematic mock is still used for labels and wires.

All 15 tests in this file now pass; the broader related set
(test_pin_locator_y_flip, test_get_pin_angle, test_move_with_wire_preservation,
test_pin_locator_and_component) also pass — 84 total.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 21:59:04 +01:00
Eugene Mikhantyev
22eb3319f9 fix(pin_locator): rstrip "_" in WireDragger.find_symbol; clean stale tests
Resolves the four failing tests in tests/test_pin_locator_and_component.py
left behind by the PR #145 / commit 3c22580 Y-flip work.

Per-test rationale:

- TestPinLocatorYAxisNegation::{test_pin1_y_above_center_for_rotation_0,
  test_pin2_y_below_center_for_rotation_0, test_pin1_rotated_90}: stale.
  Their assertions encoded the *correct* post-PR-145 convention (96.19,
  103.81, etc.), but their setup MagicMock'd self._schematic_cache while
  bypassing _get_symbol_transform, which reads the .kicad_sch file
  directly via sexpdata. The end-to-end Y-flip behaviour is already
  covered against eeschema in tests/test_pin_locator_y_flip.py — keeping
  three mock-based duplicates added no value, so they were removed.

- TestPinLocatorReferenceRstrip::test_get_pin_location_finds_symbol_with_trailing_underscore:
  revealed a real production bug. PinLocator.get_pin_location strips a
  trailing "_" on the kicad-skip lookup path, but the sexpdata-based
  _get_symbol_transform delegates to WireDragger.find_symbol which used an
  exact-equality comparison. With kicad-skip's "R1_" artifact the function
  returned None, so the whole pin-location call failed even when the symbol
  was clearly present. Fixed find_symbol to apply the same rstrip("_") on
  the stored reference before comparing, mirroring the existing behaviour
  in PinLocator. The test was also rewritten to use a real temp .kicad_sch
  (with the on-disk reference mangled to "R1_") so it actually exercises
  both lookup paths instead of bypassing one with mocks.

Files changed:
- python/commands/wire_dragger.py:78-89 — rstrip("_") on the reference
  read out of the symbol property before comparing to the caller-supplied
  reference.
- tests/test_pin_locator_and_component.py — removed three stale mock-based
  Y-axis tests (covered by tests/test_pin_locator_y_flip.py end-to-end);
  rewrote rstrip tests to use a real schematic file so _get_symbol_transform
  is actually exercised.

Verified: tests/test_pin_locator_and_component.py + test_pin_locator_y_flip.py
+ test_get_pin_angle.py + test_move_with_wire_preservation.py — 69 passed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 21:53:21 +01:00
Eugene Mikhantyev
3c225809b9 fix(pin_locator): apply lib→screen Y-flip to get_pin_angle
PR #145 restored the Y-axis flip in WireDragger.pin_world_xy so pin
coordinates now match the schematic (Y-down) frame instead of the
library (Y-up) frame. PinLocator.get_pin_angle was the companion to
that transform but never received the matching fix: it was returning
the library-frame angle (with mirror handling but no Y-flip), so
angles came out 180° off along the Y axis.

This was masked before PR #145 because pin_world_xy was wrong in the
same direction — both functions skipped the Y-flip, so callers that
compared pin endpoints to angles saw a self-consistent picture. Once
pin_world_xy was corrected the inconsistency surfaced.

Apply the same lib→screen Y-flip (negate angle) after the mirror
handling and before the symbol-rotation add, matching pin_world_xy's
order: mirror in lib space → Y-flip → rotate → translate (no
translate for angles since angles are translation-invariant).

Fixes the 24 parametrized cases in
tests/test_get_pin_angle.py::test_get_pin_angle_matches_geometric_expectation
(pin × mirror × rotation matrix). The test derives its expected value
from pin_world_xy itself, making it the canonical geometric oracle.
test_pin_locator_y_flip and test_move_with_wire_preservation continue
to pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 21:50:14 +01:00
Eugene Mikhantyev
84ba778d60 Merge pull request #145 from inktomi/fix/pin-world-y-flip
fix: apply Y-axis flip in WireDragger.pin_world_xy
2026-05-03 21:43:27 +01:00
Eugene Mikhantyev
7bc86854e6 Merge pull request #146 from inktomi/fix/multiline-text-escape
fix: escape newlines in WireManager.add_text
2026-05-03 21:04:06 +01:00
Eugene Mikhantyev
8945b21977 Merge pull request #142 from domesticmouse/npm-audit-fix
Update package-lock.json
2026-05-03 21:00:16 +01:00
Matthew Runo
9ba00102b4 feat: guard SWIG auto-save against external file changes
After every board-mutating SWIG command, kicad_interface._auto_save_board()
unconditionally calls pcbnew.SaveBoard() with the in-memory board. When the
on-disk .kicad_pcb has been modified externally between our LoadBoard and
SaveBoard (KiCad GUI's own save, git checkout, another process), the
in-memory state silently overwrites those external changes - losing data
the user can't see was at risk.

This change records the file's mtime_ns + sha256 at LoadBoard and verifies
the signature matches before each auto-save:

  * If the signature has diverged, refuse the save and attach a structured
    warning to the command result so callers know their mutation is
    in-memory only and they need to reload before retrying.
  * If it matches, copy the existing file to .mcp-backups/<name>.<ts>
    (rotating, keeps last 20) before overwriting.
  * Update the recorded signature after our own writes so subsequent
    saves are not falsely flagged.

Backwards compatible:
  * No tool schemas changed.
  * Successful saves return as before, with an extra `autoSave` field
    when the wrapper observed something noteworthy.
  * Refused saves return success: true (the in-memory mutation did
    succeed) plus warnings: [...] and autoSave.diskChangedExternally,
    so callers can detect the situation programmatically.

Adds tests/test_auto_save_guard.py (10 tests, all passing) covering:
signature math, refusal on external change, backup creation + content,
backup rotation, first-save semantics (no recorded signature proceeds
normally), and skip cases (no board / no path).

Motivation: the aircam-pdb fork-user lost ~480 traces and the full
footprint layout to a silent overwrite incident on 2026-05-03; recovery
was only possible because VS Code's local-history extension happened to
have a snapshot from a few minutes earlier. This guard makes that class
of incident loud and locally recoverable.
2026-05-03 10:04:51 -07:00
Tom
611b363fca Merge pull request #150 from Kletternaut/feature/vscode-clean
feat: multilingual README, VS Code setup guide, add_no_connect handler, tool descriptions, corrected tool counts
2026-05-03 12:01:51 +02:00
Tom
963a39c463 feat: add no_connect handler, server icon, split READMEs by language, VS Code Copilot guide, tool inventory update 2026-05-03 11:51:53 +02:00
Michael Holm
890746c6ac Make project-scope sym-lib-table visible to symbol-discovery tools
search_symbols, list_symbol_libraries, list_library_symbols, and
get_symbol_info previously only consulted the global sym-lib-table. A
library registered with project scope (an entry in
<project>/sym-lib-table) was therefore invisible — even right after
open_project succeeded — making add_schematic_component the only tool
that could see it.

Fix has two parts:

1. Wrap project_commands.open_project and project_commands.create_project
   in handlers that rebuild SymbolLibraryCommands.library_manager against
   the project directory. After open_project, project-scope libraries are
   automatically visible to subsequent search/list/info calls.

2. Add an optional projectPath parameter to the four discovery tools
   (accepts a project directory, .kicad_pro, .kicad_pcb, or .kicad_sch
   path). Stateless callers can resolve project libraries without first
   calling open_project. SymbolLibraryCommands._derive_project_path also
   walks up from schematicPath/boardPath to find the directory that owns
   the project, mirroring the logic in _handle_add_schematic_component.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:35:15 +02:00
Matthew Runo
09ec6aaeb5 fix: escape newlines in WireManager.add_text
The KiCad s-expression parser rejects raw newline and carriage-return
characters inside quoted string literals — a multi-line text annotation
written through `add_text` produced a `.kicad_sch` file that eeschema
silently tolerated but `kicad-cli sch ...` refused with "Failed to load
schematic." The escape pass only handled backslashes and double quotes.

Add `\\n` → `\\\\n` and `\\r` → `\\\\r` to the same escape chain. Order
matters: backslashes are escaped first so we don't double-escape our
own escapes.

A new regression test (`test_escapes_newlines_in_multiline_text`)
checks both that the resulting quoted string literal contains no raw
newline characters and that the file round-trips cleanly through the
sexpdata parser.

End-to-end smoke: a 4-line annotation written through the patched
add_text now passes `kicad-cli sch erc` (exit 0) where the previous
behaviour failed parse.

Note: the same escape gap exists in `_make_hierarchical_label_text`
and `_make_sheet_pin_text` for unescaped quotes/newlines in the user-
supplied text. Not fixed here to keep this PR scoped to the documented
add_text bug; happy to fold it in if a reviewer prefers.
2026-05-01 10:03:49 -07:00
Matthew Runo
bf74b85caf fix: apply Y-axis flip in WireDragger.pin_world_xy
Library symbol pins are stored Y-up (positive Y is upward in the symbol
editor's coordinate system) but `.kicad_sch` is Y-down (positive Y is
downward in the schematic). `pin_world_xy` was returning `sym_y + ry`
without negating the rotated lib Y, so for any non-symmetric symbol
pin 1 and pin 2 ended up at swapped world positions.

For symmetric two-pin passives (R, C non-polarized) this was invisible
because pin 1 and pin 2 are electrically equivalent. For polarized
parts — electrolytic and polymer caps, diodes, MOSFETs, BJTs — it
silently swapped polarity. A label snapped to a polarized cap's pin 1
ended up on pin 2, which is catastrophic at first power-up.

The order matches eeschema's actual transformation:
  mirror in lib space → Y-flip to screen → rotate → translate.

The existing regression test in test_pin_locator_y_flip.py was already
written with the correct expected coordinates but the matching code fix
was never landed; that test now passes.

Three tests in test_move_with_wire_preservation.py had baked the buggy
expected coordinates into their assertions; updated those to the correct
y-flipped values. The touching-pin fixture had to flip R2's Y from
-7.62 to +7.62 so the two pins still meet under the corrected formula.

Verified end-to-end on a 46-component aerospace PDB schematic: all
8 polarized-part pins (4 polymer caps + 4 TVS diodes) now produce
world coordinates that match the labels actually placed in the file.
2026-05-01 10:02:13 -07:00
Brett Morgan
b140015299 Update package-lock.json
This is the results of running `npm audit fix`
2026-05-01 08:29:24 +10:00
Eugene Mikhantyev
d3c01e20bd Merge pull request #139 from mixelpixx/fix/post-pr88-regressions
Fix two regressions from PR #88 (rotate/mirror)
2026-04-29 21:45:47 +01:00
Eugene Mikhantyev
5907954b3e Fix two regressions from PR #88 (rotate/mirror)
1. add_schematic_net_label failed on schematics with no existing labels.
   The clone-based path required a pre-existing label to copy from;
   the documented "fallback to sexpdata" was a misleading log line —
   the RuntimeError was caught and the call silently returned False.
   Restore hand-built sexpdata construction (without the buggy
   fields_autoplaced token, with orientation-aware justify).

2. get_pin_angle returned the wrong angle for every mirrored symbol
   (off by exactly 180°, all rotations, both mirror axes). The
   mirror_x and mirror_y formulas were swapped relative to the
   pin_world_xy convention — pin_world_xy mirrors a position by
   flipping its local axis component, so the matching angle
   transform is (180 - θ) for mirror_x and -θ for mirror_y.

Add regression tests:
- test_add_label_empty_schematic.py — first label on empty schematic,
  orientation-aware justify.
- test_get_pin_angle.py — full 24-case matrix
  (4 rotations × 3 mirror states × 2 pins).
2026-04-29 21:40:39 +01:00
Eugene Mikhantyev
109ba3f23a Merge pull request #88 from tnemrap/fix/rotate-mirror
Fix/rotate mirror
2026-04-29 21:22:52 +01:00
Tom
41b455c4ca Add Star History section to README
Added Star History section with dynamic chart display.
2026-04-29 15:37:08 +02:00
Michael Parment
a6b4f92e4b fix: stub annotations module and add python/ to sys.path in smoke test
Upstream added `from annotations import AnnotationLoader` and moved
`from commands.wire_manager import WireManager` to module-level in
kicad_interface.py. The smoke test now stubs annotations and ensures
python/ is on sys.path so commands.* imports resolve without installing.
2026-04-28 13:38:12 +02:00
Michael Parment
7cafbda127 fix: get_schematic_pin_locations now accounts for mirror flags
Previously get_pin_location and get_pin_angle read symbol state from a
kicad-skip cache that does not reflect (mirror x/y) tokens written by
rotate_schematic_component. Pin coordinates were always computed as if
the symbol was unmirrored.

Fix:
- Added _get_symbol_transform() which reads position, rotation, mirror_x,
  mirror_y, and lib_id directly from the .kicad_sch file via sexpdata +
  WireDragger.find_symbol (the authoritative source after a rotate/mirror)
- get_pin_location now delegates the full transform (mirror → rotate →
  translate) to WireDragger.pin_world_xy, matching the logic used by
  move_schematic_component and rotate_schematic_component
- get_pin_angle now applies mirror-induced angle reflection before adding
  symbol rotation: mirror_x negates the angle, mirror_y reflects across 180°

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 13:36:29 +02:00
Michael Parment
d53533b322 fix: add_schematic_net_label uses kicad-skip clone() instead of sexpdata
Two bugs fixed:

1. fields_autoplaced yes was always injected — caused incorrect visual
   rendering of label text in KiCAD. Removed by using clone() which
   copies an existing label without that field.

2. (justify left bottom) was hardcoded regardless of orientation.
   For orientation 180/270 KiCAD requires (justify right bottom).
   Now set correctly via new_label.effects.justify._tree[1].

Implementation switches from manual sexpdata list construction to
kicad-skip Schematic.label[0].clone(), which produces a structurally
correct label that KiCAD can round-trip without modification.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 13:33:25 +02:00
Michael Parment
53e656b952 fix: rotate_schematic_component uses sexpdata API and drags wires
Previously the handler used kicad-skip to apply rotation and mirror.
kicad-skip has no API for (mirror x/y) on placed symbols, causing:
  'NoneType' object has no attribute 'value'

Fix:
- Rewrote _handle_rotate_schematic_component to use sexpdata (same
  approach as move_schematic_component) for both rotation and mirror
- Added WireDragger.compute_pin_positions_for_rotation: computes old
  and new pin world positions when rotation/mirror changes at fixed (x,y)
- Added WireDragger.update_symbol_rotation_mirror: updates (at) rotation
  and adds/removes/replaces the (mirror x/y) sexpdata token cleanly
- Connected wires now follow pin positions after rotate/mirror via the
  existing WireDragger.drag_wires infrastructure

Tests: 10 unit tests in tests/test_rotate_schematic_mirror.py covering
update_symbol_rotation_mirror, compute_pin_positions_for_rotation, and
a handler smoke test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 13:32:43 +02:00
Eugene Mikhantyev
f9f8e4f7d6 Merge pull request #137 from mixelpixx/fix/pwr-flag-orphan-wire-anchor
Treat PWR_FLAG anchors as connected in orphan-wire detection
2026-04-26 16:08:31 +01:00
Eugene Mikhantyev
7f3a379657 Treat PWR_FLAG anchors as connected in orphan-wire detection
PWR_FLAG instances use a #FLG reference prefix, not #PWR, so their
pin positions were never registered as virtual connection anchors in
_parse_virtual_connections. As a result, find_orphaned_wires reported
wire ends terminating on a PWR_FLAG as dangling. Other call sites
(schematic_analysis.py:127, kicad_interface.py:3814) already recognize
#FLG as a power symbol; align this site with them.

Also coerce a previously-validated Optional[int] to int in board/layers.py
so the file passes mypy (required by the pre-commit hook); behavior is
unchanged because the value is already None-checked above.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 16:05:35 +01:00
Eugene Mikhantyev
a87c4515c5 Merge pull request #136 from mixelpixx/fix/auto-junction-sync-followups
Auto-sync schematic junctions on wire and symbol mutations
2026-04-26 15:43:20 +01:00
Eugene Mikhantyev
193e1a603f Drop research-project disclaimer from CHANGELOG header
The disclaimer is project-stance commentary, not a changelog entry; moved
to local notes outside the repo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 15:28:32 +01:00
Eugene Mikhantyev
f11d453c31 Tidy auto-junction-sync code
- Refactor _handle_rotate_schematic_component to use raw sexp throughout
  and write the schematic once instead of three times
- Hoist sexpdata and WireManager imports to module scope; drop the
  unnecessary underscore aliases in move/rotate handlers
- Move WireManager._SUB_UNIT_RE to the top of the class body
- Promote the per-call Symbol("symbol")/Symbol("unit") allocations in
  _parse_lib_pins / _collect_pin_positions to module-level _SYM_*
  constants
- Document the assumption behind _SUB_UNIT_RE's greedy match

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 15:13:47 +01:00
Eugene Mikhantyev
7a6558b9fa Auto-sync junctions on wire/symbol mutations
Replaces the manual add_schematic_junction tool with automatic junction
management. WireManager.sync_junctions inserts/removes junction dots
based on wire endpoints plus component pin positions and is invoked
after add_wire, add_polyline_wire, delete_wire, move, and rotate.

- Pin-aware: parses lib_symbols and applies KiCad's mirror/rotate/
  translate transform to compute world pin coordinates
- Multi-unit safe: filters lib_symbols sub-units by the placed
  symbol's (unit N) field plus the unit-0 common body
- Removes the now-unused WireManager.add_junction static method
- Updates CHANGELOG [Unreleased] with the tool removal notice
- Adds .mcp.json to .gitignore (machine-local paths)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 14:59:47 +01:00
mixelpixx
17a2e14e9c Merge pull request #131 from 7tobias/fix/add-layer-kicad9-api
fix(layers): use KiCad 9 API for add_layer
2026-04-26 01:55:24 -04:00
mixelpixx
d957a22144 Merge pull request #125 from daeng5913-create/fix/windows-kicad10-python-path
fix(windows): support KiCad 10.0 pcbnew path (bin/Lib/site-packages)
2026-04-25 19:59:09 -04:00
mixelpixx
b386bc3be5 Merge pull request #130 from 7tobias/feat/delete-trace-wildcard
feat(routing): support '*' wildcard in delete_trace net_name
2026-04-25 19:58:47 -04:00
mixelpixx
c9a98e44be Merge pull request #132 from 7tobias/fix/connect-to-net-pcb-pad-assignment
fix(connect_to_net): also assign net to PCB pad so save_project persi…
2026-04-25 19:13:28 -04:00
Eugene Mikhantyev
c62a859e0b Merge pull request #134 from vdawger/fix/import-time-logging-crash
fix: prevent import-time crash when ~/.kicad-mcp/ is not writable
2026-04-25 23:31:09 +01:00
Eugene Mikhantyev
0efcd923c1 Merge pull request #133 from johndev7000/fix/pin-locator-y-double-negation
fix: remove duplicate Y-axis negation in PinLocator.get_pin_location
2026-04-25 23:29:43 +01:00
Eugene Mikhantyev
9f932b13ef Merge pull request #127 from matthewellis/fix/jlcpcb-downloader-improvements
fix: improve JLCPCB downloader robustness and disk usage
2026-04-25 23:20:10 +01:00