125 lines
4.9 KiB
Python
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()
|