Files
osmWorkflow/blender/tests/test_traffic_signals.py

125 lines
4.9 KiB
Python

"""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()