9 library_standards = ConfigParser() |
9 library_standards = ConfigParser() |
10 |
10 |
11 with ini_path.open() as file: |
11 with ini_path.open() as file: |
12 library_standards.read_file(file) |
12 library_standards.read_file(file) |
13 |
13 |
|
14 @problem_type('zero-determinant', |
|
15 severity = 'error', |
|
16 message = 'matrix row or column all zero' |
|
17 ) |
14 def determinant_test(model): |
18 def determinant_test(model): |
15 ''' |
19 ''' |
16 Checks all subfile references for matrices with rows or columns all |
20 Checks all subfile references for matrices with rows or columns all |
17 zero. |
21 zero. |
18 ''' |
22 ''' |
19 yield from ( |
23 yield from ( |
20 error(subfile_reference, 'zero-determinant') |
24 report_problem('zero-determinant', bad_object = subfile_reference) |
21 for subfile_reference in model.subfile_references |
25 for subfile_reference in model.subfile_references |
22 if abs(subfile_reference.matrix.determinant() - 0) < 1e-15 |
26 if abs(subfile_reference.matrix.determinant() - 0) < 1e-15 |
23 ) |
27 ) |
24 |
28 |
25 def scaling_description(scaling, axes = 'xyz'): |
29 def scaling_description(scaling, axes = 'xyz'): |
26 ''' |
30 ''' |
27 Returns a pretty description of a scaling vector. The axes parameter |
31 Returns a pretty description of a scaling vector. The axes parameter |
28 controls what axes are printed and can be used to filter away |
32 controls what axes are printed and can be used to filter away |
29 uninteresting values. |
33 uninteresting values. |
30 ''' |
34 ''' |
31 return ', '.join( |
35 if isinstance(scaling, str): |
32 str.format('{} = {}', letter, getattr(scaling, letter)) |
36 return scaling |
33 for letter in axes |
37 else: |
34 ) |
38 return ', '.join( |
|
39 str.format('{} = {}', letter, getattr(scaling, letter)) |
|
40 for letter in axes |
|
41 ) |
35 |
42 |
36 def check_scaling(scaling, axes): |
43 def check_scaling(scaling, axes): |
37 ''' Returns whether all given axes on the given scaling vector are 1. ''' |
44 ''' Returns whether all given axes on the given scaling vector are 1. ''' |
38 return all( |
45 return all( |
39 abs(getattr(scaling, axis) - 1) < 1e-5 |
46 abs(getattr(scaling, axis) - 1) < 1e-5 |
49 # check if y-scaling is 1 or -1 |
56 # check if y-scaling is 1 or -1 |
50 abs(abs(scaling.y) - 1) < 1e-5, |
57 abs(abs(scaling.y) - 1) < 1e-5, |
51 ]), |
58 ]), |
52 } |
59 } |
53 |
60 |
|
61 @problem_type('illegal-scaling', |
|
62 severity = 'error', |
|
63 message = lambda primitive, scaling, axes: |
|
64 str.format('scaling of unscalable primitive {} ({})', |
|
65 primitive, |
|
66 scaling_description(scaling, axes), |
|
67 ), |
|
68 ) |
54 def scaling_legality_test(model): |
69 def scaling_legality_test(model): |
55 ''' |
70 ''' |
56 Checks the part against primitive references with bad scaling. Some |
71 Checks the part against primitive references with bad scaling. Some |
57 primitives (e.g. pegs) are not allowed to be scaled in the |
72 primitives (e.g. pegs) are not allowed to be scaled in the |
58 X or Z directions. Some (e.g. most studs) are not allowed to be scaled |
73 X or Z directions. Some (e.g. most studs) are not allowed to be scaled |
78 interesting_axes = ''.join( |
93 interesting_axes = ''.join( |
79 axis |
94 axis |
80 for axis in 'xyz' |
95 for axis in 'xyz' |
81 if abs(getattr(scaling, axis) - 1) > 1e-5 |
96 if abs(getattr(scaling, axis) - 1) > 1e-5 |
82 ) |
97 ) |
83 yield warning(subfile_reference, 'illegal-scaling', |
98 yield report_problem('illegal-scaling', |
|
99 bad_object = subfile_reference, |
84 primitive = primitive, |
100 primitive = primitive, |
85 axes = interesting_axes, |
101 axes = interesting_axes, |
86 scaling = scaling) |
102 scaling = scaling, |
87 |
103 ) |
|
104 |
|
105 @problem_type('cyclical-reference', |
|
106 severity = 'error', |
|
107 message = lambda chain: |
|
108 str.format('cyclical subfile dependency: {chain}', |
|
109 **locals(), |
|
110 ), |
|
111 ) |
|
112 @problem_type('bad-subfile', |
|
113 severity = 'error', |
|
114 message = lambda path, problem_text: |
|
115 str.format('cannot process subfile "{path}": {problem_text}', |
|
116 **locals(), |
|
117 ), |
|
118 ) |
|
119 @problem_type('moved-file-used', |
|
120 severity = 'error', |
|
121 message = lambda moved_file, new_file: |
|
122 str.format('subfile "{moved_file}" has been moved to "{new_file}"', |
|
123 **locals(), |
|
124 ), |
|
125 ) |
|
126 @problem_type('unnecessary-scaling', |
|
127 severity = 'notice', |
|
128 message = lambda scaled_flat_dimensions, scaling_vector: |
|
129 str.format( |
|
130 'subfile unnecessarily scaled in the {dims} ({scaling})', |
|
131 dims = dimensions_description(scaled_flat_dimensions), |
|
132 scaling = scaling_description(scaling_vector), |
|
133 ), |
|
134 ) |
88 def dependent_subfile_tests(model): |
135 def dependent_subfile_tests(model): |
89 ''' |
136 ''' |
90 Tests subfile references for such qualities that are dependent on the |
137 Tests subfile references for such qualities that are dependent on the |
91 actual contents of the subfiles. Checks whether moved-to files are used. |
138 actual contents of the subfiles. Checks whether moved-to files are used. |
92 Checks whether flat subfiles are scaled in the flat direction. |
139 Checks whether flat subfiles are scaled in the flat direction. |
102 else: |
149 else: |
103 try: |
150 try: |
104 subfile = cache.prepare_file(path) |
151 subfile = cache.prepare_file(path) |
105 except filecache.CyclicalReferenceError as e: |
152 except filecache.CyclicalReferenceError as e: |
106 failed_subfiles.add(path) |
153 failed_subfiles.add(path) |
107 yield error(subfile_reference, 'cyclical-reference', |
154 yield report_problem( |
|
155 'cyclical-reference', |
|
156 bad_object = subfile_reference, |
108 chain = str(e), |
157 chain = str(e), |
109 ) |
158 ) |
110 if not subfile.valid: |
159 if not subfile.valid: |
111 yield error(subfile_reference, 'bad-subfile', |
160 yield report_problem( |
|
161 'bad-subfile', |
|
162 bad_object = subfile_reference, |
112 path = path, |
163 path = path, |
113 problem_text = subfile.problem, |
164 problem_text = subfile.problem, |
114 ) |
165 ) |
115 failed_subfiles.add(path) |
166 failed_subfiles.add(path) |
116 else: |
167 else: |
117 import re |
168 import re |
118 match = re.search(r'^\~Moved(?: to (\w+))?$', subfile.description) |
169 match = re.search(r'^\~Moved(?: to (\w+))?$', subfile.description) |
119 if match: |
170 if match: |
120 yield error(subfile_reference, 'moved-file-used', |
171 yield report_problem( |
|
172 'moved-file-used', |
|
173 bad_object = subfile_reference, |
121 moved_file = path, |
174 moved_file = path, |
122 new_file = match.group(1)) |
175 new_file = match.group(1), |
|
176 ) |
123 scaling_vector = subfile_reference.matrix.scaling_vector() |
177 scaling_vector = subfile_reference.matrix.scaling_vector() |
124 scaled_dimensions = { |
178 scaled_dimensions = { |
125 dimension |
179 dimension |
126 for dimension in subfile.flatness |
180 for dimension in subfile.flatness |
127 if not math.isclose( |
181 if not math.isclose( |
130 abs_tol = 1.0e-05 |
184 abs_tol = 1.0e-05 |
131 ) |
185 ) |
132 } |
186 } |
133 scaled_flat_dimensions = subfile.flatness & scaled_dimensions |
187 scaled_flat_dimensions = subfile.flatness & scaled_dimensions |
134 if scaled_flat_dimensions: |
188 if scaled_flat_dimensions: |
135 yield testsuite.notice(subfile_reference, 'unnecessary-scaling', |
189 yield report_problem( |
|
190 'unnecessary-scaling', |
|
191 bad_object = subfile_reference, |
136 scaled_flat_dimensions = scaled_flat_dimensions, |
192 scaled_flat_dimensions = scaled_flat_dimensions, |
137 scaling_vector = scaling_vector, |
193 scaling_vector = scaling_vector, |
138 ) |
194 ) |
139 |
195 |
140 def dimensions_description(dimensions): |
196 def dimensions_description(dimensions): |
141 sorted_dims = sorted(dimensions) |
197 if isinstance(dimensions, str): |
142 if len(sorted_dims) == 1: |
198 return dimensions |
143 return sorted_dims[0] + ' dimension' |
|
144 else: |
199 else: |
145 return str.format('{} and {} dimensions', |
200 sorted_dims = sorted(dimensions) |
146 ', '.join(sorted_dims[:-1]), |
201 if len(sorted_dims) == 1: |
147 sorted_dims[-1], |
202 return sorted_dims[0] + ' dimension' |
148 ) |
203 else: |
|
204 return str.format('{} and {} dimensions', |
|
205 ', '.join(sorted_dims[:-1]), |
|
206 sorted_dims[-1], |
|
207 ) |
149 |
208 |
150 manifest = { |
209 manifest = { |
151 'tests': { |
210 'tests': [ |
152 'determinant': determinant_test, |
211 determinant_test, |
153 'scaling-legality': scaling_legality_test, |
212 scaling_legality_test, |
154 'dependent-subfiles': dependent_subfile_tests, |
213 dependent_subfile_tests, |
155 }, |
214 ], |
156 'messages': { |
|
157 'zero-determinant': 'matrix determinant is zero ' |
|
158 '(row or column all zero)', |
|
159 'illegal-scaling': lambda primitive, scaling, axes: |
|
160 str.format('scaling of unscalable primitive {} ({})', |
|
161 primitive, |
|
162 scaling_description(scaling, axes), |
|
163 ), |
|
164 'cyclical-reference': lambda chain: |
|
165 str.format('cyclical subfile dependency: {chain}', |
|
166 **locals(), |
|
167 ), |
|
168 'bad-subfile': lambda path, problem_text: |
|
169 str.format('cannot process subfile "{path}": {problem_text}', |
|
170 **locals(), |
|
171 ), |
|
172 'moved-file-used': lambda moved_file, new_file: |
|
173 str.format('subfile "{moved_file}" has been moved to "{new_file}"', |
|
174 **locals(), |
|
175 ), |
|
176 'unnecessary-scaling': lambda scaled_flat_dimensions, scaling_vector: |
|
177 str.format( |
|
178 'subfile unnecessarily scaled in the {dims} ({scaling})', |
|
179 dims = dimensions_description(scaled_flat_dimensions), |
|
180 scaling = scaling_description(scaling_vector), |
|
181 ) |
|
182 }, |
|
183 } |
215 } |