/
__init__.py
125 lines (108 loc) · 5.17 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
"""A folder tree of inheritable style definitions and resources
A 'style' is defined by a collection of YAML configuration files and other file
resources in a folder. This module provides methods for accessing this data.
Alternative styles are accessed by a stylename, each represented by a subfolder
that inherits data and resources from all parent folders. For example, a style
named 'thin' may be found in a folder named ${share_dir}/rustic/wood/thin; any
query for 'thin' data not found in this folder will be sought in
${share_dir}/rustic/wood; failing that it will be sought in
${share_dir}/rustic, and finally in ${share_dir} itself.
Note that styles are accessed by their short stylename _not_ the path, this
allows inheritance to be defined entirely by rearrangement of the configuration
data. This also means that there may only be one folder called 'thin' in the
folder tree, all others will be ignored.
"""
import os
import yaml
import copy
import json
import ifcopenshell
class Style:
"""Inheritable style definitions and resources"""
def __init__(self, args=None):
"""Read all the data in ${share_dir} and sub-folders, collect names of
non-YAML files. Default location is a folder called 'share' installed with this
module, or pass an absolute path in the 'share_dir' parameter to indicate a
different collection of styles."""
if args is None:
args = {}
self.share_dir = "share"
self.data = {"default": {"ancestors": [], "traces": {}, "hulls": {}}}
self.files = {}
self.libraries = {}
for arg in args:
self.__dict__[arg] = args[arg]
if not os.path.isabs(self.share_dir):
self.share_dir = os.path.abspath(
os.path.join(os.path.dirname(__file__), "..", "..", self.share_dir)
)
self.share_dir = os.path.normpath(self.share_dir)
# share_dir should now be an absolute path
# slurp all the yaml data under share_dir
for root, dirs, files in os.walk(self.share_dir):
for name in files:
prefix, ext = os.path.splitext(name)
relpath = os.path.relpath(root, self.share_dir)
ancestors = list(reversed(relpath.split(os.sep)))
if not relpath == ".":
ancestors.append("default")
stylename = ancestors.pop(0)
if stylename == ".":
stylename = "default"
if stylename not in self.data:
self.data[stylename] = {}
if stylename not in self.files:
self.files[stylename] = {}
if stylename not in self.libraries:
self.libraries[stylename] = {}
if ext == ".yml":
fh = open(os.path.join(root, name), "rb")
data = yaml.safe_load(fh.read())
fh.close()
self.data[stylename][prefix] = data
self.data[stylename]["ancestors"] = ancestors
elif ext == ".json":
with open(os.path.join(root, name)) as fh:
data = json.load(fh)
self.data[stylename][prefix] = data
self.data[stylename]["ancestors"] = ancestors
elif ext == ".ifc":
self.libraries[stylename][prefix] = {
"path": os.path.join(root, name),
"file": None,
}
else:
self.files[stylename][name] = os.path.join(root, name)
def get(self, stylename):
"""retrieves a flattened style definition with ancestors filling in the gaps"""
# FIXME this results in duplicated assets when an ancestor style is also in use
if stylename not in self.data:
return self.get("default")
mydata = copy.deepcopy(self.data[stylename])
if len(mydata["ancestors"]) == 0:
return mydata
ancestor = self.get(mydata["ancestors"][0])
for key in ancestor:
if not key == "ancestors":
if key in mydata:
ancestor[key].update(mydata[key])
return ancestor
def get_from_library(self, stylename, ifc_class, name):
"""retrieves from Project Libraries in stylename folder or ancestors as necessary"""
if stylename not in self.libraries:
return self.get_from_library("default", ifc_class, name)
# look in all IFC files in this folder
for prefix in self.libraries[stylename]:
library = self.libraries[stylename][prefix]
# load and cache ifc libraries if not loaded
if library["file"] is None:
library["file"] = ifcopenshell.open(library["path"])
for item in library["file"].by_type(ifc_class):
if item.Name == name:
return (stylename, library["file"], item)
if len(self.data[stylename]["ancestors"]) == 0:
return (None, None, None)
# try ancestor
return self.get_from_library(
self.data[stylename]["ancestors"][0], ifc_class, name
)