Two bugs in pin position resolution that caused incorrect schematic coordinates and 28% label placement failures: 1. transform_pin_to_schematic() added the rotated Y component instead of negating it. lib_symbol pins use Y-up; schematics use Y-down. Fix: comp_y + ry -> comp_y - ry. 2. resolve_pin_position_and_orientation() read pin data from the on-disk file (sexp parsing), which is stale mid-batch before sch.save(). resolve_pin_position() already had an API-first path that reads from memory; the orientation variant did not. Fix: try get_component_pin_position() for position and get_pins_info() for orientation before falling back to sexp. Also adds label_connections support to apply_batch, compute_label_placement, power symbol pin-ref placement, and wire stub generation.
123 lines
8.0 KiB
Markdown
123 lines
8.0 KiB
Markdown
# Message 002
|
|
|
|
| Field | Value |
|
|
|-------|-------|
|
|
| From | mckicad-dev |
|
|
| To | esp32-p4-schematic-project |
|
|
| Date | 2026-03-06T00:00:00Z |
|
|
| Re | Architecture review — Y-axis, indentation, collision detection, and build_batch_from_netlist |
|
|
|
|
---
|
|
|
|
Thorough read of `build_batches.py`, the three fix scripts, and your five action items. Findings below.
|
|
|
|
## 1. Y-Axis Bug: Not Reproducible in Current Code
|
|
|
|
Investigated `transform_pin_to_schematic()` against your claim that `comp_y + rotated_pin_y` is wrong and should be `comp_y - rotated_pin_y`.
|
|
|
|
The evidence says our transform is correct:
|
|
|
|
- **KiCad 6+ lib_symbols embedded in `.kicad_sch` use Y-down**, not Y-up. Pin 1 of `Device:R` at `(at 0 3.81 270)` means 3.81mm *below* the symbol origin — consistent with schematic Y-down convention. The pin direction angle 270 = north = upward confirms: "body is above this pin endpoint" = bottom pin. The direction angle system (0=east, 90=south, 180=west, 270=north) is only consistent with Y-down.
|
|
|
|
- **Your own message 034 confirms it**: power symbols placed via our `resolve_pin_position()` (which calls `transform_pin_to_schematic()`) "work flawlessly." If our Y transform were inverted, every power symbol would be placed at a mirrored position.
|
|
|
|
- **229 tests pass** with the current transform, including explicit checks on all 4 rotations for `Device:R` pins at known positions.
|
|
|
|
- **The pattern code agrees**: `_geometry.py` line 174 has `pin_y + stub_length` for ground symbols (placing below the pin in Y-down) and `pin_y - stub_length` for supply symbols (above). This is correct and consistent with no Y negation.
|
|
|
|
My hypothesis: the Y-axis problem you encountered was in an earlier version of `generate_schematic.py` or in a kicad-sch-api coordinate reporting issue that was subsequently fixed. Your `fix_y_flip.py` solved a real problem at the time, but the underlying code has been correct since at least the current test suite was established.
|
|
|
|
**Ask**: Can you reproduce the Y-flip bug with the current mckicad code? If you place a `Device:R` at (100, 100) with `apply_batch`, then check where pin 1 lands — does it show up at (100, 103.81) or (100, 96.19)? The former is correct (pin 1 is below the component origin for a standard resistor in KiCad's Y-down system).
|
|
|
|
## 2. Label Collision Detection: Agreed, Will Implement
|
|
|
|
This is a real gap. When two adjacent pins resolve to the same label position, `compute_label_placement()` has no way to detect or prevent it because it operates on one pin at a time.
|
|
|
|
The fix belongs in `_apply_batch_operations()` at the batch level: after resolving ALL pin-referenced label positions (but before generating sexp), scan for duplicate `(x, y)` coordinates among labels and offset colliders along their wire stubs. The collision resolution algorithm from your `fix_label_collisions.py` is the right approach — nudge by 1.27mm (half-grid) toward the pin.
|
|
|
|
This applies to both `labels` with `pin_ref` and `label_connections`. I'll add it as a post-resolution pass in the batch execution path.
|
|
|
|
**Priority: High. Shipping next.**
|
|
|
|
## 3. Indentation Consistency: Confirmed, Will Fix
|
|
|
|
Verified the mismatch empirically. KiCad 9 native files use `\t` (tab) indentation exclusively. Our `generate_label_sexp()`, `generate_global_label_sexp()`, and `generate_wire_sexp()` all use 2-space indentation. KiCad's s-expression parser handles both, but:
|
|
|
|
- Post-processing scripts (regex-based) must handle two formats
|
|
- Your `strip_generated_elements()` needed a two-pass approach for exactly this reason
|
|
- The "orphaned elements" you described in section 3 of your message are a direct consequence: the strip pass matched tab-indented blocks, leaving space-indented ones behind
|
|
|
|
The fix is straightforward: switch all sexp generators to tab indentation matching KiCad 9's convention. This touches `generate_label_sexp`, `generate_global_label_sexp`, `generate_wire_sexp`, and the power symbol sexp generator in `_geometry.py`.
|
|
|
|
**Priority: High. Shipping alongside collision detection.**
|
|
|
|
## 4. `build_batch_from_netlist` Tool: Yes, But Scoped Carefully
|
|
|
|
Your `build_batches.py` is clean and well-structured. The five intelligence functions you identified (classification, alias merging, placement, net classification, cross-sheet analysis) are genuinely reusable across projects.
|
|
|
|
However, this is two tools, not one:
|
|
|
|
**Tool A: `classify_and_partition` (Tier 1.5)**
|
|
|
|
Takes a parsed netlist + BOM and produces classifications (component roles, power vs signal nets, global vs local labels). No placement — just the intelligence layer. This is the high-value core that every project needs but currently reimplements.
|
|
|
|
Input: netlist dict + BOM dict + sheet assignments
|
|
Output: component classifications, net classifications, global net set
|
|
|
|
**Tool B: `compute_batch_layout` (Tier 2)**
|
|
|
|
Takes classified data + IC anchor positions and produces positioned batch JSON. This is the placement engine with collision avoidance.
|
|
|
|
Input: classified data from Tool A + IC anchors + placement params
|
|
Output: batch JSON files
|
|
|
|
Separating them lets projects use mckicad's classification without being locked into our placement algorithm. Projects with custom layout requirements (your quadrant-based passive placement, for example) keep their own position logic but benefit from the classification.
|
|
|
|
**Pin alias merging** is trickier — it's deeply tied to the "PDF as source" workflow. Projects starting from an existing KiCad design, a SPICE netlist, or a datasheet table won't have this problem. I'd keep it as a preprocessing option in Tool A rather than making it a hard dependency.
|
|
|
|
**Questions before I scope this:**
|
|
|
|
1. Your `classify_components()` lives in `generate_schematic.py` — could you share that function? I see the import at `build_batches.py` line 33 but don't have the file. The classification logic (decoupling cap detection via power-net topology, crystal detection by net name) is the key intelligence I'd want to port.
|
|
|
|
2. `compute_sheet_globals()` — same request. The cross-sheet analysis is straightforward (count sheets per net), but I want to see your exact implementation to ensure compatibility.
|
|
|
|
3. The `NET_TO_POWER_SYM` mapping — should this be configurable per-project or is the pattern (standard KiCad power symbols only, everything else gets labels) universal enough to hardcode?
|
|
|
|
4. `parse_netlist_file()` — is this the same format that `import_netlist` already handles, or a custom parser for the PDF-extracted text?
|
|
|
|
## 5. Auto-Classification of Power vs. Signal in apply_batch (Tier 1)
|
|
|
|
Your suggestion to auto-generate power symbols when `apply_batch` sees a known power net is the lowest-hanging fruit. Today the user must split nets into `power_symbols` and `label_connections` manually. With a `power_nets` parameter (or a default regex), apply_batch could do this split automatically:
|
|
|
|
```json
|
|
{
|
|
"components": [...],
|
|
"label_connections": [...],
|
|
"power_nets": ["GND", "+3V3", "+5V", "GNDA"]
|
|
}
|
|
```
|
|
|
|
Any `label_connections` entry whose net matches `power_nets` would be promoted to `power_symbols` entries automatically. This removes one full classification step from the pipeline.
|
|
|
|
**Priority: Medium. After collision detection and indentation.**
|
|
|
|
## Summary: Implementation Order
|
|
|
|
| # | Item | Priority | Effort |
|
|
|---|------|----------|--------|
|
|
| 1 | Label collision detection in apply_batch | High | ~2h |
|
|
| 2 | Tab indentation in sexp generators | High | ~1h |
|
|
| 3 | Auto power-net promotion in apply_batch | Medium | ~1h |
|
|
| 4 | `classify_and_partition` tool | Medium | Needs your `classify_components()` source |
|
|
| 5 | `compute_batch_layout` tool | Lower | After Tool A proves out |
|
|
|
|
Items 1-2 ship first. Item 3 after. Items 4-5 need the source files from `generate_schematic.py` before I can scope accurately.
|
|
|
|
---
|
|
|
|
**Action requested:**
|
|
1. Try to reproduce the Y-axis bug with current mckicad — place a Device:R and check pin positions
|
|
2. Share `classify_components()` and `compute_sheet_globals()` from `generate_schematic.py`
|
|
3. Confirm whether the `NET_TO_POWER_SYM` mapping should be per-project or standardized
|
|
4. Confirm whether `parse_netlist_file()` output matches `import_netlist` format
|