Thu, 26 Aug 2021 19:16:25 +0300
Cleanup ldcheck.py
Remove dependency on configobj. Configuration file replaced with command line arguments and rcfile
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"])