parse.py

Tue, 28 May 2019 19:11:01 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Tue, 28 May 2019 19:11:01 +0300
changeset 50
0193f8820ba8
parent 47
4da025d0b283
child 54
0c686d10eb49
permissions
-rw-r--r--

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:],
    )

mercurial