"""Static traffic-signal geometry can be exercised without Blender itself.""" import importlib import os import sys import types import unittest sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) class FakeBatch: created = [] def __init__(self, name, collection, material): self.name = name self.vertices = [] self.faces = [] FakeBatch.created.append(self) def add_prism(self, ring, base, height): if len(ring) < 3: return start = len(self.vertices) self.vertices.extend((x, y, base) for x, y in ring) self.vertices.extend((x, y, base + height) for x, y in ring) size = len(ring) self.faces.extend((tuple(range(start, start + size)), tuple(range(start + size, start + size * 2)))) def finish(self): return self.vertices or None class Projector: def xy(self, point): return point class TrafficSignalGeometryTest(unittest.TestCase): @classmethod def setUpClass(cls): mesh = types.ModuleType("osmassets.mesh") mesh.MeshBatch = FakeBatch cls.previous_mesh = sys.modules.get("osmassets.mesh") sys.modules["osmassets.mesh"] = mesh sys.modules.pop("osmassets.traffic_signals", None) cls.signals = importlib.import_module("osmassets.traffic_signals") @classmethod def tearDownClass(cls): sys.modules.pop("osmassets.traffic_signals", None) if cls.previous_mesh is None: sys.modules.pop("osmassets.mesh", None) else: sys.modules["osmassets.mesh"] = cls.previous_mesh def test_valid_anchor_builds_static_geometry_on_the_driver_right(self): FakeBatch.created = [] count = self.signals.assemble({ "layout": {"countdownLateralMeters": 1.15}, "signals": [{ "longitude": 10.0, "latitude": 20.0, "headingDegrees": 0.0, "mastReachMeters": 4.5, }], }, Projector(), object(), { "metal": object(), "housing": object(), "lenses": {"red": object(), "yellow": object(), "green": object()}, }) self.assertEqual(count, 1) housing = next(batch for batch in FakeBatch.created if batch.name == "Traffic Signal Housing") # A northbound driver's right is east, so the board's vertices must # extend east of the mast-reached head at longitude 5.5. self.assertGreater(max(vertex[0] for vertex in housing.vertices), 6.5) red_lens = next(batch for batch in FakeBatch.created if batch.name == "Traffic Signal Red Lens") # The mast arm and the head share z=6.25. The red lens sits inside # the top half of the 1.62m head rather than above its centre line. self.assertLessEqual(max(vertex[2] for vertex in red_lens.vertices), 6.98) def test_missing_anchor_coordinate_is_skipped(self): FakeBatch.created = [] count = self.signals.assemble({"signals": [{"longitude": 10.0}]}, Projector(), object(), {"metal": object(), "housing": object(), "lenses": {"red": object(), "yellow": object(), "green": object()}}) self.assertEqual(count, 0) def test_dynamic_lens_geometry_is_in_front_of_static_lens_face(self): FakeBatch.created = [] signal = { "id": "signal-1", "longitude": 10.0, "latitude": 20.0, "headingDegrees": 0.0, "pose": { "pole": {"longitude": 10.0, "latitude": 20.0}, "head": {"longitude": 10.0, "latitude": 20.0, "faceHeadingDegrees": 0.0}, "lenses": [{"state": state, "longitude": 10.0, "latitude": 20.0, "height": 6.25} for state in ("red", "yellow", "green")], "countdown": {"longitude": 10.0, "latitude": 20.0, "height": 6.25}, }, } self.signals.assemble_dynamic({"signals": [signal]}, Projector(), object(), { "red": object(), "yellow": object(), "green": object(), "active": object(), "countdown": {0: object(), 1: object()}, }) red = next(batch for batch in FakeBatch.created if batch.name == "TrafficSignalDynamic_signal-1_red") # Facing north, every active overlay vertex must sit north of the # static lens centre rather than intersecting its body. self.assertGreater(min(vertex[1] for vertex in red.vertices), 20.035) def test_countdown_uses_the_versioned_font_and_twenty_shared_values(self): self.assertTrue(os.path.exists(self.signals.COUNTDOWN_FONT_PATH)) self.assertEqual(self.signals.COUNTDOWN_VALUES, tuple("%02d" % value for value in range(20))) if __name__ == "__main__": unittest.main()