replaced the collinear test with a new one based on the hairline test that checks interior angles against official limits of 0.025 and 179.9

Tue, 25 Aug 2020 23:04:27 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Tue, 25 Aug 2020 23:04:27 +0300
changeset 103
662de6b8cfc2
parent 102
952e2a9a669b
child 104
1ad664f783d6

replaced the collinear test with a new one based on the hairline test that checks interior angles against official limits of 0.025 and 179.9

geometry.py file | annotate | diff | comparison | revisions
reference/collinearity.ldr file | annotate | diff | comparison | revisions
tests/quadrilaterals.py file | annotate | diff | comparison | revisions
--- a/geometry.py	Tue Aug 25 22:34:26 2020 +0300
+++ b/geometry.py	Tue Aug 25 23:04:27 2020 +0300
@@ -195,20 +195,16 @@
     def perimeter_lines(self):
         for v1, v2 in pairs(self.vertices):
             yield LineSegment(v1, v2)
-    @property
-    def angles(self):
-        from math import acos, isclose
+    def angle_cosines(self):
+        from math import isclose
         for v1, v2, v3 in pairs(self.vertices, count = 3):
             vec1 = (position_vector(v3) - position_vector(v2)).normalized()
             vec2 = (position_vector(v1) - position_vector(v2)).normalized()
             if not isclose(vec1.length(), 0) and not isclose(vec2.length(), 0):
                 cosine = dot_product(vec1, vec2) / vec1.length() / vec2.length()
-                yield acos(cosine)
-    @property
-    def smallest_angle(self):
-        return min(
-            angle_magnitude_key(angle)
-            for angle in self.angles)
+                yield cosine
+            else:
+                yield 1 # cos(0)
     @property
     def hairline_ratio(self):
         lengths = [line.length for line in self.perimeter_lines]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/reference/collinearity.ldr	Tue Aug 25 23:04:27 2020 +0300
@@ -0,0 +1,3 @@
+3 16 0 0 0 0 100 0 0.01745 100 0
+3 16 0 0 0 0.0436 100 0 0.0436 -100 0
+4 16 0 0 0 -1 2 0 0 1 0 1 2 0
--- a/tests/quadrilaterals.py	Tue Aug 25 22:34:26 2020 +0300
+++ b/tests/quadrilaterals.py	Tue Aug 25 23:04:27 2020 +0300
@@ -25,14 +25,14 @@
 @problem_type('skew-major',
     severity = 'hold',
     message = lambda skew_angle:
-        str.format('skew quadrilateral (plane angle {})',
+        str.format('skew (non-coplanar) quadrilateral (plane angle {})',
             degree_rep(skew_angle),
         ),
 )
 @problem_type('skew-minor',
     severity = 'warning',
     message = lambda skew_angle:
-        str.format('slightly skew quadrilateral (plane angle {})',
+        str.format('slightly skew (non-coplanar) quadrilateral (plane angle {})',
             degree_rep(skew_angle),
         ),
 )
@@ -77,31 +77,34 @@
                 )
                 break
 
-@problem_type('collinear', severity = 'hold', message = 'collinear polygon')
+@problem_type('collinear',
+    severity = 'hold',
+    message = lambda min_angle, max_angle: str.format(
+        'collinear polygon (smallest interior angle {min_angle}, largest {max_angle})',
+        min_angle = degree_rep(min_angle),
+        max_angle = degree_rep(max_angle),
+    ),
+)
 def collinear_test(model):
+    from math import radians, cos, acos
+    # according to the LDraw spec, all interior angles must be 
+    # between 0.025 degrees and 179.9 degrees. Note that between 0 and pi,
+    # cos(x) is a downwards curve, so the minimum cosine is for the maximum
+    # angle.
+    min_cosine = cos(radians(179.9))
+    max_cosine = cos(radians(0.025))
     for element in model.body:
         if hasattr(element, 'geometry') and len(element.geometry.vertices) >= 3:
-            for a, b, c in pairs(element.geometry.vertices, count = 3):
-                if cross_product(b - a, c - a).length() < 1e-5:
-                    yield report_problem('collinear', bad_object = element)
-                    break
-
-@problem_type('hairline-polygon',
-    severity = 'warning',
-    message = lambda smallest_angle: str.format(
-        'hairline polygon (smallest angle {})',
-        degree_rep(smallest_angle),
-    ),
-)
-def hairline_test(model):
-    for element in model.body:
-        if hasattr(element, 'geometry') and len(element.geometry.vertices) >= 3:
-            smallest_angle = element.geometry.smallest_angle
-            if smallest_angle < radians(0.5):
+            cosines = list(element.geometry.angle_cosines())
+            if any(
+                not(min_cosine < cosine < max_cosine)
+                for cosine in cosines
+            ):
                 yield report_problem(
-                    'hairline-polygon',
+                    'collinear',
                     bad_object = element,
-                    smallest_angle = smallest_angle,
+                    min_angle = min(map(acos, cosines)),
+                    max_angle = max(map(acos, cosines)),
                 )
 
 manifest = {
@@ -110,6 +113,5 @@
         concave_test,
         bowtie_test,
         collinear_test,
-        hairline_test,
     ],
 }

mercurial