Skip to content

feat: KiCad plugin fixes, zone/keepout extraction, board viewer improvements#17

Open
RolandWa wants to merge 34 commits intobbenchoff:mainfrom
RolandWa:main
Open

feat: KiCad plugin fixes, zone/keepout extraction, board viewer improvements#17
RolandWa wants to merge 34 commits intobbenchoff:mainfrom
RolandWa:main

Conversation

@RolandWa
Copy link
Copy Markdown

@RolandWa RolandWa commented Apr 1, 2026

This PR brings seven commits that fix the KiCad plugin connection layer, improve
the PCB board viewer, and add full keepout rule area support across extraction,
visualization, and routing enforcement.


KiCad plugin fixes

  • Fixed SWIG plugin not loading on Windows due to hard-coded paths
  • IPC API socket/token now auto-discovered from KiCad environment variables
    with fallback scan
  • Track and via extraction now correctly converts nm -> mm and normalises KiCad
    proto layer names (BL_F_Cu -> F.Cu)
  • Tracks rendered with RoundCap/RoundJoin pen caps matching KiCad's style

Copper zone extraction and rendering

  • _extract_zones() reads board.get_zones() via IPC, collecting both the
    zone outline and per-layer filled_polygons (post-pour fills)
  • _draw_zones() renders filled polygons with semi-transparent layer-colored
    fills and respects per-layer visibility checkboxes

Keepout rule area support

Extraction (rich_kicad_interface.py)
_extract_zones() now returns a (zones, keepouts) tuple. Rule areas
(ZT_RULE_AREA) are extracted with all five constraint flags from the
rule_area_settings proto: keepout_tracks, keepout_vias, keepout_copper,
keepout_pads, keepout_footprints.

Domain model (board.py)
Added keepouts: List[Dict] field to the Board dataclass.

Router enforcement (unified_pathfinder.py)
New _apply_keepout_obstacles(board) called at the end of initialize_graph():

  • Module-level _points_in_polygon() NumPy ray-casting helper; vectorised over
    the full (Nx x Ny) lattice plane in one pass per keepout.
  • Sets base_cost = 1e9 on planar edges when keepout_tracks=True, via edges
    when keepout_vias=True. Follows the same pattern as
    _apply_via_keepouts_to_graph().
  • CuPy (GPU) array synced back after modification.

Visualization (main_window.py)

  • _draw_keepouts() renders each keepout outline as a dashed red
    semi-transparent polygon, respecting layer visibility.
  • Paint event now calls _draw_keepouts() when the "Keepouts" checkbox is
    enabled. (The checkbox existed but had no effect: the method and call site
    were missing.)

Interactive inspection (main_window.py)
Right-clicking inside a keepout area shows a context menu with the keepout name,
affected layers, and per-constraint status:

Keepout: GND_Taper_LV
─────────────────────────
Layers: F.Cu, F.SilkS
─────────────────────────
✓  No Tracks
✓  No Vias
✘  No Copper Fills
✓  No Pads
✓  No Footprints

✘ = prohibited · ✓ = allowed. Right-clicking outside any keepout shows nothing.


Log hygiene

_draw_tracks, _draw_vias, and _draw_zones logged at INFO on every paint
event (~50 ms during panning/zooming), producing hundreds of identical lines per
session. All per-frame diagnostic messages downgraded to DEBUG. Genuine warnings
(invalid data, transform failures) remain at WARNING.


Testing

Tested on TestBackplane.kicad_pcb (18-layer, 1604 pads, 512 routable nets,
KiCad 9.0, RTX 4090):

  • Copper-only keepouts extracted and rendered correctly; router skips blocking
    as expected (copper fills do not restrict track/via routing).
  • Track keepout (In1.Cu/In2.Cu, keepout_tracks=True): router blocks planar
    edges on the affected layers through the keepout polygon.
  • Right-click popup shows correct per-constraint flags for all tested keepout
    types.
  • Log file stays clean during normal GUI use.

Files changed

orthoroute/infrastructure/kicad/rich_kicad_interface.py track/via/zone/keepout extraction
orthoroute/domain/models/board.py keepouts field on Board
orthoroute/presentation/plugin/kicad_plugin.py SWIG fixes; keepout wiring
orthoroute/algorithms/manhattan/unified_pathfinder.py _apply_keepout_obstacles()
orthoroute/presentation/gui/main_window.py zone/keepout drawing, context menu, log fixes

RolandW and others added 30 commits April 1, 2026 15:01
…plugin path

- Add swig_init.py: SWIG ActionPlugin that registers toolbar button and
  launches main.py as subprocess in plugin mode (kipy auto-discovers IPC
  socket, no KICAD_API_SOCKET env var needed)
- Cross-platform support: per-platform Python exe, kicad bin dir,
  3rdparty site-packages detection (Windows/Linux/macOS)
- Set QT_PLUGIN_PATH so PyQt6 finds platform plugins in subprocess env
- Windows: set PYTHONHOME, PYTHONUTF8, PATH from kicad-cmd.bat pattern
- Rewrite build.py: deploy to KiCad 3rdparty, ZIP for PCM, OneDrive
  lock resilience (incremental clean, fallback ZIP naming)
- Lazy imports in __init__.py files to avoid pcbnew crash at module load
- Add debug logging to main.py and kicad_plugin.py for plugin launch
- rich_kicad_interface.py: correct IPC API attribute access for tracks/vias,
  nm->mm conversion, BoardLayer enum name mapping (BL_F_Cu -> F.Cu),
  new _extract_vias() method
- main_window.py: normalize BL_* layer names in paint loop,
  RoundCap/RoundJoin pen for track rendering
- .gitignore: exclude copy_to_kicad.ps1 (local dev tool only)

Co-authored-by: RolandW <RolandW@users.noreply.github.com>
- rich_kicad_interface.py: implement _extract_zones() using KiCad IPC API
  board.get_zones(); skips rule areas (logged by name); extracts outline
  and filled_polygons per layer with nm->mm conversion and BL_* layer name
  mapping; logs skip reasons for rule area zones
- main_window.py: add _draw_zones() with semi-transparent layer-colored fills
  respecting visible_layers checkbox; wired into paint loop under show_zones toggle
- DRC extraction: downgrade 'No module named kicad' from WARNING to INFO
- Orphaned pads: downgrade WARNING to DEBUG, clean up message prefix

Co-authored-by: RolandW <RolandW@users.noreply.github.com>
… them

- rich_kicad_interface: _extract_zones() returns (zones, keepouts) tuple;
  rule areas extracted with full constraint flags from rule_area_settings proto
- board.py: added keepouts: List[Dict] field to Board domain model
- kicad_plugin: store board_data; attach board.keepouts before routing
- unified_pathfinder: add _apply_keepout_obstacles(board) method with
  vectorised point-in-polygon test (ray casting); blocks track edges when
  keepout_tracks=True and via edges when keepout_vias=True; called at end
  of initialize_graph(); add module-level _points_in_polygon() numpy helper

Co-authored-by: RolandW
Paint event now calls _draw_keepouts() when show_keepouts=True.
New _draw_keepouts() renders each keepout outline as a dashed red
semi-transparent polygon, respecting layer visibility.

Previously the checkbox toggled the flag but triggered no drawing
because no _draw_keepouts() method or call existed.

Co-authored-by: RolandW
Right-clicking inside a keepout area now shows a popup with:
- Keepout name and affected layers
- Per-constraint status with check/cross mark:
    No Tracks / No Vias / No Copper Fills / No Pads / No Footprints

Uses ray-casting PIP test (_point_in_polygon) against keepout outline
after converting screen coords -> world (mm) coords via _screen_to_world().
Clicking outside any keepout shows nothing.

Co-authored-by: RolandW
_draw_tracks, _draw_vias, _draw_zones were logging at INFO level on every
paint event (~50ms), flooding the log with hundreds of lines per session.
Downgraded to DEBUG: called/completed summaries, VIEWPORT DEBUG, COORD VERIFY,
TRACK COORDS, TRACK DRAWN, TRACK LAYER FILTERED messages.
Genuine warnings (invalid data, transform failures) remain at WARNING.

Co-authored-by: RolandW
build.py --deploy and ZIP builds were missing orthoroute.json (the
runtime config file). Added it to the extra-files copy loop and to
the required-files validation list so a broken build fails fast.

Co-authored-by: RolandW
- Build: corrected ZIP name, noted orthoroute.json now included and validated
- KiCad integration: document IPC data extraction (tracks/vias/zones/keepouts),
  fix plugin folder name (underscores not dots), point to plugin logs,
  add dev-sync workflow (copy_to_kicad.ps1)
- Key files table: add rich_kicad_interface, main_window, board.py
- Routing: document _apply_keepout_obstacles() keepout enforcement
- PCB viewer: add display features table and right-click keepout menu docs
- Keepout rule areas: full data structure reference section
- Common pitfalls: add log-spam regression note
- Project status: mark track/via/zone/keepout work as complete

Also fix .gitignore: .github/ was accidentally listed under 'Generated
documentation', preventing copilot-instructions.md from being tracked.

Co-authored-by: RolandW
- 3,311 GPU paths profiled across 40 iterations
- GPU Dijkstra kernel: 16ms avg (5% of per-net time)
- Python->CUDA MULTI-LAUNCH loop: ~296ms avg (95% of per-net time)
- PERSISTENT KERNEL already compiled but not activated — fix is bbenchoff#1 priority
- Via rebuild (_rebuild_via_usage_from_committed): 275ms avg, 1% of total
- Expected speedup if persistent kernel activated: 10-20x (44min -> 3-5min)

Updated docs/optimization/:
- OPTIMIZATION_QUICK_REF.md: corrected priority table, GPU architecture notes
- optimization_baseline_2026-04-03.md: GPU analysis section, revised roadmap
- README.md: corrected targets and phases
…t persistent kernel fix

- All 210 logger.info() calls converted to logger.debug()
- [GPU-SEEDS] Path found in X iterations promoted to WARNING (key timing metric)
- Result: log size 861MB -> ~10-20MB per run
- Document why _enable_persistent_kernel = False:
  persistent kernel lacks allowed_bitmap param (owner-aware node filtering)
  needed to prevent routing through other nets' pad nodes
  fix: add uint32* allowed_bitmap + bitmap_words to CUDA PTX + IN_BITMAP check
  expected speedup: 20x per net, 48min -> ~3min total run
- Add allowed_bitmap (uint32*), bitmap_words, use_bitmap params to
  persistent_sssp_kernel PTX -- owner-aware node filtering now works
- IN_BITMAP check in inner loop matches wavefront_expand_all kernel
- launch_persistent_kernel() updated to accept/pass bitmap params
- _enable_persistent_kernel = True (was False since v1)
- Bump _persistent_kernel_version to 3 (forces recompile)

Verified on TestBackplane.kicad_pcb:
  512/512 nets converged, 0 overuse, 70 iterations
  Run time: 25 min (was 47.9 min) -- 2x speedup
  Per-net kernel GPU time: 3-24ms (was 306ms multi-launch)
  Per-iteration wall time: ~20s (was ~39s)
  Note: Python bitmap construction overhead ~50ms/net limits full 20x gain
- Run time: 47.9min -> 25min (2x speedup, 70 iters, 512/512 converged)
- New bottleneck: Python bitmap construction ~50ms/net (65% of per-net)
  int(seed) loop causes ~100 GPU->CPU syncs before kernel launch
  Fix: cp.scatter_add vectorized bitmap set (one kernel call)
  Expected gain: 25min -> ~8-10min (3-4x additional speedup)
- Mark MULTI-LAUNCH overhead as DONE in priority table
…edup)

- cuda_dijkstra.py: skip cp.asarray() if allowed_bitmap already CuPy
- cuda_dijkstra.py: fix cupyx.scatter_add bitmap OR corruption (build
  zero force_mask then OR into roi_bitmaps to avoid bit-flip on existing bits)
- unified_pathfinder.py: add node_owner_gpu (CuPy mirror of node_owner)
- unified_pathfinder.py: sync node_owner_gpu once per iter in _rebuild_node_owner
- unified_pathfinder.py: batch-write to node_owner_gpu in _mark_via_barrel_ownership_for_path
- unified_pathfinder.py: _build_owner_bitmap_for_fullgraph builds bitmap
  entirely on GPU (no CPU computation, no PCIe upload) when node_owner_gpu available
- unified_pathfinder.py: CPU fallback uses reshape+matmul instead of np.add.at
- docs: record run history (runs 3+4), no-improvement findings,
  mystery 70-100ms gap documented, profiling plan added
- _path_to_edges: replace nested Python loops + per-call GPU->CPU transfer
  with cached CPU arrays (_indptr_cpu/_indices_cpu) and NumPy broadcast
  broadcast matrix search. 21.6ms -> 0.12ms avg per call (~180x)
- commit_path: replace per-element GPU indexing loop with cupyx.scatter_add
  (GPU) / np.add.at (CPU) batch scatter. 6.6ms -> 0.73ms avg per call (~9x)
- Combined per-net Python overhead reduced from ~28ms to ~0.85ms
- Profiling: _path_to_edges 5126 calls, 616ms total (was 73s for same calls)
- New bottleneck: _rebuild_via_usage_from_committed at 211ms/iter
- init_logging() respects ORTHO_DEBUG (was hardcoded DEBUG)
- File log: WARNING normal / DEBUG when ORTHO_DEBUG=1
- Console: always WARNING
- Screenshots: disabled by default, enabled with ORTHO_DEBUG=1
- image.save() offloaded to daemon thread (eliminates 6s inter-iter stall)
- setup_logging(): removed duplicate component_levels loop, added dead-code note
- performance_utils.profile_time: no-op unless ORTHO_DEBUG=1
- unified_pathfinder: removed @profile_time decorators (controlled via env var now)
- [ROUTING START] + iter=/total= timing added to normal-mode log
- README + copilot-instructions updated with ORTHO_DEBUG documentation
- Contributing.md: full spec for headless routing regression test
  - Read-only board (never modify TestBoards/TestBackplane.kicad_pcb)
  - Golden metrics: nets_routed, overuse_edges exact-or-better
  - Iterations: +-10% tolerance (non-deterministic router)
  - Timing: <=110% of golden (no performance regression)
  - Stores baseline in tests/golden_metrics.json (committed to repo)
  - Skeleton test code in tests/test_regression_backplane.py
  - --export-metrics flag needed in main.py CLI
- copilot-instructions.md: added to Needs Work list
… baseline

- conftest.py: _parse_lattice_from_log() parses 'Lattice: NxNxN = N nodes' WARNING;
  log_lattice fixture exposes it session-wide
- test_backplane.py: TestLatticeSize group (4 tests)
    - test_lattice_nodes: HARD FAIL if nodes != golden_board[lattice_nodes] (446,472)
    - test_lattice_layers: HARD FAIL if layers != golden_board[copper_layers] (18)
    - test_lattice_dimensions_reported: SOFT WARN on zero dims
    - test_lattice_node_product: SOFT WARN if cols*rows*layers != reported nodes
- docs/optimization/optimization_baseline_2026-04-05.md: Apr-5 run 717.5s (12min),
  4x faster than Apr-3 baseline
- Regression: 14 -> 18 passing, 25 skipping, 0 failing
conftest.py - _parse_routing_result_from_log now extracts:
  - [CLEAN] All nets routed with zero overuse  -> clean_zero_overuse flag
  - ROUTING COMPLETE: All N nets routed ...    -> routing_complete_banner flag
  - [FINAL] Edge routing converged (N barrel)  -> final_barrel_conflicts count
  - Applied N tracks and N vias to KiCad       -> tracks_written / vias_written
  Convergence is now driven by hard [CLEAN]/ROUTING COMPLETE markers, not just
  the [ITER N] CONVERGED flag.

test_backplane.py - TestRoutingQuality gets 2 new SOFT WARN tests:
  - test_routing_complete_banner: checks ROUTING COMPLETE line present
  - test_clean_zero_overuse: checks [CLEAN] All nets... line present

Results: 45 passed, 0 skipped (was 18 passed, 25 skipped)
- Two-run comparison table (run1 golden ref vs run2 confirmed log)
- PERSISTENT kernel confirmed in run2 (run_20260405_223659.log line 383)
- Full 67-iter convergence trend: iter 1-6, 10, 20, 30, 40, 50, 60-67
  with nets_active, overuse edges, barrel conflicts, iter/total time
- Key observations: warm-up, pres_fac cap at 64, net exclusion/retry pattern,
  barrel floor at 379, final sprint at <5s/iter
- Regression test table updated: 45 passing (was 18), added lattice group
OpenFixture Developer added 4 commits April 10, 2026 14:27
Unit tests added:
- test_edge_accountant.py - PathFinder edge cost and congestion tracking
- test_pathfinding.py - Dijkstra pathfinding methods (52 failing - mock issues)
- test_real_global_grid.py - Grid coordinate conversion and validation
- test_roi_extraction.py - Region of interest extraction (20 failing - mock issues)
- test_rrg.py - Routing resource graph data structures
- test_via_conflicts.py - Via barrel conflict detection (16 failing - mock issues)
- test_smoke.py - Quick smoke tests for regression suite

Test results: 115 passing, 52 failing (all failures due to improper mocking)
See test_failure_analysis.md for detailed analysis.

Optimization baseline updates:
- Added optimization_baseline_2026-04-08.md documenting latest routing run
- Updated README.md with performance regression analysis (17.5 min vs 11.96 min best)
- Updated OPTIMIZATION_QUICK_REF.md with current status and investigation priorities

Performance summary:
- April 8 run: 17.5 min (67 iterations, 15.7s avg) - 46% regression vs April 5
- All 512 nets routed successfully with zero overuse
- Barrel conflicts: 359 (19% improvement)
- Investigation needed: verify GPU persistent kernel usage and profile overhead
- Add 167 unit tests (all passing)
- Add 63 regression tests validating against golden result
- Document golden result: April 10, 2026 (512/512 nets, zero overuse, 18.4 min)
- Create tests/run_golden_regression.md with test documentation
- Update golden_board.json and golden_metrics.json with validated baselines
- Add docs/optimization/golden_result_2026-04-10.md with complete metrics

Test Results:
- 167/167 unit tests passing
- 47/63 regression tests passing (16 expected skips/fails)
- All routing quality metrics validated against golden baseline
- GPU hardware functional (NVIDIA Compute Capability 75)
- Create scripts/analyze_log.py: Standalone log parser for routing metrics extraction and golden comparison
- Create scripts/optimize_and_validate.ps1: PowerShell automation wrapper for full optimization cycle
- Create scripts/README.md: Comprehensive documentation for automation scripts
- Create tests/regression/smoke_metrics.json: Golden thresholds for smoke test (100 nets, 4 layers)
- Create docs/optimization/optimization_workflow.md: Complete optimization workflow guide (800+ lines)
- Create docs/optimization/baseline_template.md: Standardized template for future optimization baselines
- Extend launch_kicad_debug.ps1: Add -RunValidation flag for post-KiCad smoke test
- Extend copy_to_kicad.ps1: Add -Validate switch for deployment validation
- Update optimization docs: Add workflow references to QUICK_REF, README, golden_result_2026-04-10
- Update historical baselines: Add workflow tool references to 2026-04-03, 04-05, 04-08

Workflow enables fast iterative optimization: make change → sync → smoke test (30s) → analyze → commit
Exit codes: 0=PASS, 1=FAIL routing, 2=WARN regression, 3=ERROR environment
Smoke test becomes primary validation checkpoint (middle complexity: 100 nets vs 512 backplane)
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.

1 participant