Tue, 28 May 2019 19:11:01 +0300
added primitive CCW test
import linetypes import re from geometry import * from colours import Colour from testsuite import error class BadLdrawLine(Exception): pass def parse_ldraw_code(line): try: if isinstance(line, bytes): line = line.decode() line = line.strip() if not line: return linetypes.EmptyLine() elif line == '0': return linetypes.MetaCommand('') elif line.startswith('0 '): return parse_ldraw_meta_line(line) 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_conditional_line(line) else: raise BadLdrawLine('unknown line type') except BadLdrawLine as error: return linetypes.Error(line, str(error)) def parse_ldraw_meta_line(line): if line.startswith('0 //'): return linetypes.Comment(line[4:]) else: return linetypes.MetaCommand(line[2:]) 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'^' # matches the start of line pattern += str(type_code) # matches the type code pattern += '\s+([^ ]+)' # matches the colour pattern += r'\s+([^ ]+)' * (vertex_count * 3) # matches the vertices pattern += r'\s*$' # matches any trailing space 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)) try: colour = int(match.group(1), 0) except ValueError: raise BadLdrawLine('invalid syntax for colour: ' + repr(match.group(1))) return { 'colour': Colour(colour), '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_conditional_line(line): parse_result = generic_parse_polygon(line, type_code = 5, vertex_count = 4) return linetypes.ConditionalLine( colour = parse_result['colour'], geometry = LineSegment(*parse_result['vertices'][0:2]), control_points = parse_result['vertices'][2:], )