Thu, 27 Aug 2020 00:29:31 +0300
..., part two
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"])