filecache.py

changeset 54
0c686d10eb49
child 58
86014c443635
--- /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"])

mercurial