# F2CSaveHarness is built dynamically (see load_f2c_save_harness below), and
# make_node supplies the attributes normally initialized by the ROS node.
# pylint: disable=attribute-defined-outside-init,exec-used,no-member,protected-access
import ast
import math
import re
import unittest
from datetime import UTC, datetime
from itertools import pairwise
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import Mock
from devkit_ui.models import NodeID, TopoDoc, TopoEdge, TopoNode, TopoPose, TopoProperties, Vector2
NAV_ACTION = 'nav_to_pose'
ROW_ACTION = 'row_follow'
[docs]
def latlon_to_xy(lat: float, lon: float, anchor_lat: float, anchor_lon: float) -> tuple[float, float]:
return lat - anchor_lat, lon - anchor_lon
[docs]
def xy_to_latlon(x: float, y: float, anchor_lat: float, anchor_lon: float) -> tuple[float, float]:
return anchor_lat + x, anchor_lon + y
[docs]
def resample_row_xy(_points, _anchor_lat, _anchor_lon, _interval):
return [(1.0, 1.5)]
[docs]
def load_f2c_save_harness():
"""Load only the F2C save method, avoiding ui_node's ROS and web-server imports."""
source_path = Path(__file__).with_name('ui_node.py')
tree = ast.parse(source_path.read_text(encoding='utf-8'))
node_class = next(
node for node in tree.body
if isinstance(node, ast.ClassDef) and node.name == 'NiceGuiNode'
)
method = next(
node for node in node_class.body
if isinstance(node, ast.FunctionDef) and node.name == 'save_f2c_rows_to_topo'
)
namespace = {
'NodeID': NodeID,
'TopoDoc': TopoDoc,
'TopoEdge': TopoEdge,
'TopoNode': TopoNode,
'TopoPose': TopoPose,
'TopoProperties': TopoProperties,
'Vector2': Vector2,
'NAV_ACTION': NAV_ACTION,
'ROW_ACTION': ROW_ACTION,
'_CONTOUR_WAYPOINT_INTERVAL_M': 1.0,
'_f2c_latlon_to_xy': latlon_to_xy,
'_f2c_xy_to_latlon': xy_to_latlon,
'_resample_row_xy': resample_row_xy,
'UTC': UTC,
'datetime': datetime,
'math': math,
'pairwise': pairwise,
're': re,
}
module = ast.Module(body=[method], type_ignores=[])
exec(compile(ast.fix_missing_locations(module), source_path, 'exec'), namespace)
return type('F2CSaveHarness', (), {'save_f2c_rows_to_topo': namespace['save_f2c_rows_to_topo']})
F2CSaveHarness = load_f2c_save_harness()
[docs]
def make_node(swaths, *, contour_used=False):
node = F2CSaveHarness()
node._f2c_swaths = swaths
node._f2c_contour_used = contour_used
node._f2c_origin_ll = (51.0, -2.0)
node._is_sim = True
node.latest_odom = None
node.latest_gps = SimpleNamespace(
latitude=51.0,
longitude=-2.0,
status=SimpleNamespace(status=0),
)
node._topo_doc = TopoDoc(name='field')
node._run_vm = SimpleNamespace(
topo=SimpleNamespace(current_node=None, selected_node=None),
)
node.f2c_save_status = ''
node.get_logger = Mock(return_value=Mock())
def persist(modify, status_owner, status_attr, success_message):
modify(node._topo_doc)
setattr(status_owner, status_attr, success_message)
node._persist_and_reload = Mock(side_effect=persist)
return node
if __name__ == '__main__':
unittest.main()