Skip to content

Commit a4d935f

Browse files
committed
Restructuring of IFC.JSON-4 model
1 parent 85727ee commit a4d935f

File tree

5 files changed

+170
-163
lines changed

5 files changed

+170
-163
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,11 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
131+
# vscode
132+
.vscode/
133+
settings.json
134+
135+
# project specific
136+
samples/
137+
ifcopenshell/

ifc2json.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,31 +32,43 @@
3232
t1_start = perf_counter()
3333

3434
if __name__ == '__main__':
35-
parser = argparse.ArgumentParser(description='Convert IFC SPF file to IFC.JSON')
35+
parser = argparse.ArgumentParser(
36+
description='Convert IFC SPF file to IFC.JSON')
3637
parser.add_argument('-i', type=str, help='input ifc file path')
3738
parser.add_argument('-o', type=str, help='output json file path')
38-
parser.add_argument('-v', type=str, help='IFC.JSON version, options: "4"(default), "5a"')
39+
parser.add_argument(
40+
'-v', type=str, help='IFC.JSON version, options: "4"(default), "5a"')
41+
parser.add_argument('-c', '--compact', action='store_true', help='Pretty print is turned off and references are created without informative "type" property')
42+
# parser.add_argument('NO_GEOMETRY', action='store_true')
3943
args = parser.parse_args()
4044
if args.i:
4145
ifcFilePath = args.i
4246
else:
4347
ifcFilePath = './samples/7m900_tue_hello_wall_with_door.ifc'
48+
if args.compact:
49+
indent = None
50+
compact = True
51+
else:
52+
indent = 2
53+
compact = False
54+
55+
4456
if os.path.isfile(ifcFilePath):
4557
if args.o:
4658
jsonFilePath = args.o
4759
else:
4860
jsonFilePath = os.path.splitext(ifcFilePath)[0] + '.json'
4961
if not args.v or args.v == "4":
50-
jsonData = ifcjson.IFC2JSON4(ifcFilePath).spf2Json()
62+
jsonData = ifcjson.IFC2JSON4(ifcFilePath,compact).spf2Json()
5163
with open(jsonFilePath, 'w') as outfile:
52-
json.dump(jsonData, outfile, indent=2)
64+
json.dump(jsonData, outfile, indent=indent)
5365
elif args.v == "5a":
5466
jsonData = ifcjson.IFC2JSON5a(ifcFilePath).spf2Json()
5567
with open(jsonFilePath, 'w') as outfile:
56-
json.dump(jsonData, outfile, indent=2)
68+
json.dump(jsonData, outfile, indent=indent)
5769
else:
5870
print('Version ' + args.v + ' is not supported')
5971
else:
6072
print(str(args.i) + ' is not a valid file')
6173
t1_stop = perf_counter()
62-
print("Conversion took ", t1_stop-t1_start, " seconds")
74+
print("Conversion took ", t1_stop-t1_start, " seconds")

ifcjson/common.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,16 @@
2626
def toLowerCamelcase(string):
2727
"""Convert string from upper to lower camelCase"""
2828

29-
return string[0].lower() + string[1:]
29+
return string[0].lower() + string[1:]
30+
31+
32+
def createReferenceObject(objectDict, compact=False):
33+
"""Returns value for referenced object"""
34+
ref = {}
35+
if not compact:
36+
ref['type'] = objectDict['type']
37+
ref['ref'] = objectDict['globalId']
38+
return ref
39+
40+
# More compact alternative
41+
# return objectDict['globalId']

ifcjson/ifc2json4.py

Lines changed: 121 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -30,187 +30,155 @@
3030
import ifcopenshell
3131
import ifcopenshell.guid as guid
3232
import ifcjson.common as common
33+
from datetime import datetime
34+
from ifcopenshell.entity_instance import entity_instance
3335

3436

3537
class IFC2JSON4:
3638
maxCache = 2048
3739

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
4142
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
4446
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 = {}
4655
self.ownerHistories = {}
47-
48-
# Create dictionary of IfcGeometricRepresentationContext objects
4956
self.representationContexts = {}
5057

5158
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+
5269
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+
5898
return {
59-
'file_schema': 'IFC.JSON4',
99+
'fileSchema': 'IFC.JSON4',
60100
'originatingSystem': 'IFC2JSON_python',
101+
'timeStamp': datetime.now().strftime("%Y-%m-%dT%H:%M:%S"),
61102
'data': jsonObjects
62103
}
63104

64105
@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
66109
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
75112
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
201115
202-
@functools.lru_cache(maxsize=maxCache)
203-
def getEntityValue(self, value):
204-
if value == None:
116+
"""
117+
if value == None or value == '':
205118
jsonValue = None
206119
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)
208132
elif isinstance(value, tuple):
209133
jsonValue = None
210134
subEnts = []
211135
for subEntity in value:
212-
subEnts.append(self.getEntityValue(subEntity))
136+
subEnts.append(self.getAttributeValue(subEntity))
213137
jsonValue = subEnts
214138
else:
215139
jsonValue = value
216140
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

Comments
 (0)