|
30 | 30 | import ifcopenshell |
31 | 31 | import ifcopenshell.guid as guid |
32 | 32 | import ifcjson.common as common |
| 33 | +from datetime import datetime |
| 34 | +from ifcopenshell.entity_instance import entity_instance |
33 | 35 |
|
34 | 36 |
|
35 | 37 | class IFC2JSON4: |
36 | 38 | maxCache = 2048 |
37 | 39 |
|
38 | | - def __init__(self, ifcFilePath): |
39 | | - self.ifcFilePath = ifcFilePath |
40 | | - self.ifcModel = ifcopenshell.open(ifcFilePath) |
| 40 | + def __init__(self, ifcModel, compact=False): |
| 41 | + """IFC SPF file to IFC.JSON-4 writer |
41 | 42 |
|
42 | | - # Dictionary referencing all objects with a GlobalId that are already created |
43 | | - self.id_objects = {} |
| 43 | + parameters: |
| 44 | + ifcModel: IFC filePath or ifcopenshell model instance |
| 45 | + compact (boolean): if True then pretty print is turned off and references are created without informative "type" property |
44 | 46 |
|
45 | | - # Create dictionary of OwnerHistory objects |
| 47 | + """ |
| 48 | + if isinstance(ifcModel, ifcopenshell.file): |
| 49 | + self.ifcModel = ifcModel |
| 50 | + else: |
| 51 | + self.ifcModel = ifcopenshell.open(ifcModel) |
| 52 | + self.compact = compact |
| 53 | + self.rootObjects = {} |
| 54 | + self.objectDefinitions = {} |
46 | 55 | self.ownerHistories = {} |
47 | | - |
48 | | - # Create dictionary of IfcGeometricRepresentationContext objects |
49 | 56 | self.representationContexts = {} |
50 | 57 |
|
51 | 58 | def spf2Json(self): |
| 59 | + """ |
| 60 | + Create json dictionary structure for all attributes of the objects in the root list |
| 61 | + also including inverse attributes (except for IfcGeometricRepresentationContext and IfcOwnerHistory types) |
| 62 | + # (?) Check every IFC object to see if it is used multiple times |
| 63 | +
|
| 64 | + Returns: |
| 65 | + dict: IFC.JSON-4 model structure |
| 66 | +
|
| 67 | + """ |
| 68 | + |
52 | 69 | jsonObjects = [] |
53 | | - entityIter = iter(self.ifcModel) |
54 | | - for entity in entityIter: |
55 | | - self.entityToDict(entity) |
56 | | - for key in self.id_objects: |
57 | | - jsonObjects.append(self.id_objects[key]) |
| 70 | + |
| 71 | + for entity in self.ifcModel.by_type('IfcOwnerHistory'): |
| 72 | + self.ownerHistories[entity.id()] = str(uuid.uuid4()) |
| 73 | + |
| 74 | + for entity in self.ifcModel.by_type('IfcGeometricRepresentationContext'): |
| 75 | + self.representationContexts[entity.id()] = str(uuid.uuid4()) |
| 76 | + |
| 77 | + for entity in self.ifcModel.by_type('IfcObjectDefinition'): |
| 78 | + self.objectDefinitions[entity.id()] = guid.split( |
| 79 | + guid.expand(entity.GlobalId))[1:-1] |
| 80 | + |
| 81 | + self.rootobjects = dict(self.ownerHistories) |
| 82 | + self.rootobjects.update(self.representationContexts) |
| 83 | + self.rootobjects.update(self.objectDefinitions) |
| 84 | + |
| 85 | + for key in self.rootobjects: |
| 86 | + entity = self.ifcModel.by_id(key) |
| 87 | + entityAttributes = entity.__dict__ |
| 88 | + entityType = entityAttributes['type'] |
| 89 | + if not entityType in ['IfcGeometricRepresentationContext', 'IfcOwnerHistory']: |
| 90 | + for attr in entity.wrapped_data.get_inverse_attribute_names(): |
| 91 | + inverseAttribute = getattr(entity, attr) |
| 92 | + entityAttributes[attr] = self.getAttributeValue( |
| 93 | + inverseAttribute) |
| 94 | + |
| 95 | + entityAttributes["GlobalId"] = self.rootobjects[entity.id()] |
| 96 | + jsonObjects.append(self.createFullObject(entityAttributes)) |
| 97 | + |
58 | 98 | return { |
59 | | - 'file_schema': 'IFC.JSON4', |
| 99 | + 'fileSchema': 'IFC.JSON4', |
60 | 100 | 'originatingSystem': 'IFC2JSON_python', |
| 101 | + 'timeStamp': datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), |
61 | 102 | 'data': jsonObjects |
62 | 103 | } |
63 | 104 |
|
64 | 105 | @functools.lru_cache(maxsize=maxCache) |
65 | | - def entityToDict(self, entity): |
| 106 | + def getAttributeValue(self, value): |
| 107 | + """Recursive method that walks through all nested objects of an attribute |
| 108 | + and returns a IFC.JSON-4 model structure |
66 | 109 |
|
67 | | - # Entity names must be in camelCase |
68 | | - entityType = common.toLowerCamelcase(entity.is_a()) |
69 | | - |
70 | | - entityAttributes = entity.__dict__ |
71 | | - |
72 | | - ref = { |
73 | | - "type": entityType |
74 | | - } |
| 110 | + Parameters: |
| 111 | + value |
75 | 112 |
|
76 | | - # Add missing GlobalId to OwnerHistory |
77 | | - if entityType == 'ifcOwnerHistory': |
78 | | - if not entity.id() in self.ownerHistories: |
79 | | - self.ownerHistories[entity.id()] = guid.new() |
80 | | - entityAttributes["GlobalId"] = self.ownerHistories[entity.id()] |
81 | | - |
82 | | - # Add missing GlobalId to IfcGeometricRepresentationContext |
83 | | - if entityType == 'ifcGeometricRepresentationContext': |
84 | | - if not entity.id() in self.representationContexts: |
85 | | - self.representationContexts[entity.id()] = guid.new() |
86 | | - entityAttributes["GlobalId"] = self.representationContexts[entity.id()] |
87 | | - |
88 | | - # All objects with a GlobalId must be referenced, all others nested |
89 | | - if "GlobalId" in entityAttributes: |
90 | | - uuid = guid.split(guid.expand(entityAttributes["GlobalId"]))[1:-1] |
91 | | - ref["ref"] = uuid |
92 | | - |
93 | | - # Every object must be added to the root array only once |
94 | | - if not entityAttributes["GlobalId"] in self.id_objects: |
95 | | - d = { |
96 | | - "type": entityType |
97 | | - } |
98 | | - |
99 | | - # Add missing GlobalId to OwnerHistory |
100 | | - if entityType == 'ifcOwnerHistory': |
101 | | - d["globalId"] = guid.split(guid.expand( |
102 | | - self.ownerHistories[entity.id()]))[1:-1] |
103 | | - |
104 | | - # Add missing GlobalId to IfcGeometricRepresentationContext |
105 | | - if entityType == 'ifcGeometricRepresentationContext': |
106 | | - d["globalId"] = guid.split(guid.expand( |
107 | | - self.representationContexts[entity.id()]))[1:-1] |
108 | | - |
109 | | - # Inverse attributes must be added if not OwnerHistory or GeometricRepresentationContext |
110 | | - if not entityType in ['ifcGeometricRepresentationContext', 'ifcOwnerHistory']: |
111 | | - for attr in entity.wrapped_data.get_inverse_attribute_names(): |
112 | | - inverseAttribute = getattr(entity, attr) |
113 | | - |
114 | | - # print(attr) |
115 | | - # d[attrKey] = { |
116 | | - # "type": attr.Name, |
117 | | - # "ref": "guid" |
118 | | - # } |
119 | | - # print(inverseAttribute) |
120 | | - if isinstance(inverseAttribute, tuple): |
121 | | - for attribute in inverseAttribute: |
122 | | - attrKey = common.toLowerCamelcase(attribute.is_a()) |
123 | | - # print(dir(attribute)) |
124 | | - d[attr] = { |
125 | | - 'type': attrKey |
126 | | - } |
127 | | - if 'GlobalId' in dir(attribute): |
128 | | - d[attr]['ref'] = guid.split(guid.expand(attribute.GlobalId))[1:-1] |
129 | | - else: |
130 | | - print(entityType + ' has no GlobalId for referencing!') |
131 | | - |
132 | | - for attr in entityAttributes: |
133 | | - attrKey = common.toLowerCamelcase(attr) |
134 | | - if attr == "GlobalId": |
135 | | - d[attrKey] = uuid |
136 | | - else: |
137 | | - |
138 | | - # Line numbers are not part of IFC JSON |
139 | | - if attr == 'id': |
140 | | - continue |
141 | | - |
142 | | - jsonValue = self.getEntityValue(entityAttributes[attr]) |
143 | | - if jsonValue: |
144 | | - if ((entityType == 'ifcOwnerHistory') and (attr == "GlobalId")): |
145 | | - pass |
146 | | - else: |
147 | | - d[attrKey] = jsonValue |
148 | | - if entityAttributes[attr] == None: |
149 | | - continue |
150 | | - elif isinstance(entityAttributes[attr], ifcopenshell.entity_instance): |
151 | | - d[attrKey] = self.entityToDict( |
152 | | - entityAttributes[attr]) |
153 | | - elif isinstance(entityAttributes[attr], tuple): |
154 | | - subEnts = [] |
155 | | - for subEntity in entityAttributes[attr]: |
156 | | - if isinstance(subEntity, ifcopenshell.entity_instance): |
157 | | - subEntJson = self.entityToDict(subEntity) |
158 | | - if subEntJson: |
159 | | - subEnts.append(subEntJson) |
160 | | - else: |
161 | | - subEnts.append(subEntity) |
162 | | - if len(subEnts) > 0: |
163 | | - d[attrKey] = subEnts |
164 | | - else: |
165 | | - d[attrKey] = entityAttributes[attr] |
166 | | - self.id_objects[entityAttributes["GlobalId"]] = d |
167 | | - return ref |
168 | | - else: |
169 | | - d = { |
170 | | - "type": entityType |
171 | | - } |
172 | | - |
173 | | - for i in range(0, len(entity)): |
174 | | - attr = entity.attribute_name(i) |
175 | | - attrKey = common.toLowerCamelcase(attr) |
176 | | - if attr in entityAttributes: |
177 | | - if not attr == "OwnerHistory": |
178 | | - jsonValue = self.getEntityValue(entityAttributes[attr]) |
179 | | - if jsonValue: |
180 | | - d[attrKey] = jsonValue |
181 | | - if entityAttributes[attr] == None: |
182 | | - continue |
183 | | - elif isinstance(entityAttributes[attr], ifcopenshell.entity_instance): |
184 | | - d[attrKey] = self.entityToDict( |
185 | | - entityAttributes[attr]) |
186 | | - elif isinstance(entityAttributes[attr], tuple): |
187 | | - subEnts = [] |
188 | | - for subEntity in entityAttributes[attr]: |
189 | | - if isinstance(subEntity, ifcopenshell.entity_instance): |
190 | | - # subEnts.append(None) |
191 | | - subEntJson = self.entityToDict(subEntity) |
192 | | - if subEntJson: |
193 | | - subEnts.append(subEntJson) |
194 | | - else: |
195 | | - subEnts.append(subEntity) |
196 | | - if len(subEnts) > 0: |
197 | | - d[attrKey] = subEnts |
198 | | - else: |
199 | | - d[attrKey] = entityAttributes[attr] |
200 | | - return d |
| 113 | + Returns: |
| 114 | + attribute data converted to IFC.JSON-4 model structure |
201 | 115 |
|
202 | | - @functools.lru_cache(maxsize=maxCache) |
203 | | - def getEntityValue(self, value): |
204 | | - if value == None: |
| 116 | + """ |
| 117 | + if value == None or value == '': |
205 | 118 | jsonValue = None |
206 | 119 | elif isinstance(value, ifcopenshell.entity_instance): |
207 | | - jsonValue = self.entityToDict(value) |
| 120 | + entity = value |
| 121 | + entityAttributes = entity.__dict__ |
| 122 | + |
| 123 | + # All objects with a GlobalId must be referenced, all others nested |
| 124 | + if entity.id() in self.rootobjects: |
| 125 | + entityAttributes["GlobalId"] = self.rootobjects[entity.id()] |
| 126 | + return self.createReferenceObject(entityAttributes, self.compact) |
| 127 | + else: |
| 128 | + if 'GlobalId' in entityAttributes: |
| 129 | + entityAttributes["GlobalId"] = guid.split( |
| 130 | + guid.expand(entity.GlobalId))[1:-1] |
| 131 | + return self.createFullObject(entityAttributes) |
208 | 132 | elif isinstance(value, tuple): |
209 | 133 | jsonValue = None |
210 | 134 | subEnts = [] |
211 | 135 | for subEntity in value: |
212 | | - subEnts.append(self.getEntityValue(subEntity)) |
| 136 | + subEnts.append(self.getAttributeValue(subEntity)) |
213 | 137 | jsonValue = subEnts |
214 | 138 | else: |
215 | 139 | jsonValue = value |
216 | 140 | return jsonValue |
| 141 | + |
| 142 | + @functools.lru_cache(maxsize=maxCache) |
| 143 | + def createReferenceObject(self, entityAttributes, compact=False): |
| 144 | + """Returns object reference |
| 145 | +
|
| 146 | + Parameters: |
| 147 | + entityAttributes (dict): Dictionary of IFC object data |
| 148 | + compact (boolean): verbose or non verbose IFC.JSON-4 output |
| 149 | +
|
| 150 | + Returns: |
| 151 | + dict: object containing reference to another object |
| 152 | +
|
| 153 | + """ |
| 154 | + ref = {} |
| 155 | + if not compact: |
| 156 | + ref['type'] = entityAttributes['type'] |
| 157 | + ref['ref'] = entityAttributes['GlobalId'] |
| 158 | + return ref |
| 159 | + |
| 160 | + @functools.lru_cache(maxsize=maxCache) |
| 161 | + def createFullObject(self, entityAttributes): |
| 162 | + """Returns complete IFC.JSON-4 object |
| 163 | +
|
| 164 | + Parameters: |
| 165 | + entityAttributes (dict): Dictionary of IFC object data |
| 166 | +
|
| 167 | + Returns: |
| 168 | + dict: containing complete IFC.JSON-4 object |
| 169 | +
|
| 170 | + """ |
| 171 | + entityType = entityAttributes['type'] |
| 172 | + fullObject = {} |
| 173 | + |
| 174 | + for attr in entityAttributes: |
| 175 | + attrKey = common.toLowerCamelcase(attr) |
| 176 | + |
| 177 | + # Line numbers are not part of IFC JSON |
| 178 | + if attr == 'id': |
| 179 | + continue |
| 180 | + |
| 181 | + jsonValue = self.getAttributeValue(entityAttributes[attr]) |
| 182 | + if jsonValue: |
| 183 | + fullObject[attrKey] = jsonValue |
| 184 | + return fullObject |
0 commit comments