Thu, 21 Dec 2017 12:27:16 +0200
Added ldconfig.ldr support
colours.py | file | annotate | diff | comparison | revisions | |
ldraw.py | file | annotate | diff | comparison | revisions | |
ldverify.py | file | annotate | diff | comparison | revisions | |
parse.py | file | annotate | diff | comparison | revisions |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/colours.py Thu Dec 21 12:27:16 2017 +0200 @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +class Colour: + ''' + Colour interface. Supports LDConfig colours and direct colours. + For LDConfig colours to work, load_colours must be called first. + ''' + def __init__(self, index): + if not isinstance(index, int): + index = int(index, 0) + self.index = index + def __str__(self): + if self.is_direct_colour: + # write direct colours with hex codes + return '0x%07X' % self.index + else: + return str(self.index) + def __repr__(self): + try: + return 'colours.' + colours_inverse_dict[self.index] + except KeyError: + return str.format('Colour({!r})', self.index) + @property + def is_direct_colour(self): + return self.index >= 0x2000000 + @property + def is_ldconfig_colour(self): + return self.index in ldconfig_colour_data + @property + def name(self): + if self.is_ldconfig_colour: + return ldconfig_colour_data[self.index]['name'] + else: + return str(self) + @property + def face_colour(self): + if self.is_ldconfig_colour: + return ldconfig_colour_data[self.index]['value'] + elif self.is_direct_colour: + return '#%06X' % (self.index & 0xffffff) + else: + return '#000000' + @property + def is_valid(self): + return self.is_ldconfig_colour or self.is_direct_colour + def __eq__(self, other): + return self.index == other.index + def __lt__(self, other): + return self.index < other.index + def __le__(self, other): + return self.index <= other.index + def __gt__(self, other): + return self.index > other.index + def __ge__(self, other): + return self.index >= other.index + +def parse_ldconfig_ldr_line(line): + ''' + Parses a single LDConfig.ldr line. + + Returns: + · a dict for a successful parsed colour. + · None for empty lines and comments. + + Raises an error for invalid lines. + ''' + line = line.strip() + import re + def parse_tag(tag): + match = re.search(tag + r'\s+([^ ]+)', line) + if match: + return match.group(1) + else: + raise KeyError(str.format('Tag {} not found', tag)) + # parse 0 !COLOUR and get the name + match = re.search(r'^0\s+!COLOUR\s([^ ]+)', line) + if not match: + # failed, check if it's an empty line or comment: + if not line or line.startswith('0'): + return None + else: + # failed too, thus the line is bad + raise ValueError('Bad LDConfig.ldr line') + # replace underscores for readability + name = match.group(1).replace('_', ' ') + # parse tags + code = int(parse_tag('CODE')) + value = parse_tag('VALUE') + edge = parse_tag('EDGE') + return { + 'name': name, + 'code': code, + 'value': value, + 'edge': edge, + } + +def parse_ldconfig_ldr(ldconfig_ldr): + ''' + Parses LDConfig.ldr and returns pairs + ''' + for line in ldconfig_ldr: + colour = parse_ldconfig_ldr_line(line) + if colour: + yield (colour['code'], colour) + +class colours: + ''' + LDConfig colour namespace, exists for interactive mode and for + Colour.__repr__ to return something pretty. + ''' + pass + +# LDConfig lookup tables +colours_inverse_dict = {} +ldconfig_colour_data = {} + +def load_colours(ldconfig_ldr): + ''' + Loads colours. Expects a file pointer to LDConfig.ldr as the parameter. + ''' + global ldconfig_colour_data + ldconfig_colour_data = dict(parse_ldconfig_ldr(ldconfig_ldr)) + for index, colour in ldconfig_colour_data.items(): + identifier = colour['name'].replace(' ', '_').lower() + setattr(colours, identifier, Colour(index)) + colours_inverse_dict[index] = identifier + +# Interactive mode support (pass LDConfig.ldr path as a command-line argument) +if __name__ == '__main__': + from sys import argv + with open(argv[1]) as ldconfig_ldr: + load_colours(ldconfig_ldr) \ No newline at end of file
--- a/ldraw.py Thu Dec 21 10:46:41 2017 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -class Colour: - def __init__(self, index): - self.index = int(index, 0) - def __str__(self): - if self.is_direct_colour(): - # write direct colours with hex codes - return '0x%07X' % self.index - else: - return str(self.index) - def is_direct_colour(self): - return self.index >= 0x2000000
--- a/ldverify.py Thu Dec 21 10:46:41 2017 +0200 +++ b/ldverify.py Thu Dec 21 12:27:16 2017 +0200 @@ -1,6 +1,12 @@ #!/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() @@ -8,17 +14,31 @@ 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': '/home/teemu/ldraw', 'role': 'official'}] + 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) - min_angle_tup = (1e12,) 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(