Mon, 22 Jan 2018 21:00:45 +0200
added test for forbidden primitive scaling
geometry.py | file | annotate | diff | comparison | revisions | |
tests/library-standards.ini | file | annotate | diff | comparison | revisions | |
tests/subfiles.py | file | annotate | diff | comparison | revisions |
--- a/geometry.py Mon Jan 22 18:23:25 2018 +0200 +++ b/geometry.py Mon Jan 22 21:00:45 2018 +0200 @@ -67,16 +67,18 @@ def __matmul__(self, transformation_matrix): return transform(self, transformation_matrix) def __eq__(self, other): - return all( - abs(a - b) < 1e-8 - for a, b in zip(self.coordinates, other.coordinates) - ) + return self.is_close(other, threshold = 1e-10) def __ne__(self, other): return not self.__eq__(other) def __lt__(self, other): return self.coordinates < other.coordinates def __hash__(self): return hash(self.coordinates) + def is_close(self, other, *, threshold): + return all( + abs(a - b) < threshold + for a, b in zip(self.coordinates, other.coordinates) + ) class VertexOp: def __init__(self, callable): @@ -263,7 +265,6 @@ -(self[0, 1] * self[1, 0] * self[2, 2]), -(self[0, 0] * self[1, 2] * self[2, 1]), ]) - @property def scaling_vector(self): ''' Extracts scaling factors from this transformation matrix. @@ -274,7 +275,6 @@ y = sqrt(self[0, 1] ** 2 + self[1, 1] ** 2 + self[2, 1] ** 2), z = sqrt(self[0, 2] ** 2 + self[1, 2] ** 2 + self[2, 2] ** 2), ) - @property def rotation_component(self): ''' Extracts rotation from this matrix. @@ -284,24 +284,22 @@ ''' Extracts the rotation matrix and scaling vector. ''' - vec = self.scaling_vector + vec = self.scaling_vector() return Matrix3x3([ self[i, j] / vec.coordinates[j] for i, j in matrix_indices() ]), vec - @property def contains_scaling(self): ''' Returns whether this matrix contains scaling factors. ''' - vec = self.scaling_vector + vec = self.scaling_vector() return abs((vec.x * vec.y * vec.z) - 1) >= 1e-10 - @property def contains_rotation(self): ''' Returns whether this matrix contains rotation factors. ''' - return self.rotation_component != Matrix3x3() + return self.rotation_component() != Matrix3x3() def __eq__(self, other): ''' Returns whether two matrices are equivalent.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/library-standards.ini Mon Jan 22 21:00:45 2018 +0200 @@ -0,0 +1,48 @@ +[scaling restrictions] +axle.dat = y-scaling only +axleend.dat = y-scaling only +axl?hol?.dat = y-scaling only +axlesphe.dat = no scaling +bushloc?.dat = no scaling +steerend.dat = no scaling +connect.dat = no scaling +connect?.dat = no scaling +confric?.dat = no scaling +connhol?.dat = no scaling +beamhole.dat = no scaling +peghole.dat = y-scaling only +peghole?.dat = y-scaling only +npeghol?.dat = y-scaling only +tooth*.dat = no scaling +daxle.dat = y-scaling only +daxlehole.dat = y-scaling only +daxlehub.dat = y-scaling only +dtoothc.dat = no scaling +stug?*.dat = no scaling +arm?.dat = no scaling +clh?*.dat = no scaling +4-4crh?.dat = no scaling +clip?.dat = no scaling +clip??.dat = no scaling +finger1.dat = no scaling +h[12].dat = no scaling +plug34.dat = no scaling +wpin*.dat = no scaling +bump5000.dat = no scaling +slotm.dat = no scaling +handle*.dat = no scaling +rail12v.dat = no scaling +primotop.dat = no scaling +primobot.dat = no scaling +zstud.dat = no scaling +# no scaling on studs +stud*.dat = no scaling +# allow y-scaling only on stud4 for historical reasons +stud4.dat = y-scaling only +# allow -1 y-scaling only on some stud primitives for historical reasons +stud3.dat = stud3-like scaling +stud4s.dat = stud3-like scaling +stud4s2.dat = stud3-like scaling +stud16.dat = stud3-like scaling +stud21a.dat = stud3-like scaling +stud22a.dat = stud3-like scaling
--- a/tests/subfiles.py Mon Jan 22 18:23:25 2018 +0200 +++ b/tests/subfiles.py Mon Jan 22 21:00:45 2018 +0200 @@ -1,18 +1,90 @@ -from testsuite import error +from testsuite import warning, error +from geometry import * +from os.path import dirname +from pathlib import Path +from configparser import ConfigParser +ini_path = Path(dirname(__file__)) / 'library-standards.ini' +library_standards = ConfigParser() + +with open(ini_path) as file: + library_standards.read_file(file) def determinant_test(model): - yield from [ + yield from ( error(subfile_reference, 'zero-determinant') for subfile_reference in model.subfile_references if abs(subfile_reference.matrix.determinant() - 0) < 1e-15 - ] + ) + +def scaling_description(scaling, axes = 'xyz'): + ''' + Returns a pretty description of a scaling vector. The axes parameter + controls what axes are printed and can be used to filter away + uninteresting values. + ''' + return ', '.join( + str.format('{} = {}', letter, getattr(scaling, letter)) + for letter in axes + ) + +def check_scaling(scaling, axes): + ''' Returns whether all given axes on the given scaling vector are 1. ''' + return all( + abs(getattr(scaling, axis) - 1) < 1e-5 + for axis in axes + ) + +# Restriction to checking function mapping. +restriction_tests = { + 'no scaling': lambda scaling: check_scaling(scaling, 'xyz'), + 'y-scaling only': lambda scaling: check_scaling(scaling, 'xz'), + 'stud3-like scaling': lambda scaling: all([ + check_scaling(scaling, 'xz'), + # check if y-scaling is 1 or -1 + abs(abs(scaling.y) - 1) < 1e-5, + ]), +} + +def scaling_legality_test(model): + from fnmatch import fnmatch + scaling_restrictions = library_standards['scaling restrictions'] + for subfile_reference in model.subfile_references: + primitive = subfile_reference.subfile_path.lower() + scaling = subfile_reference.matrix.scaling_vector() + # Find all restrictions that apply to this subfile reference. + restrictions = { + restriction + for pattern, restriction in scaling_restrictions.items() + if fnmatch(primitive, pattern) + } + # Check restrictions against the subfile. If any restrictions were + # found then the scaling vector must pass at least one of them. + if restrictions and not any( + restriction_tests[restriction](scaling) + for restriction in restrictions + ): + interesting_axes = ''.join( + axis + for axis in 'xyz' + if abs(getattr(scaling, axis) - 1) > 1e-5 + ) + yield warning(subfile_reference, 'illegal-scaling', + primitive = primitive, + axes = interesting_axes, + scaling = scaling) manifest = { 'tests': { 'determinant': determinant_test, + 'scaling-legality': scaling_legality_test, }, 'messages': { 'zero-determinant': 'matrix determinant is zero ' '(row or column all zero)', + 'illegal-scaling': lambda primitive, scaling, axes: + str.format('scaling of unscalable primitive {} ({})', + primitive, + scaling_description(scaling, axes), + ), }, }