added test for forbidden primitive scaling

Mon, 22 Jan 2018 21:00:45 +0200

author
Santeri Piippo
date
Mon, 22 Jan 2018 21:00:45 +0200
changeset 24
f8080ffceaa9
parent 23
1b9645b7ddb0
child 25
8990ac138cc2

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),
+            ),
     },
 }

mercurial