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>
120 lines
3.8 KiB
Python
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"
|