filecache.py

Tue, 25 Aug 2020 23:04:27 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Tue, 25 Aug 2020 23:04:27 +0300
changeset 103
662de6b8cfc2
parent 95
a3536e51f6bc
child 145
fde18c4d6784
permissions
-rw-r--r--

replaced the collinear test with a new one based on the hairline test that checks interior angles against official limits of 0.025 and 179.9

import datetime
import parse
import os
from pathlib import Path
import linetypes
import math

def find_ldraw_file(filename, libraries):
    '''
        Searches the LDraw libraries for the specified file.
        `libraries` must be an iterable of LDraw library root paths.
    '''
    if os.path.sep != "\\":
        filename = filename.replace("\\", os.path.sep)
    for library in libraries:
        for path in [
            Path(library) / filename,
            Path(library) / "parts" / filename,
            Path(library) / "p" / filename,
        ]:
            if path.exists():
                return path
    raise FileNotFoundError(str.format(
        '"{}" not found in the LDraw libraries',
        filename,
    ))

def is_logo_stud_name(name):
    return name in ['stud.dat', 'stud3.dat']

class CyclicalReferenceError(Exception):
    pass

class SubfileCache:
    class Subfile:
        def __init__(self):
            self.valid = None
            self.flatness = None
            self.description = None
            self.problem = None
            self.has_studs = None # Whether or not it contains studs
            self.vertices = set()
    def __init__(self, ldraw_directories):
        '''
            Initializes a new subfile cache
        '''
        self.cache = dict()
        if ldraw_directories and isinstance(ldraw_directories[0], str):
            self.ldraw_directories = [
                Path(os.path.expanduser(directory))
                for directory in ldraw_directories
            ]
        else:
            from copy import copy
            self.ldraw_directories = copy(ldraw_directories)
        self.reference_stack = []
    def flatness_of(self, filename):
        '''
            Returns the set of all directiones the specified file is flat in.
        '''
        self.prepare_file(filename)
        return self.cache[filename].flatness
    def description_of(self, filename):
        '''
            Returns the description of the specified file
        '''
        self.prepare_file(filename)
        return self.cache[filename].description
    def find_file(self, filename):
        return find_ldraw_file(
            filename = filename,
            libraries = self.ldraw_directories,
        )
    def prepare_file(self, filename):
        '''
            Loads the file if not loaded yet.
        '''
        if filename not in self.cache:
            self._load_subfile(filename)
        return self.cache[filename]
    def _load_subfile(self, filename):
        # ward against cyclical dependencies
        if filename in self.reference_stack:
            raise CyclicalReferenceError(
                ' -> '.join(self.reference_stack + [filename]),
            )
        self.reference_stack.append(filename)
        subfile = SubfileCache.Subfile()
        self.cache[filename] = subfile
        try:
            path = self.find_file(filename)
            with path.open('rb') as file:
                model = parse.read_ldraw(
                    file,
                    ldraw_directories = self.ldraw_directories,
                )
        except (FileNotFoundError, IOError, PermissionError) as error:
            subfile.valid = False
            subfile.problem = str(error)
        else:
            if isinstance(model.body[0], linetypes.MetaCommand):
                subfile.valid = True
                subfile.description = model.body[0].text
            else:
                subfile.valid = False
                subfile.problem = 'Description not found'
            if subfile.valid:
                subfile.vertices = set(parse.model_vertices(model, file_cache = self))
                subfile.flatness = {'x', 'y', 'z'}
                for vertex in subfile.vertices:
                    # Use list(subfile.flatness) for iteration because the
                    # actual set may be modified during the loop
                    for dimension in list(subfile.flatness):
                        if not math.isclose(
                            getattr(vertex, dimension),
                            0.0,
                            abs_tol = 1e-05,
                        ):
                            subfile.flatness.remove(dimension)
                    if not subfile.flatness:
                        break
                for subfile_reference in model.subfile_references:
                    # Go through all the subfile references so that we catch any
                    # cyclical references and to get studs
                    self.prepare_file(subfile_reference.subfile_path)
                subfile.has_studs = any(
                    self.cache[subfile_reference.subfile_path].has_studs
                    or is_logo_stud_name(subfile_reference.subfile_path)
                    for subfile_reference in model.subfile_references
                )
        self.reference_stack.pop()

if __name__ == '__main__':
    cache = SubfileCache(ldraw_directories = ["~/ldraw"])

mercurial