# HG changeset patch # User Santeri Piippo # Date 1516539818 -7200 # Node ID 12d4ddc4bfd8f61c83a18a7277416bb1d1540c1d # Parent eb74680a5e43cd10354eb1a9aaf482b2f8036e4f Got the skew test working diff -r eb74680a5e43 -r 12d4ddc4bfd8 geometry.py --- a/geometry.py Fri Jan 19 13:41:23 2018 +0200 +++ b/geometry.py Sun Jan 21 15:03:38 2018 +0200 @@ -67,7 +67,12 @@ def __matmul__(self, transformation_matrix): return transform(self, transformation_matrix) def __eq__(self, other): - return self.coordinates == other.coordinates + return all( + abs(a - b) < 1e-8 + for a, b in zip(self.coordinates, other.coordinates) + ) + def __ne__(self, other): + return not self.__eq__(other) def __lt__(self, other): return self.coordinates < other.coordinates def __hash__(self): @@ -146,6 +151,9 @@ ]).determinant() return Vector(x, y, z).normalized() +class NoIntersection(Exception): + pass + def pairs(iterable, *, count = 2): ''' Iterates the given iterable and returns tuples containing `count` diff -r eb74680a5e43 -r 12d4ddc4bfd8 ldcheck.py --- a/ldcheck.py Fri Jan 19 13:41:23 2018 +0200 +++ b/ldcheck.py Sun Jan 21 15:03:38 2018 +0200 @@ -7,6 +7,7 @@ from colours import load_colours from geometry import * from pathlib import Path +import linetypes from os.path import realpath script_directory = Path(realpath(__file__)).parent @@ -47,6 +48,15 @@ from math import log10 return max(0, -log10(smallest_angle)) +class Model: + def __init__(self, body): + self.body = body + @property + def quadrilaterals(self): + yield from filter( + lambda element: isinstance(element, linetypes.Quadrilateral), + self.body) + if __name__ == '__main__': from sys import argv config = load_config('ldcheck.cfg') @@ -54,8 +64,9 @@ with ldconfig_ldr_path.open() as ldconfig_ldr: load_colours(ldconfig_ldr) with open(argv[1], 'r') as file: - model = read_ldraw(file, config = config) - for line_number, entry in enumerate(model, 1): + model_body = read_ldraw(file, config = config) + model = Model(body = model_body) + for line_number, entry in enumerate(model_body, 1): if hasattr(entry, 'colour'): print(repr(entry.colour)) if hasattr(entry, 'geometry') and len(entry.geometry) >= 3: @@ -66,4 +77,4 @@ line_number = line_number, )) print(entry.textual_representation()) - print('-' * 25) \ No newline at end of file + print('-' * 25) diff -r eb74680a5e43 -r 12d4ddc4bfd8 reference/quadrilaterals.ldr --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/reference/quadrilaterals.ldr Sun Jan 21 15:03:38 2018 +0200 @@ -0,0 +1,19 @@ +0 // square +4 16 -20 0 10 -20 0 20 -10 0 20 -10 0 10 +0 // concave +4 16 10 0 10 5 0 25 10 0 20 15 0 25 +0 // kite +4 16 -15 0 -15 -20 0 0 -15 0 5 -10 0 0 +0 // bowtie +4 16 5 0 -5 5 0 -15 15 0 -5 15 0 -15 +0 // trapezium +4 16 25 0 10 30 0 20 35 0 20 40 0 10 +0 // skew +4 16 25 0 -5 25 0 -15 35 0 -15 35 5 -5 +4 16 40 0 -5 40 0 -15 50 5 -15 50 0 -5 +0 // parallelogram +4 16 25 0 25 30 0 35 40 0 35 35 0 25 +0 // rhombus +4 16 -5 0 40 -15 0 35 -5 0 30 5 0 35 +0 // collinear +4 16 20 0 40 30 0 45 30 0 50 30 0 55 diff -r eb74680a5e43 -r 12d4ddc4bfd8 tests/concave.py --- a/tests/concave.py Fri Jan 19 13:41:23 2018 +0200 +++ b/tests/concave.py Sun Jan 21 15:03:38 2018 +0200 @@ -1,5 +1,5 @@ -from math import degrees, pi as π -from testsuite import warning +from math import acos, degrees, radians, pi as π +from testsuite import warning, error from geometry import * def sign_consistency(container): @@ -11,7 +11,7 @@ def concave_test(model): - for quadrilateral in model: + for quadrilateral in model.quadrilaterals: print([cross_product(v2 - v1, v3 - v1) for v1, v2, v3 in pairs(quadrilateral.geometry.vertices, count = 3)]) z_scores = [ cross_product(v2 - v1, v3 - v1).z @@ -19,4 +19,44 @@ ] print(z_scores) if not sign_consistency(z_scores): - yield warning(quadrilateral, 'Concave quadrilateral') \ No newline at end of file + yield warning(quadrilateral, 'Concave quadrilateral') + +def bowtie_quadrilateral_test(model): + for quadrilateral in model.quadrilaterals: + vertices = IndexRing(quadrilateral.geometry.vertices) + for i in (0, 1): + line1 = LineSegment(vertices[0 + i], vertices[1 + i]) + line2 = LineSegment(vertices[2 + i], vertices[3 + i]) + try: + line_intersection(line1, line2) + except NoIntersection: + pass + else: + yield error(quadrilateral, 'Bowtie quadrilateral') + break + +def vector_angle(vec_1, vec_2, normalized = False): + cosine = dot_product(vec_1, vec_2) + try: + cosine /= vec_1.length() * vec_2.length() + except ZeroDivisionError: + return 0 + angle = acos(cosine) + if normalized and angle > π / 2: + angle = π - angle + return angle + +def skew_test(model): + for quadrilateral in model.quadrilaterals: + vertices = IndexRing(quadrilateral.geometry.vertices) + for i in (0, 1): + a, b = vertices[0 + i], vertices[1 + i] + c, d = vertices[2 + i], vertices[3 + i] + plane_1 = cross_product(b - a, d - a) + plane_2 = cross_product(d - c, b - c) + angle = vector_angle(plane_1, plane_2, normalized = True) + if angle > radians(0.1): + yield error(quadrilateral, + 'Skew quadrilateral (plane angle {}°)', + '%.3f' % degrees(angle)) + break diff -r eb74680a5e43 -r 12d4ddc4bfd8 testsuite.py --- a/testsuite.py Fri Jan 19 13:41:23 2018 +0200 +++ b/testsuite.py Sun Jan 21 15:03:38 2018 +0200 @@ -3,4 +3,11 @@ 'type': 'warning', 'object': object, 'message': str.format(*message), - } \ No newline at end of file + } + +def error(object, *message): + return { + 'type': 'error', + 'object': object, + 'message': str.format(*message), + }