filecache.py

Thu, 30 May 2019 12:03:53 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Thu, 30 May 2019 12:03:53 +0300
changeset 54
0c686d10eb49
child 58
86014c443635
permissions
-rw-r--r--

added tests for moved-to files and scaling in flat dimensions

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

class CyclicalReferenceError(Exception):
    pass

class SubfileCache:
    class Subfile:
        def __init__(self):
            self.valid = None
            self.flatness = None
            self.description = None
            self.problem = None
            self.vertices = set()
    def __init__(self, ldraw_directories):
        '''
            Initializes a new subfile cache
        '''
        self.cache = dict()
        self.ldraw_directories = [
            Path(os.path.expanduser(directory))
            for directory in 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() 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))
                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
                    self.prepare_file(subfile_reference.subfile_path)
        self.reference_stack.pop()

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

mercurial