Files
kicad-mcp-server/tests/test_component_bbox_courtyard.py
Gavin Colonese f03a74a93f Feat: add bounding box and courtyard info to component queries (#163)
Components now include boundingBox (min/max X/Y, width, height) in
get_component_list and get_component_properties responses. The SWIG
get_component_properties also includes courtyard dimensions when the
footprint defines a courtyard layer.

SWIG backend uses GetBoundingBox() and GetCourtyard(). IPC backend
tries get_item_bounding_box(), then pad-extent fallback, then falls
back to SWIG backend data when available. This ensures bounding box
data is present regardless of which backend handles the query.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 09:36:33 -04:00

120 lines
3.8 KiB
Python

"""Tests for bounding-box / courtyard data in component queries (SWIG path).
``get_component_properties`` and ``get_component_list`` now include a
``boundingBox`` field built from ``module.GetBoundingBox()`` and (for the
single-component query) a ``courtyard`` field built from
``module.GetCourtyard()`` against ``F_CrtYd``/``B_CrtYd``.
"""
import sys
from pathlib import Path
from unittest.mock import MagicMock
import pytest
sys.path.insert(0, str(Path(__file__).parent.parent / "python"))
def _bbox(left_nm, top_nm, right_nm, bottom_nm):
bb = MagicMock()
bb.GetLeft.return_value = left_nm
bb.GetTop.return_value = top_nm
bb.GetRight.return_value = right_nm
bb.GetBottom.return_value = bottom_nm
return bb
def _make_module(*, ref="U1", value="LM317", with_courtyard=False):
pos = MagicMock()
pos.x = 10_000_000 # 10 mm
pos.y = 20_000_000 # 20 mm
module = MagicMock()
module.GetPosition.return_value = pos
module.GetReference.return_value = ref
module.GetValue.return_value = value
module.GetFPIDAsString.return_value = "Lib:Footprint"
orientation = MagicMock()
orientation.AsDegrees.return_value = 0
module.GetOrientation.return_value = orientation
module.GetLayer.return_value = 0
module.GetAttributes.return_value = 0
module.GetBoundingBox.return_value = _bbox(0, 0, 5_000_000, 3_000_000) # 5 x 3 mm
courtyard_outline = MagicMock()
if with_courtyard:
courtyard_outline.OutlineCount.return_value = 1
courtyard_outline.BBox.return_value = _bbox(-500_000, -500_000, 5_500_000, 3_500_000)
else:
courtyard_outline.OutlineCount.return_value = 0
module.GetCourtyard.return_value = courtyard_outline
return module
def _make_board(modules):
board = MagicMock()
board.GetLayerName.return_value = "F.Cu"
board.GetFootprints.return_value = list(modules)
if modules:
board.FindFootprintByReference.return_value = modules[0]
else:
board.FindFootprintByReference.return_value = None
return board
def test_get_component_properties_returns_bounding_box():
from commands.component import ComponentCommands
module = _make_module()
cmd = ComponentCommands(board=_make_board([module]))
result = cmd.get_component_properties({"reference": "U1"})
assert result["success"] is True
bb = result["component"]["boundingBox"]
assert bb["min_x"] == 0.0
assert bb["min_y"] == 0.0
assert bb["max_x"] == 5.0
assert bb["max_y"] == 3.0
assert bb["width"] == 5.0
assert bb["height"] == 3.0
assert bb["unit"] == "mm"
def test_get_component_properties_returns_courtyard_when_present():
from commands.component import ComponentCommands
module = _make_module(with_courtyard=True)
cmd = ComponentCommands(board=_make_board([module]))
result = cmd.get_component_properties({"reference": "U1"})
courtyard = result["component"]["courtyard"]
assert courtyard is not None
assert courtyard["min_x"] == -0.5
assert courtyard["max_x"] == 5.5
assert courtyard["unit"] == "mm"
def test_get_component_properties_courtyard_none_when_absent():
from commands.component import ComponentCommands
module = _make_module(with_courtyard=False)
cmd = ComponentCommands(board=_make_board([module]))
result = cmd.get_component_properties({"reference": "U1"})
assert result["component"]["courtyard"] is None
def test_get_component_list_includes_bounding_box():
from commands.component import ComponentCommands
modules = [_make_module(ref="R1"), _make_module(ref="R2")]
cmd = ComponentCommands(board=_make_board(modules))
result = cmd.get_component_list({})
assert result["success"] is True
assert len(result["components"]) == 2
for comp in result["components"]:
assert "boundingBox" in comp
assert comp["boundingBox"]["unit"] == "mm"