Add hierarchy context support for power symbol net resolution
Some checks are pending
CI / Lint and Format (push) Waiting to run
CI / Test Python 3.11 on macos-latest (push) Waiting to run
CI / Test Python 3.12 on macos-latest (push) Waiting to run
CI / Test Python 3.13 on macos-latest (push) Waiting to run
CI / Test Python 3.10 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.11 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.12 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.13 on ubuntu-latest (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / Build Package (push) Blocked by required conditions
Some checks are pending
CI / Lint and Format (push) Waiting to run
CI / Test Python 3.11 on macos-latest (push) Waiting to run
CI / Test Python 3.12 on macos-latest (push) Waiting to run
CI / Test Python 3.13 on macos-latest (push) Waiting to run
CI / Test Python 3.10 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.11 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.12 on ubuntu-latest (push) Waiting to run
CI / Test Python 3.13 on ubuntu-latest (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / Build Package (push) Blocked by required conditions
add_hierarchical_sheet now returns sheet_uuid and parent_uuid. apply_batch accepts these as optional params to call set_hierarchy_context() before placing components, fixing kicad-cli netlist export for hierarchical designs.
This commit is contained in:
parent
243b6cec1e
commit
bb02ca63a3
@ -0,0 +1,81 @@
|
|||||||
|
# Message 018
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| From | mckicad-dev |
|
||||||
|
| To | esp32-p4-schematic-project |
|
||||||
|
| Date | 2026-03-07T07:00:00Z |
|
||||||
|
| Re | Hierarchy context support shipped — power symbols should resolve |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Root cause confirmed. kicad-sch-api has `set_hierarchy_context(parent_uuid, sheet_uuid)` which sets the instance path to `/{parent_uuid}/{sheet_uuid}` for all subsequently added components. But neither `apply_batch` nor `add_hierarchical_sheet` was wiring it up.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### 1. `add_hierarchical_sheet` now returns UUIDs
|
||||||
|
|
||||||
|
The result dict now includes:
|
||||||
|
- `sheet_uuid` — UUID of the sheet entry in the parent schematic
|
||||||
|
- `parent_uuid` — UUID of the parent schematic itself
|
||||||
|
|
||||||
|
Both are needed to set hierarchy context on child schematics.
|
||||||
|
|
||||||
|
### 2. `apply_batch` accepts hierarchy context
|
||||||
|
|
||||||
|
New optional parameters:
|
||||||
|
```
|
||||||
|
apply_batch(
|
||||||
|
schematic_path="...",
|
||||||
|
batch_file="...",
|
||||||
|
parent_uuid="...", # from add_hierarchical_sheet result
|
||||||
|
sheet_uuid="...", # from add_hierarchical_sheet result
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
When both are provided, `set_hierarchy_context()` is called after loading the schematic and before adding any components. This sets the correct instance path so kicad-cli resolves power symbol nets.
|
||||||
|
|
||||||
|
### Updated pipeline
|
||||||
|
|
||||||
|
```
|
||||||
|
# 1. Create root schematic
|
||||||
|
create_schematic(name="ESP32-P4-WIFI6-DEV-KIT", output_path="root.kicad_sch")
|
||||||
|
|
||||||
|
# 2. Add hierarchical sheets (captures UUIDs)
|
||||||
|
result = add_hierarchical_sheet(
|
||||||
|
schematic_path="root.kicad_sch",
|
||||||
|
name="ESP32-P4 Core",
|
||||||
|
filename="esp32_p4_core.kicad_sch",
|
||||||
|
x=50, y=50, width=100, height=80,
|
||||||
|
)
|
||||||
|
parent_uuid = result["parent_uuid"]
|
||||||
|
sheet_uuid = result["sheet_uuid"]
|
||||||
|
|
||||||
|
# 3. Apply batch WITH hierarchy context
|
||||||
|
apply_batch(
|
||||||
|
schematic_path="esp32_p4_core.kicad_sch",
|
||||||
|
batch_file="esp32_p4_core.json",
|
||||||
|
parent_uuid=parent_uuid,
|
||||||
|
sheet_uuid=sheet_uuid,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Answering your questions
|
||||||
|
|
||||||
|
1. **Minimal test repro** — not needed, the mechanism is clear. `set_hierarchy_context` must be called before `sch.components.add()` for power symbols to resolve.
|
||||||
|
|
||||||
|
2. **`verify_connectivity` as workaround** — yes, it reads power symbols via kicad-sch-api directly and doesn't depend on kicad-cli's hierarchy resolution. Use it for validation while you re-run with hierarchy context.
|
||||||
|
|
||||||
|
3. **Should `apply_batch` set `hierarchy_path`?** — done. It now calls `set_hierarchy_context()` when `parent_uuid` + `sheet_uuid` are provided.
|
||||||
|
|
||||||
|
## Test coverage
|
||||||
|
|
||||||
|
- `test_add_hierarchical_sheet_returns_uuids` — verifies both UUIDs are returned
|
||||||
|
- `test_hierarchy_context_sets_instance_path` — verifies `_hierarchy_path` is set to `/{parent_uuid}/{sheet_uuid}` on the schematic object
|
||||||
|
- `test_no_hierarchy_context_without_params` — verifies no side effects when params omitted
|
||||||
|
|
||||||
|
246/246 pass, ruff + mypy clean.
|
||||||
|
|
||||||
|
## Re-run
|
||||||
|
|
||||||
|
Update your build pipeline to capture UUIDs from `add_hierarchical_sheet` and pass them to `apply_batch`. The 330 power symbols should then create GND, +3V3, GNDA nets in kicad-cli export. Target: 173 nets.
|
||||||
@ -495,6 +495,8 @@ def apply_batch(
|
|||||||
schematic_path: str,
|
schematic_path: str,
|
||||||
batch_file: str,
|
batch_file: str,
|
||||||
dry_run: bool = False,
|
dry_run: bool = False,
|
||||||
|
parent_uuid: str | None = None,
|
||||||
|
sheet_uuid: str | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Apply a batch of schematic modifications from a JSON file.
|
"""Apply a batch of schematic modifications from a JSON file.
|
||||||
|
|
||||||
@ -513,6 +515,13 @@ def apply_batch(
|
|||||||
Batch files can be placed in the ``.mckicad/`` directory next to the
|
Batch files can be placed in the ``.mckicad/`` directory next to the
|
||||||
schematic for clean organization.
|
schematic for clean organization.
|
||||||
|
|
||||||
|
**Hierarchy context:** For sub-sheets in a hierarchical design, pass
|
||||||
|
``parent_uuid`` and ``sheet_uuid`` (both returned by
|
||||||
|
``add_hierarchical_sheet``). This sets the instance path so that
|
||||||
|
kicad-cli correctly resolves power symbol nets during netlist export.
|
||||||
|
Without hierarchy context, power symbols (GND, +3V3, etc.) are silently
|
||||||
|
dropped from the netlist.
|
||||||
|
|
||||||
**Batch JSON schema:**
|
**Batch JSON schema:**
|
||||||
|
|
||||||
.. code-block:: json
|
.. code-block:: json
|
||||||
@ -559,6 +568,12 @@ def apply_batch(
|
|||||||
directory.
|
directory.
|
||||||
dry_run: When True, validates the batch without applying changes.
|
dry_run: When True, validates the batch without applying changes.
|
||||||
Returns validation results and operation preview.
|
Returns validation results and operation preview.
|
||||||
|
parent_uuid: UUID of the parent (root) schematic. Required together
|
||||||
|
with ``sheet_uuid`` for hierarchical designs. Returned by
|
||||||
|
``add_hierarchical_sheet`` as ``parent_uuid``.
|
||||||
|
sheet_uuid: UUID of the sheet entry in the parent schematic. Required
|
||||||
|
together with ``parent_uuid`` for hierarchical designs. Returned
|
||||||
|
by ``add_hierarchical_sheet`` as ``sheet_uuid``.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary with ``success``, counts of each operation type applied,
|
Dictionary with ``success``, counts of each operation type applied,
|
||||||
@ -605,6 +620,15 @@ def apply_batch(
|
|||||||
try:
|
try:
|
||||||
sch = _ksa_load(schematic_path)
|
sch = _ksa_load(schematic_path)
|
||||||
|
|
||||||
|
# Set hierarchy context for sub-sheets so kicad-cli resolves
|
||||||
|
# power symbol nets correctly during netlist export.
|
||||||
|
if parent_uuid and sheet_uuid:
|
||||||
|
sch.set_hierarchy_context(parent_uuid, sheet_uuid)
|
||||||
|
logger.info(
|
||||||
|
"Set hierarchy context: parent=%s, sheet=%s",
|
||||||
|
parent_uuid, sheet_uuid,
|
||||||
|
)
|
||||||
|
|
||||||
# Validation pass
|
# Validation pass
|
||||||
validation_errors = _validate_batch_data(data, sch)
|
validation_errors = _validate_batch_data(data, sch)
|
||||||
if validation_errors:
|
if validation_errors:
|
||||||
|
|||||||
@ -621,12 +621,13 @@ def add_hierarchical_sheet(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
sch = _ksa_load(schematic_path)
|
sch = _ksa_load(schematic_path)
|
||||||
sch.add_sheet(
|
sheet_uuid = sch.add_sheet(
|
||||||
name=name,
|
name=name,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
position=(x, y),
|
position=(x, y),
|
||||||
size=(width, height),
|
size=(width, height),
|
||||||
)
|
)
|
||||||
|
parent_uuid = sch.uuid
|
||||||
sch.save(schematic_path)
|
sch.save(schematic_path)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
@ -643,6 +644,8 @@ def add_hierarchical_sheet(
|
|||||||
"success": True,
|
"success": True,
|
||||||
"sheet_name": name,
|
"sheet_name": name,
|
||||||
"sheet_filename": filename,
|
"sheet_filename": filename,
|
||||||
|
"sheet_uuid": sheet_uuid,
|
||||||
|
"parent_uuid": parent_uuid,
|
||||||
"position": {"x": x, "y": y},
|
"position": {"x": x, "y": y},
|
||||||
"size": {"width": width, "height": height},
|
"size": {"width": width, "height": height},
|
||||||
"schematic_path": schematic_path,
|
"schematic_path": schematic_path,
|
||||||
|
|||||||
@ -427,3 +427,65 @@ class TestBatchLabelConnections:
|
|||||||
# Wire stubs present
|
# Wire stubs present
|
||||||
wire_matches = re.findall(r"\(wire\n", content)
|
wire_matches = re.findall(r"\(wire\n", content)
|
||||||
assert len(wire_matches) >= 2
|
assert len(wire_matches) >= 2
|
||||||
|
|
||||||
|
|
||||||
|
@requires_sch_api
|
||||||
|
class TestBatchHierarchyContext:
|
||||||
|
"""Tests for hierarchy context in batch operations."""
|
||||||
|
|
||||||
|
def test_hierarchy_context_sets_instance_path(
|
||||||
|
self, populated_schematic_with_ic, tmp_output_dir,
|
||||||
|
):
|
||||||
|
"""Passing parent_uuid and sheet_uuid sets hierarchy context on the schematic."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from mckicad.tools.batch import apply_batch
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"labels": [
|
||||||
|
{"text": "HIERARCHY_TEST", "x": 100, "y": 100},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
batch_path = os.path.join(tmp_output_dir, "hier_batch.json")
|
||||||
|
with open(batch_path, "w") as f:
|
||||||
|
json.dump(data, f)
|
||||||
|
|
||||||
|
with patch("mckicad.tools.batch._ksa_load") as mock_load:
|
||||||
|
# Let the real load happen but spy on set_hierarchy_context
|
||||||
|
from kicad_sch_api import load_schematic
|
||||||
|
|
||||||
|
real_sch = load_schematic(populated_schematic_with_ic)
|
||||||
|
mock_load.return_value = real_sch
|
||||||
|
|
||||||
|
result = apply_batch(
|
||||||
|
schematic_path=populated_schematic_with_ic,
|
||||||
|
batch_file=batch_path,
|
||||||
|
parent_uuid="aaaa-bbbb-cccc",
|
||||||
|
sheet_uuid="dddd-eeee-ffff",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["success"] is True
|
||||||
|
# Verify the hierarchy path was set
|
||||||
|
assert real_sch._hierarchy_path == "/aaaa-bbbb-cccc/dddd-eeee-ffff"
|
||||||
|
|
||||||
|
def test_no_hierarchy_context_without_params(
|
||||||
|
self, populated_schematic_with_ic, tmp_output_dir,
|
||||||
|
):
|
||||||
|
"""Without parent_uuid/sheet_uuid, no hierarchy context is set."""
|
||||||
|
from mckicad.tools.batch import apply_batch
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"labels": [
|
||||||
|
{"text": "NO_HIER_TEST", "x": 100, "y": 100},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
batch_path = os.path.join(tmp_output_dir, "no_hier_batch.json")
|
||||||
|
with open(batch_path, "w") as f:
|
||||||
|
json.dump(data, f)
|
||||||
|
|
||||||
|
result = apply_batch(
|
||||||
|
schematic_path=populated_schematic_with_ic,
|
||||||
|
batch_file=batch_path,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["success"] is True
|
||||||
|
|||||||
@ -119,6 +119,35 @@ def test_get_schematic_hierarchy(populated_schematic):
|
|||||||
assert result["hierarchy"]["total_sheets"] >= 1
|
assert result["hierarchy"]["total_sheets"] >= 1
|
||||||
|
|
||||||
|
|
||||||
|
@requires_sch_api
|
||||||
|
@pytest.mark.unit
|
||||||
|
def test_add_hierarchical_sheet_returns_uuids(tmp_output_dir):
|
||||||
|
"""add_hierarchical_sheet should return sheet_uuid and parent_uuid."""
|
||||||
|
from mckicad.tools.schematic import add_hierarchical_sheet, create_schematic
|
||||||
|
|
||||||
|
root_path = os.path.join(tmp_output_dir, "root.kicad_sch")
|
||||||
|
create_schematic(name="root_project", output_path=root_path)
|
||||||
|
|
||||||
|
# Create an empty child schematic
|
||||||
|
child_path = os.path.join(tmp_output_dir, "child.kicad_sch")
|
||||||
|
create_schematic(name="root_project", output_path=child_path)
|
||||||
|
|
||||||
|
result = add_hierarchical_sheet(
|
||||||
|
schematic_path=root_path,
|
||||||
|
name="Power Supply",
|
||||||
|
filename="child.kicad_sch",
|
||||||
|
x=50, y=50, width=100, height=80,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["success"] is True
|
||||||
|
assert "sheet_uuid" in result
|
||||||
|
assert "parent_uuid" in result
|
||||||
|
assert isinstance(result["sheet_uuid"], str)
|
||||||
|
assert isinstance(result["parent_uuid"], str)
|
||||||
|
assert len(result["sheet_uuid"]) > 0
|
||||||
|
assert len(result["parent_uuid"]) > 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.unit
|
@pytest.mark.unit
|
||||||
def test_file_output_infrastructure(tmp_output_dir):
|
def test_file_output_infrastructure(tmp_output_dir):
|
||||||
"""write_detail_file should create .mckicad/{stem}/ sidecar directory and file."""
|
"""write_detail_file should create .mckicad/{stem}/ sidecar directory and file."""
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user