Tue, 25 Aug 2020 23:04:27 +0300
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
--- 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, ], }