Tue, 23 Jan 2018 15:30:48 +0200
added test for collinearity, fixed bowtie test
import linetypes import re from geometry import * from colours import Colour class BadLdrawLine(Exception): pass def parse_ldraw_code(line): if isinstance(line, bytes): line = line.decode() line = line.strip() if not line: return linetypes.EmptyLine() elif line == '0': return linetypes.Comment('') elif line.startswith('0 '): return linetypes.Comment(line[2:].strip()) elif line.startswith('1 '): return parse_ldraw_subfile_reference(line) elif line.startswith('2 '): return parse_ldraw_line(line) elif line.startswith('3 '): return parse_ldraw_triangle(line) elif line.startswith('4 '): return parse_ldraw_quadrilateral(line) elif line.startswith('5 '): return parse_ldraw_contour(line) else: raise BadLdrawLine('unknown line type') def parse_ldraw_subfile_reference(line): pattern = r'^1\s+([^ ]+)' + r'\s+([^ ]+)' * (3 + 9 + 1) + r'\s*$' match = re.search(pattern, line) if not match: raise BadLdrawLine('unable to parse') groups = list(match.groups()) indices = { 'colour_index': 0, 'anchor': slice(1, 4), 'matrix': slice(4, 13), 'subfile_path': 13 } try: colour = Colour(groups[indices['colour_index']]) vertex_values = [float(x) for x in groups[indices['anchor']]] matrix_values = [float(x) for x in groups[indices['matrix']]] except ValueError: raise BadLdrawLine('bad numeric values') return linetypes.SubfileReference( colour = colour, anchor = Vertex(*vertex_values), matrix = Matrix3x3(matrix_values), subfile_path = groups[indices['subfile_path']] ) def generic_parse_polygon(line, *, type_code, vertex_count): pattern = r'^' \ + str(type_code) \ + '\s+([^ ]+)' \ + r'\s+([^ ]+)' * (vertex_count * 3) \ + r'\s*$' match = re.search(pattern, line) if not match: raise BadLdrawLine(str.format('cannot parse type-{} line', type_code)) vertices = [] for vertex_index in range(vertex_count): slice_begin = 1 + vertex_index * 3 slice_end = 1 + (vertex_index + 1) * 3 coordinates = match.groups()[slice_begin:slice_end] assert(len(coordinates) == 3) try: coordinates = [float(x) for x in coordinates] except ValueError: raise BadLdrawLine('bad numeric values') vertices.append(Vertex(*coordinates)) return { 'colour': Colour(match.group(1)), 'vertices': vertices, } def parse_ldraw_line(line): parse_result = generic_parse_polygon(line, type_code = 2, vertex_count = 2) return linetypes.LineSegment( colour = parse_result['colour'], geometry = LineSegment(*parse_result['vertices']), ) def parse_ldraw_triangle(line): parse_result = generic_parse_polygon(line, type_code = 3, vertex_count = 3) return linetypes.Triangle( colour = parse_result['colour'], geometry = Polygon(parse_result['vertices']), ) def parse_ldraw_quadrilateral(line): parse_result = generic_parse_polygon(line, type_code = 4, vertex_count = 4) return linetypes.Quadrilateral( colour = parse_result['colour'], geometry = Polygon(parse_result['vertices']), ) def parse_ldraw_contour(line): parse_result = generic_parse_polygon(line, type_code = 5, vertex_count = 4) return linetypes.Contour( colour = parse_result['colour'], geometry = LineSegment(*parse_result['vertices'][0:2]), control_points = parse_result['vertices'][2:], )