chore: apply Black formatting and add tests for get_wire_connections
- Apply Black formatting to wire_connectivity.py, kicad_interface.py, and tool_schemas.py (changed files only) - Add python/tests/conftest.py with pcbnew/skip C-extension stubs - Add python/tests/test_wire_connectivity.py with 29 unit tests covering schema validation, handler dispatch, parameter validation, and core logic (_to_iu, _parse_wires, _build_adjacency, _find_connected_wires, get_wire_connections) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,7 @@ from pathlib import Path
|
|||||||
from typing import Dict, List, Optional, Set, Tuple
|
from typing import Dict, List, Optional, Set, Tuple
|
||||||
from commands.pin_locator import PinLocator
|
from commands.pin_locator import PinLocator
|
||||||
|
|
||||||
logger = logging.getLogger('kicad_interface')
|
logger = logging.getLogger("kicad_interface")
|
||||||
|
|
||||||
_IU_PER_MM = 10000 # KiCad schematic internal units per millimeter
|
_IU_PER_MM = 10000 # KiCad schematic internal units per millimeter
|
||||||
|
|
||||||
@@ -95,7 +95,9 @@ def _parse_virtual_connections(schematic, schematic_path):
|
|||||||
locator = PinLocator()
|
locator = PinLocator()
|
||||||
for symbol in schematic.symbol:
|
for symbol in schematic.symbol:
|
||||||
try:
|
try:
|
||||||
if not hasattr(symbol, "property") or not hasattr(symbol.property, "Reference"):
|
if not hasattr(symbol, "property") or not hasattr(
|
||||||
|
symbol.property, "Reference"
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
ref = symbol.property.Reference.value
|
ref = symbol.property.Reference.value
|
||||||
if not ref.startswith("#PWR"):
|
if not ref.startswith("#PWR"):
|
||||||
@@ -192,7 +194,9 @@ def _find_pins_on_net(
|
|||||||
ref = None
|
ref = None
|
||||||
for symbol in schematic.symbol:
|
for symbol in schematic.symbol:
|
||||||
try:
|
try:
|
||||||
if not hasattr(symbol, 'property') or not hasattr(symbol.property, "Reference"):
|
if not hasattr(symbol, "property") or not hasattr(
|
||||||
|
symbol.property, "Reference"
|
||||||
|
):
|
||||||
continue
|
continue
|
||||||
ref = symbol.property.Reference.value
|
ref = symbol.property.Reference.value
|
||||||
if ref.startswith("_TEMPLATE"):
|
if ref.startswith("_TEMPLATE"):
|
||||||
@@ -207,12 +211,16 @@ def _find_pins_on_net(
|
|||||||
seen.add(key)
|
seen.add(key)
|
||||||
pins.append({"component": ref, "pin": pin_num})
|
pins.append({"component": ref, "pin": pin_num})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Error checking pins for {ref if ref is not None else '<unknown>'}: {e}")
|
logger.warning(
|
||||||
|
f"Error checking pins for {ref if ref is not None else '<unknown>'}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
return pins
|
return pins
|
||||||
|
|
||||||
|
|
||||||
def get_wire_connections(schematic, schematic_path: str, x_mm: float, y_mm: float) -> Optional[Dict]:
|
def get_wire_connections(
|
||||||
|
schematic, schematic_path: str, x_mm: float, y_mm: float
|
||||||
|
) -> Optional[Dict]:
|
||||||
"""Find all component pins reachable from a point via connected wires, net labels, and power symbols.
|
"""Find all component pins reachable from a point via connected wires, net labels, and power symbols.
|
||||||
|
|
||||||
The query point (x_mm, y_mm) must be exactly on a wire endpoint or junction (exact IU match).
|
The query point (x_mm, y_mm) must be exactly on a wire endpoint or junction (exact IU match).
|
||||||
@@ -233,10 +241,16 @@ def get_wire_connections(schematic, schematic_path: str, x_mm: float, y_mm: floa
|
|||||||
|
|
||||||
adjacency, iu_to_wires = _build_adjacency(all_wires)
|
adjacency, iu_to_wires = _build_adjacency(all_wires)
|
||||||
|
|
||||||
point_to_label, label_to_points = _parse_virtual_connections(schematic, schematic_path)
|
point_to_label, label_to_points = _parse_virtual_connections(
|
||||||
|
schematic, schematic_path
|
||||||
|
)
|
||||||
|
|
||||||
visited, net_points = _find_connected_wires(
|
visited, net_points = _find_connected_wires(
|
||||||
x_mm, y_mm, all_wires, iu_to_wires, adjacency,
|
x_mm,
|
||||||
|
y_mm,
|
||||||
|
all_wires,
|
||||||
|
iu_to_wires,
|
||||||
|
adjacency,
|
||||||
point_to_label=point_to_label,
|
point_to_label=point_to_label,
|
||||||
label_to_points=label_to_points,
|
label_to_points=label_to_points,
|
||||||
)
|
)
|
||||||
@@ -244,8 +258,16 @@ def get_wire_connections(schematic, schematic_path: str, x_mm: float, y_mm: floa
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
wires_out = [
|
wires_out = [
|
||||||
{"start": {"x": all_wires[i][0][0] / _IU_PER_MM, "y": all_wires[i][0][1] / _IU_PER_MM},
|
{
|
||||||
"end": {"x": all_wires[i][-1][0] / _IU_PER_MM, "y": all_wires[i][-1][1] / _IU_PER_MM}}
|
"start": {
|
||||||
|
"x": all_wires[i][0][0] / _IU_PER_MM,
|
||||||
|
"y": all_wires[i][0][1] / _IU_PER_MM,
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"x": all_wires[i][-1][0] / _IU_PER_MM,
|
||||||
|
"y": all_wires[i][-1][1] / _IU_PER_MM,
|
||||||
|
},
|
||||||
|
}
|
||||||
for i in visited
|
for i in visited
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -2332,12 +2332,18 @@ class KiCADInterface:
|
|||||||
y = params.get("y")
|
y = params.get("y")
|
||||||
|
|
||||||
if not (schematic_path and x is not None and y is not None):
|
if not (schematic_path and x is not None and y is not None):
|
||||||
return {"success": False, "message": "Missing required parameters: schematicPath, x, y"}
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "Missing required parameters: schematicPath, x, y",
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
x, y = float(x), float(y)
|
x, y = float(x), float(y)
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return {"success": False, "message": "Parameters x and y must be numeric"}
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": "Parameters x and y must be numeric",
|
||||||
|
}
|
||||||
|
|
||||||
schematic = SchematicManager.load_schematic(schematic_path)
|
schematic = SchematicManager.load_schematic(schematic_path)
|
||||||
if not schematic:
|
if not schematic:
|
||||||
@@ -2348,13 +2354,17 @@ class KiCADInterface:
|
|||||||
|
|
||||||
result = get_wire_connections(schematic, schematic_path, x, y)
|
result = get_wire_connections(schematic, schematic_path, x, y)
|
||||||
if result is None:
|
if result is None:
|
||||||
return {"success": False, "message": f"No wire found at ({x},{y}) within tolerance"}
|
return {
|
||||||
|
"success": False,
|
||||||
|
"message": f"No wire found at ({x},{y}) within tolerance",
|
||||||
|
}
|
||||||
|
|
||||||
return {"success": True, **result}
|
return {"success": True, **result}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting wire connections: {str(e)}")
|
logger.error(f"Error getting wire connections: {str(e)}")
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
logger.error(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
return {"success": False, "message": str(e)}
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
|
|||||||
@@ -1480,19 +1480,19 @@ SCHEMATIC_TOOLS = [
|
|||||||
"properties": {
|
"properties": {
|
||||||
"schematicPath": {
|
"schematicPath": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Path to schematic file"
|
"description": "Path to schematic file",
|
||||||
},
|
},
|
||||||
"x": {
|
"x": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "X coordinate of the point on the wire"
|
"description": "X coordinate of the point on the wire",
|
||||||
},
|
},
|
||||||
"y": {
|
"y": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "Y coordinate of the point on the wire"
|
"description": "Y coordinate of the point on the wire",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
"required": ["schematicPath", "x", "y"]
|
"required": ["schematicPath", "x", "y"],
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "get_schematic_pin_locations",
|
"name": "get_schematic_pin_locations",
|
||||||
|
|||||||
338
python/tests/test_wire_connectivity.py
Normal file
338
python/tests/test_wire_connectivity.py
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
"""
|
||||||
|
Tests for the wire_connectivity module and the get_wire_connections handler.
|
||||||
|
|
||||||
|
Covers:
|
||||||
|
- Schema shape (TestSchema)
|
||||||
|
- Handler dispatch registration (TestHandlerDispatch)
|
||||||
|
- Parameter validation in the handler (TestHandlerParamValidation)
|
||||||
|
- Core logic: _to_iu, _parse_wires, _build_adjacency, _find_connected_wires,
|
||||||
|
get_wire_connections (TestCoreLogic)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
# Ensure the python package root is importable
|
||||||
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Module under test
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
from commands.wire_connectivity import (
|
||||||
|
_build_adjacency,
|
||||||
|
_find_connected_wires,
|
||||||
|
_parse_wires,
|
||||||
|
_to_iu,
|
||||||
|
get_wire_connections,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Helpers to build minimal mock schematic objects
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def _make_point(x: float, y: float) -> MagicMock:
|
||||||
|
pt = MagicMock()
|
||||||
|
pt.value = [x, y]
|
||||||
|
return pt
|
||||||
|
|
||||||
|
|
||||||
|
def _make_wire(x1: float, y1: float, x2: float, y2: float) -> MagicMock:
|
||||||
|
wire = MagicMock()
|
||||||
|
wire.pts = MagicMock()
|
||||||
|
wire.pts.xy = [_make_point(x1, y1), _make_point(x2, y2)]
|
||||||
|
return wire
|
||||||
|
|
||||||
|
|
||||||
|
def _make_schematic(*wires) -> MagicMock:
|
||||||
|
sch = MagicMock()
|
||||||
|
sch.wire = list(wires)
|
||||||
|
# No net labels, no symbols by default
|
||||||
|
del sch.label # make hasattr(..., "label") return False
|
||||||
|
del sch.symbol # make hasattr(..., "symbol") return False
|
||||||
|
return sch
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# TestSchema
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
class TestSchema:
|
||||||
|
"""Verify the get_wire_connections tool schema is present and well-formed."""
|
||||||
|
|
||||||
|
def test_schema_registered(self):
|
||||||
|
from schemas.tool_schemas import TOOL_SCHEMAS
|
||||||
|
|
||||||
|
assert "get_wire_connections" in TOOL_SCHEMAS
|
||||||
|
|
||||||
|
def test_schema_required_fields(self):
|
||||||
|
from schemas.tool_schemas import TOOL_SCHEMAS
|
||||||
|
|
||||||
|
schema = TOOL_SCHEMAS["get_wire_connections"]
|
||||||
|
required = schema["inputSchema"]["required"]
|
||||||
|
assert "schematicPath" in required
|
||||||
|
assert "x" in required
|
||||||
|
assert "y" in required
|
||||||
|
|
||||||
|
def test_schema_has_title_and_description(self):
|
||||||
|
from schemas.tool_schemas import TOOL_SCHEMAS
|
||||||
|
|
||||||
|
schema = TOOL_SCHEMAS["get_wire_connections"]
|
||||||
|
assert schema.get("title")
|
||||||
|
assert schema.get("description")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# TestHandlerDispatch
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
class TestHandlerDispatch:
|
||||||
|
"""Verify the handler is wired into KiCadInterface.command_routes."""
|
||||||
|
|
||||||
|
def test_get_wire_connections_in_routes(self):
|
||||||
|
# Import lazily to avoid heavy side-effects at collection time
|
||||||
|
with patch("kicad_interface.USE_IPC_BACKEND", False):
|
||||||
|
from kicad_interface import KiCADInterface
|
||||||
|
|
||||||
|
iface = KiCADInterface.__new__(KiCADInterface)
|
||||||
|
iface.board = None
|
||||||
|
iface.project_filename = None
|
||||||
|
iface.use_ipc = False
|
||||||
|
iface.ipc_backend = MagicMock()
|
||||||
|
iface.ipc_board_api = None
|
||||||
|
iface.footprint_library = MagicMock()
|
||||||
|
iface.project_commands = MagicMock()
|
||||||
|
iface.board_commands = MagicMock()
|
||||||
|
iface.component_commands = MagicMock()
|
||||||
|
iface.routing_commands = MagicMock()
|
||||||
|
|
||||||
|
# Build routes only (avoid full __init__ side-effects)
|
||||||
|
# The routes dict is built in __init__; we call it directly.
|
||||||
|
iface.__init__()
|
||||||
|
|
||||||
|
assert "get_wire_connections" in iface.command_routes
|
||||||
|
assert callable(iface.command_routes["get_wire_connections"])
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# TestHandlerParamValidation
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
class TestHandlerParamValidation:
|
||||||
|
"""Handler returns error responses for bad or missing parameters."""
|
||||||
|
|
||||||
|
def _make_handler(self):
|
||||||
|
"""Return a bound _handle_get_wire_connections without full init."""
|
||||||
|
with patch("kicad_interface.USE_IPC_BACKEND", False):
|
||||||
|
from kicad_interface import KiCADInterface
|
||||||
|
|
||||||
|
iface = KiCADInterface.__new__(KiCADInterface)
|
||||||
|
return iface._handle_get_wire_connections
|
||||||
|
|
||||||
|
def test_missing_schematic_path(self):
|
||||||
|
handler = self._make_handler()
|
||||||
|
result = handler({"x": 1.0, "y": 2.0})
|
||||||
|
assert result["success"] is False
|
||||||
|
assert "schematicPath" in result["message"] or "Missing" in result["message"]
|
||||||
|
|
||||||
|
def test_missing_x(self):
|
||||||
|
handler = self._make_handler()
|
||||||
|
result = handler({"schematicPath": "/tmp/test.kicad_sch", "y": 2.0})
|
||||||
|
assert result["success"] is False
|
||||||
|
|
||||||
|
def test_missing_y(self):
|
||||||
|
handler = self._make_handler()
|
||||||
|
result = handler({"schematicPath": "/tmp/test.kicad_sch", "x": 1.0})
|
||||||
|
assert result["success"] is False
|
||||||
|
|
||||||
|
def test_non_numeric_x(self):
|
||||||
|
handler = self._make_handler()
|
||||||
|
result = handler({"schematicPath": "/tmp/test.kicad_sch", "x": "bad", "y": 2.0})
|
||||||
|
assert result["success"] is False
|
||||||
|
assert "numeric" in result["message"].lower() or "x" in result["message"]
|
||||||
|
|
||||||
|
def test_non_numeric_y(self):
|
||||||
|
handler = self._make_handler()
|
||||||
|
result = handler({"schematicPath": "/tmp/test.kicad_sch", "x": 1.0, "y": "bad"})
|
||||||
|
assert result["success"] is False
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# TestCoreLogic
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_IU = 10_000 # IU per mm
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.unit
|
||||||
|
class TestCoreLogic:
|
||||||
|
"""Unit tests for the pure-logic functions in wire_connectivity."""
|
||||||
|
|
||||||
|
# --- _to_iu ---
|
||||||
|
|
||||||
|
def test_to_iu_integer_mm(self):
|
||||||
|
assert _to_iu(1.0, 2.0) == (10_000, 20_000)
|
||||||
|
|
||||||
|
def test_to_iu_fractional_mm(self):
|
||||||
|
assert _to_iu(0.5, 0.25) == (5_000, 2_500)
|
||||||
|
|
||||||
|
def test_to_iu_zero(self):
|
||||||
|
assert _to_iu(0.0, 0.0) == (0, 0)
|
||||||
|
|
||||||
|
def test_to_iu_negative(self):
|
||||||
|
assert _to_iu(-1.0, -2.0) == (-10_000, -20_000)
|
||||||
|
|
||||||
|
# --- _parse_wires ---
|
||||||
|
|
||||||
|
def test_parse_wires_single_wire(self):
|
||||||
|
sch = _make_schematic(_make_wire(0.0, 0.0, 1.0, 0.0))
|
||||||
|
result = _parse_wires(sch)
|
||||||
|
assert len(result) == 1
|
||||||
|
assert result[0] == [(0, 0), (10_000, 0)]
|
||||||
|
|
||||||
|
def test_parse_wires_empty_schematic(self):
|
||||||
|
sch = MagicMock()
|
||||||
|
sch.wire = []
|
||||||
|
assert _parse_wires(sch) == []
|
||||||
|
|
||||||
|
def test_parse_wires_multiple_wires(self):
|
||||||
|
sch = _make_schematic(
|
||||||
|
_make_wire(0.0, 0.0, 1.0, 0.0),
|
||||||
|
_make_wire(1.0, 0.0, 2.0, 0.0),
|
||||||
|
)
|
||||||
|
assert len(_parse_wires(sch)) == 2
|
||||||
|
|
||||||
|
def test_parse_wires_skips_wire_without_pts(self):
|
||||||
|
bad_wire = MagicMock(spec=[]) # no `pts` attribute
|
||||||
|
sch = MagicMock()
|
||||||
|
sch.wire = [bad_wire]
|
||||||
|
assert _parse_wires(sch) == []
|
||||||
|
|
||||||
|
# --- _build_adjacency ---
|
||||||
|
|
||||||
|
def test_build_adjacency_two_connected_wires(self):
|
||||||
|
# wire0: (0,0)-(1,0), wire1: (1,0)-(2,0) — share endpoint (1,0)
|
||||||
|
wires = [
|
||||||
|
[(0, 0), (10_000, 0)],
|
||||||
|
[(10_000, 0), (20_000, 0)],
|
||||||
|
]
|
||||||
|
adjacency, iu_to_wires = _build_adjacency(wires)
|
||||||
|
assert 1 in adjacency[0]
|
||||||
|
assert 0 in adjacency[1]
|
||||||
|
|
||||||
|
def test_build_adjacency_two_disconnected_wires(self):
|
||||||
|
wires = [
|
||||||
|
[(0, 0), (10_000, 0)],
|
||||||
|
[(20_000, 0), (30_000, 0)],
|
||||||
|
]
|
||||||
|
adjacency, _ = _build_adjacency(wires)
|
||||||
|
assert adjacency[0] == set()
|
||||||
|
assert adjacency[1] == set()
|
||||||
|
|
||||||
|
def test_build_adjacency_iu_to_wires_maps_correctly(self):
|
||||||
|
wires = [
|
||||||
|
[(0, 0), (10_000, 0)],
|
||||||
|
[(10_000, 0), (20_000, 0)],
|
||||||
|
]
|
||||||
|
_, iu_to_wires = _build_adjacency(wires)
|
||||||
|
assert iu_to_wires[(10_000, 0)] == {0, 1}
|
||||||
|
assert iu_to_wires[(0, 0)] == {0}
|
||||||
|
|
||||||
|
def test_build_adjacency_three_wires_at_junction(self):
|
||||||
|
# All three wires meet at (10,000, 0)
|
||||||
|
wires = [
|
||||||
|
[(0, 0), (10_000, 0)],
|
||||||
|
[(10_000, 0), (20_000, 0)],
|
||||||
|
[(10_000, 0), (10_000, 10_000)],
|
||||||
|
]
|
||||||
|
adjacency, _ = _build_adjacency(wires)
|
||||||
|
assert adjacency[0] == {1, 2}
|
||||||
|
assert adjacency[1] == {0, 2}
|
||||||
|
assert adjacency[2] == {0, 1}
|
||||||
|
|
||||||
|
# --- _find_connected_wires ---
|
||||||
|
|
||||||
|
def test_find_connected_wires_no_wire_at_point(self):
|
||||||
|
wires = [[(0, 0), (10_000, 0)]]
|
||||||
|
adjacency, iu_to_wires = _build_adjacency(wires)
|
||||||
|
visited, net_points = _find_connected_wires(
|
||||||
|
5.0, 0.0, wires, iu_to_wires, adjacency
|
||||||
|
)
|
||||||
|
assert visited is None
|
||||||
|
assert net_points is None
|
||||||
|
|
||||||
|
def test_find_connected_wires_single_wire(self):
|
||||||
|
wires = [[(0, 0), (10_000, 0)]]
|
||||||
|
adjacency, iu_to_wires = _build_adjacency(wires)
|
||||||
|
visited, net_points = _find_connected_wires(
|
||||||
|
0.0, 0.0, wires, iu_to_wires, adjacency
|
||||||
|
)
|
||||||
|
assert visited == {0}
|
||||||
|
assert (0, 0) in net_points
|
||||||
|
assert (10_000, 0) in net_points
|
||||||
|
|
||||||
|
def test_find_connected_wires_flood_fills_chain(self):
|
||||||
|
# Three wires in a chain: A-B-C-D
|
||||||
|
wires = [
|
||||||
|
[(0, 0), (10_000, 0)],
|
||||||
|
[(10_000, 0), (20_000, 0)],
|
||||||
|
[(20_000, 0), (30_000, 0)],
|
||||||
|
]
|
||||||
|
adjacency, iu_to_wires = _build_adjacency(wires)
|
||||||
|
visited, net_points = _find_connected_wires(
|
||||||
|
0.0, 0.0, wires, iu_to_wires, adjacency
|
||||||
|
)
|
||||||
|
assert visited == {0, 1, 2}
|
||||||
|
|
||||||
|
def test_find_connected_wires_does_not_cross_gap(self):
|
||||||
|
# Two disconnected segments; query on segment 0 should not reach segment 1
|
||||||
|
wires = [
|
||||||
|
[(0, 0), (10_000, 0)],
|
||||||
|
[(20_000, 0), (30_000, 0)],
|
||||||
|
]
|
||||||
|
adjacency, iu_to_wires = _build_adjacency(wires)
|
||||||
|
visited, _ = _find_connected_wires(0.0, 0.0, wires, iu_to_wires, adjacency)
|
||||||
|
assert visited == {0}
|
||||||
|
|
||||||
|
# --- get_wire_connections (integration of internal functions) ---
|
||||||
|
|
||||||
|
def test_get_wire_connections_no_wires(self):
|
||||||
|
sch = MagicMock()
|
||||||
|
sch.wire = []
|
||||||
|
result = get_wire_connections(sch, "/fake/path.kicad_sch", 0.0, 0.0)
|
||||||
|
assert result == {"pins": [], "wires": []}
|
||||||
|
|
||||||
|
def test_get_wire_connections_no_wire_at_point_returns_none(self):
|
||||||
|
sch = _make_schematic(_make_wire(0.0, 0.0, 1.0, 0.0))
|
||||||
|
result = get_wire_connections(sch, "/fake/path.kicad_sch", 5.0, 0.0)
|
||||||
|
assert result is None
|
||||||
|
|
||||||
|
def test_get_wire_connections_returns_wire_data(self):
|
||||||
|
sch = _make_schematic(_make_wire(0.0, 0.0, 1.0, 0.0))
|
||||||
|
# Prevent _find_pins_on_net from iterating symbols
|
||||||
|
result = get_wire_connections(sch, "/fake/path.kicad_sch", 0.0, 0.0)
|
||||||
|
assert result is not None
|
||||||
|
assert result["pins"] == []
|
||||||
|
assert len(result["wires"]) == 1
|
||||||
|
wire = result["wires"][0]
|
||||||
|
assert wire["start"] == {"x": 0.0, "y": 0.0}
|
||||||
|
assert wire["end"] == {"x": 1.0, "y": 0.0}
|
||||||
|
|
||||||
|
def test_get_wire_connections_chain_returns_all_wires(self):
|
||||||
|
sch = _make_schematic(
|
||||||
|
_make_wire(0.0, 0.0, 1.0, 0.0),
|
||||||
|
_make_wire(1.0, 0.0, 2.0, 0.0),
|
||||||
|
)
|
||||||
|
result = get_wire_connections(sch, "/fake/path.kicad_sch", 0.0, 0.0)
|
||||||
|
assert result is not None
|
||||||
|
assert len(result["wires"]) == 2
|
||||||
Reference in New Issue
Block a user