diff -r 0cc196c634f1 -r 0c686d10eb49 filecache.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/filecache.py Thu May 30 12:03:53 2019 +0300 @@ -0,0 +1,121 @@ +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"])