# HG changeset patch # User Santeri Piippo # Date 1513948423 -7200 # Node ID fea8e9ae6f29a811edaa17c596ec23ccc5580a9f # Parent 303c51137cb24d259ecf2052c341331e567cbc03 Added matrix code, moved library paths to ldcheck.cfg. diff -r 303c51137cb2 -r fea8e9ae6f29 geometry.py --- a/geometry.py Thu Dec 21 12:27:16 2017 +0200 +++ b/geometry.py Fri Dec 22 15:13:43 2017 +0200 @@ -205,28 +205,107 @@ def is_real(number): return isinstance(number, int) or isinstance(number, float) +def matrix_indices(): + ''' + Yields index pairs for matrix iteration + ''' + from itertools import product + yield from product(range(3), range(3)) + class Matrix3x3: ''' A 3×3 matrix forming the top-left portion of a full 4×4 transformation matrix. ''' - def __init__(self, values): - assert(all(is_real(x) for x in values)) - assert len(values) == 9 - self.values = values + class Row: + def __init__(self, matrix_ref, i1, i2, i3): + self.matrix_ref = matrix_ref + self.matrix_indices = i1, i2, i3 + def __getitem__(self, row_index): + return self.matrix_ref[self.matrix_indices[row_index]] + def __setitem__(self, row_index, value): + self.matrix_ref[self.matrix_indices[row_index]] = value + def __init__(self, values = None): + if values is not None: + assert(all(is_real(x) for x in values)) + assert len(values) == 9 + self.values = values + else: + self.values = [1, 0, 0, 0, 1, 0, 0, 0, 1] def __repr__(self): - return str.format('Matrix3x3({!r})', self.values) + if self != Matrix3x3(): + return str.format('Matrix3x3({!r})', self.values) + else: + return 'Matrix3x3()' def __getitem__(self, index): - return self.values[index] + if isinstance(index, tuple): + assert all(k in [0, 1, 2] for k in index) + return self.values[index[0] * 3 + index[1]] + else: + return Matrix3x3.Row(self, index * 3, index * 3 + 1, index * 3 + 2) def determinant(self): - v = self.values return sum([ - +(v[0] * v[4] * v[8]), - +(v[1] * v[5] * v[6]), - +(v[2] * v[3] * v[7]), - -(v[2] * v[4] * v[6]), - -(v[1] * v[3] * v[8]), - -(v[0] * v[5] * v[7]), + +(self[0, 0] * self[1, 1] * self[2, 2]), + +(self[0, 1] * self[1, 2] * self[2, 0]), + +(self[0, 2] * self[1, 0] * self[2, 1]), + -(self[0, 2] * self[1, 1] * self[2, 0]), + -(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. + ''' + from math import sqrt + return Vector( + x = sqrt(self[0, 0] ** 2 + self[1, 0] ** 2 + self[2, 0] ** 2), + 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. + ''' + return self.split()[0] + def split(self): + ''' + Extracts the rotation matrix and 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 + 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() + def __eq__(self, other): + ''' + Returns whether two matrices are equivalent. + ''' + return all( + abs(self[(i, j)] - other[(i, j)]) < 1e-10 + for i, j in matrix_indices() + ) + def __matmul__(self, other): + ''' + Computes the matrix multiplication self × other. + ''' + return Matrix3x3([ + sum(self[i, k] * other[k, j] for k in range(3)) + for i, j in matrix_indices() ]) def complete_matrix(matrix, anchor): @@ -235,9 +314,9 @@ transformation matrix. ''' return [ - matrix[0], matrix[1], matrix[2], anchor.x, - matrix[3], matrix[4], matrix[5], anchor.y, - matrix[6], matrix[7], matrix[8], anchor.z, + matrix[0, 0], matrix[0, 1], matrix[0, 2], anchor.x, + matrix[1, 0], matrix[1, 1], matrix[1, 2], anchor.y, + matrix[2, 0], matrix[2, 1], matrix[2, 2], anchor.z, 0, 0, 0, 1, ] @@ -245,16 +324,17 @@ ''' Transforms a vertex by a 4×4 transformation matrix. ''' - u = transformation_matrix[0] * vertex.x \ - + transformation_matrix[1] * vertex.y \ - + transformation_matrix[2] * vertex.z \ - + transformation_matrix[3] - v = transformation_matrix[4] * vertex.x \ - + transformation_matrix[5] * vertex.y \ - + transformation_matrix[6] * vertex.z \ - + transformation_matrix[7] - w = transformation_matrix[8] * vertex.x \ - + transformation_matrix[9] * vertex.y \ - + transformation_matrix[10] * vertex.z \ - + transformation_matrix[11] - return Vertex(u, v, w) + return Vertex( + x = transformation_matrix[0] * vertex.x \ + + transformation_matrix[1] * vertex.y \ + + transformation_matrix[2] * vertex.z \ + + transformation_matrix[3], + y = transformation_matrix[4] * vertex.x \ + + transformation_matrix[5] * vertex.y \ + + transformation_matrix[6] * vertex.z \ + + transformation_matrix[7], + z = transformation_matrix[8] * vertex.x \ + + transformation_matrix[9] * vertex.y \ + + transformation_matrix[10] * vertex.z \ + + transformation_matrix[11], + ) diff -r 303c51137cb2 -r fea8e9ae6f29 ldcheck.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ldcheck.py Fri Dec 22 15:13:43 2017 +0200 @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +from sys import version_info +if version_info < (3, 4): + raise RuntimeError('Python 3.4 or newer required') + +from parse import parse_ldraw_code +from colours import load_colours +from geometry import * +from pathlib import Path + +from os.path import realpath +script_directory = Path(realpath(__file__)).parent + +def load_config(filename): + from configobj import ConfigObj + from copy import deepcopy + config = ConfigObj(filename, encoding = 'UTF8') + read_config = deepcopy(config) + if 'libraries' not in config: + config['libraries'] = ['/path/to/ldraw'] + if config != read_config: + config.write() + return config + +def read_ldraw(file, *, config): + result = list() + for line in file: + result.append(parse_ldraw_code(line)) + return result + +def library_paths(config): + for library_path_string in config['libraries']: + yield Path(library_path_string).expanduser() + +def find_ldconfig_ldr_paths(config): + for library_path in library_paths(config): + ldconfig_paths = [ + library_path / 'LDConfig.ldr', + library_path / 'ldconfig.ldr', + ] + for path in ldconfig_paths: + print(path) + if path.is_file(): + yield path + +def hairline_score(smallest_angle): + from math import log10 + return max(0, -log10(smallest_angle)) + +if __name__ == '__main__': + from sys import argv + config = load_config('ldcheck.cfg') + for ldconfig_ldr_path in find_ldconfig_ldr_paths(config): + 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): + if hasattr(entry, 'colour'): + print(repr(entry.colour)) + if hasattr(entry, 'geometry') and len(entry.geometry) >= 3: + if hairline_score(entry.geometry.smallest_angle) >= 2.0: + print(str.format( + 'Hairline {type} at line {line_number}', + type = entry.typename(), + line_number = line_number, + )) + print(entry.textual_representation()) + print('-' * 25) \ No newline at end of file diff -r 303c51137cb2 -r fea8e9ae6f29 ldverify.py --- a/ldverify.py Thu Dec 21 12:27:16 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,50 +0,0 @@ -#!/usr/bin/env python3 -from sys import version_info -if version_info < (3, 4): - raise RuntimeError('Python 3.4 or newer required') - -from parse import parse_ldraw_code -from colours import load_colours -from geometry import * -from pathlib import Path - -def read_ldraw(file, *, libraries): - result = list() - for line in file: - result.append(parse_ldraw_code(line)) - return result - -def find_ldconfig_ldr_paths(libraries): - for library in libraries: - ldconfig_paths = [ - library['path'] / 'LDConfig.ldr', - library['path'] / 'ldconfig.ldr', - ] - for path in ldconfig_paths: - if path.is_file(): - yield path - -def hairline_score(smallest_angle): - from math import log10 - return max(0, -log10(smallest_angle)) - -if __name__ == '__main__': - from sys import argv - libraries = [{'path': Path('~/ldraw').expanduser(), 'role': 'official'}] - for ldconfig_ldr_path in find_ldconfig_ldr_paths(libraries): - with ldconfig_ldr_path.open() as ldconfig_ldr: - load_colours(ldconfig_ldr) - with open(argv[1], 'r') as file: - model = read_ldraw(file, libraries = libraries) - for line_number, entry in enumerate(model, 1): - if hasattr(entry, 'colour'): - print(repr(entry.colour)) - if hasattr(entry, 'geometry') and len(entry.geometry) >= 3: - if hairline_score(entry.geometry.smallest_angle) >= 2.0: - print(str.format( - 'Hairline {type} at line {line_number}', - type = entry.typename(), - line_number = line_number, - )) - print(entry.textual_representation()) - print('-' * 25) \ No newline at end of file