filecache.py

Thu, 26 Aug 2021 19:37:00 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Thu, 26 Aug 2021 19:37:00 +0300
changeset 148
8f621aa4cfd7
parent 145
fde18c4d6784
permissions
-rw-r--r--

Update license year

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, context):
        '''
            Initializes a new subfile cache
        '''
        self.cache = dict()
        self.context = context
        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.context.libraries,
        )
    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, context = self.context)
        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()

mercurial