filecache.py

changeset 54
0c686d10eb49
child 58
86014c443635
equal deleted inserted replaced
53:0cc196c634f1 54:0c686d10eb49
1 import datetime
2 import parse
3 import os
4 from pathlib import Path
5 import linetypes
6 import math
7
8 def find_ldraw_file(filename, libraries):
9 '''
10 Searches the LDraw libraries for the specified file.
11 `libraries` must be an iterable of LDraw library root paths.
12 '''
13 if os.path.sep != "\\":
14 filename = filename.replace("\\", os.path.sep)
15 for library in libraries:
16 for path in [
17 Path(library) / filename,
18 Path(library) / "parts" / filename,
19 Path(library) / "p" / filename,
20 ]:
21 if path.exists():
22 return path
23 raise FileNotFoundError(str.format(
24 '"{}" not found in the LDraw libraries',
25 filename,
26 ))
27
28 class CyclicalReferenceError(Exception):
29 pass
30
31 class SubfileCache:
32 class Subfile:
33 def __init__(self):
34 self.valid = None
35 self.flatness = None
36 self.description = None
37 self.problem = None
38 self.vertices = set()
39 def __init__(self, ldraw_directories):
40 '''
41 Initializes a new subfile cache
42 '''
43 self.cache = dict()
44 self.ldraw_directories = [
45 Path(os.path.expanduser(directory))
46 for directory in ldraw_directories
47 ]
48 self.reference_stack = []
49 def flatness_of(self, filename):
50 '''
51 Returns the set of all directiones the specified file is flat in.
52 '''
53 self.prepare_file(filename)
54 return self.cache[filename].flatness
55 def description_of(self, filename):
56 '''
57 Returns the description of the specified file
58 '''
59 self.prepare_file(filename)
60 return self.cache[filename].description
61 def find_file(self, filename):
62 return find_ldraw_file(
63 filename = filename,
64 libraries = self.ldraw_directories,
65 )
66 def prepare_file(self, filename):
67 '''
68 Loads the file if not loaded yet.
69 '''
70 if filename not in self.cache:
71 self._load_subfile(filename)
72 return self.cache[filename]
73 def _load_subfile(self, filename):
74 # ward against cyclical dependencies
75 if filename in self.reference_stack:
76 raise CyclicalReferenceError(
77 ' -> '.join(self.reference_stack + [filename]),
78 )
79 self.reference_stack.append(filename)
80 subfile = SubfileCache.Subfile()
81 self.cache[filename] = subfile
82 try:
83 path = self.find_file(filename)
84 with path.open() as file:
85 model = parse.read_ldraw(
86 file,
87 ldraw_directories = self.ldraw_directories,
88 )
89 except (FileNotFoundError, IOError, PermissionError) as error:
90 subfile.valid = False
91 subfile.problem = str(error)
92 else:
93 if isinstance(model.body[0], linetypes.MetaCommand):
94 subfile.valid = True
95 subfile.description = model.body[0].text
96 else:
97 subfile.valid = False
98 subfile.problem = 'Description not found'
99 if subfile.valid:
100 subfile.vertices = set(parse.model_vertices(model))
101 subfile.flatness = {'x', 'y', 'z'}
102 for vertex in subfile.vertices:
103 # Use list(subfile.flatness) for iteration because the
104 # actual set may be modified during the loop
105 for dimension in list(subfile.flatness):
106 if not math.isclose(
107 getattr(vertex, dimension),
108 0.0,
109 abs_tol = 1e-05,
110 ):
111 subfile.flatness.remove(dimension)
112 if not subfile.flatness:
113 break
114 for subfile_reference in model.subfile_references:
115 # Go through all the subfile references so that we catch any
116 # cyclical references
117 self.prepare_file(subfile_reference.subfile_path)
118 self.reference_stack.pop()
119
120 if __name__ == '__main__':
121 cache = SubfileCache(ldraw_directories = ["~/ldraw"])

mercurial