-
Notifications
You must be signed in to change notification settings - Fork 8
/
dotbimifc.py
266 lines (216 loc) · 10.4 KB
/
dotbimifc.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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
import uuid
import numpy
import dotbimpy
import pyquaternion
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.geom
import ifcopenshell.util.element
import multiprocessing
class Ifc2Dotbim:
def __init__(self, ifc):
self.file = ifc
self.meshes = {}
self.mesh_colors = {}
self.elements = []
def execute(self):
self.settings = ifcopenshell.geom.settings()
self.filter_body_contexts()
iterator = ifcopenshell.geom.iterator(self.settings, self.file, multiprocessing.cpu_count())
if not iterator.initialize():
return
mesh_name_dictionary = {}
current_mesh_id = 0
while True:
shape = iterator.get()
element = self.file.by_guid(shape.guid)
if element.is_a("IfcAnnotation") or element.is_a("IfcOpeningElement"):
iterator.next()
continue
mesh_name = shape.geometry.id
if mesh_name not in mesh_name_dictionary:
mesh_name_dictionary[mesh_name] = current_mesh_id
current_mesh_id += 1
mesh = self.meshes.get(mesh_name)
if mesh is None:
mesh = self.create_mesh(mesh_name, shape, mesh_name_dictionary[mesh_name])
rgba = self.mesh_colors.get(mesh_name, [255, 255, 255, 255])
color = dotbimpy.Color(r=rgba[0], g=rgba[1], b=rgba[2], a=rgba[3])
m = shape.transformation.matrix.data
mat = numpy.array(
([m[0], m[3], m[6], m[9]], [m[1], m[4], m[7], m[10]], [m[2], m[5], m[8], m[11]], [0, 0, 0, 1])
)
qw, qx, qy, qz = pyquaternion.Quaternion(matrix=mat).elements
rotation = dotbimpy.Rotation(qx=float(qx), qy=float(qy), qz=float(qz), qw=float(qw))
vector = dotbimpy.Vector(x=m[9], y=m[10], z=m[11])
info = {
str(k): str(v) for k, v in element.get_info().items() if not isinstance(v, ifcopenshell.entity_instance)
}
for pset, properties in ifcopenshell.util.element.get_psets(element).items():
for prop, value in properties.items():
info[f"{pset}-{prop}"] = str(value)
element_type = ifcopenshell.util.element.get_type(element)
if element_type:
for pset, properties in ifcopenshell.util.element.get_psets(element_type).items():
for prop, value in properties.items():
info[f"{pset}-{prop}"] = str(value)
self.elements.append(
dotbimpy.Element(
mesh_id=mesh_name_dictionary[mesh_name],
vector=vector,
guid=str(uuid.UUID(ifcopenshell.guid.expand(element.GlobalId))),
info=info,
rotation=rotation,
type=element.is_a(),
color=color,
)
)
if not iterator.next():
break
file_info = {
"Author": " ".join(self.file.header.file_name.author),
"Date": self.file.header.file_name.time_stamp,
}
self.dotbim_file = dotbimpy.File(
"1.0.0", meshes=list(self.meshes.values()), elements=self.elements, info=file_info
)
def write(self, output):
self.dotbim_file.save(output)
def filter_body_contexts(self):
self.body_contexts = [
c.id()
for c in self.file.by_type("IfcGeometricRepresentationSubContext")
if c.ContextIdentifier in ["Body", "Facetation"]
]
# Ideally, all representations should be in a subcontext, but some BIM programs don't do this correctly
self.body_contexts.extend(
[
c.id()
for c in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
if c.ContextType == "Model"
]
)
if self.body_contexts:
self.settings.set_context_ids(self.body_contexts)
def create_mesh(self, name, shape, mesh_id):
faces = shape.geometry.faces
verts = shape.geometry.verts
materials = shape.geometry.materials
material_ids = shape.geometry.material_ids
material_popularity_contest = {}
material_rgbas = {}
if materials:
for material in materials:
if material.has_diffuse:
alpha = 1.0
if material.has_transparency and material.transparency > 0:
alpha = 1.0 - material.transparency
rgba = material.diffuse + (alpha,)
rgba = [int(v * 255) for v in rgba]
material_rgbas[material.name] = rgba
for material_id in material_ids:
material_name = materials[material_id].name
material_popularity_contest.setdefault(material_name, 0)
material_popularity_contest[material_name] += 1
if material_popularity_contest:
flattened_contest = [(k, v) for k, v in material_popularity_contest.items()]
most_popular_material = list(reversed(sorted(flattened_contest, key=lambda x: x[1])))[0][0]
self.mesh_colors[name] = material_rgbas[most_popular_material]
mesh = dotbimpy.Mesh(mesh_id=mesh_id, coordinates=list(verts), indices=list(faces))
self.meshes[name] = mesh
return mesh
class Dotbim2Ifc:
def __init__(self, dotbim):
self.dotbim = dotbim
self.file = None
self.meshes = {}
self.mesh_colors = {}
def execute(self):
ifcopenshell.api.run("project.create_file")
self.file = ifcopenshell.api.run("project.create_file", version="IFC4")
project = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject", name="Project")
ifcopenshell.api.run("unit.assign_unit", self.file, units=[ifcopenshell.api.run("unit.add_si_unit", self.file)])
model = ifcopenshell.api.run("context.add_context", self.file, context_type="Model")
self.body = ifcopenshell.api.run(
"context.add_context",
self.file,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
parent=model,
)
site = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcSite", name="Site")
ifcopenshell.api.run("aggregate.assign_object", self.file, product=site, relating_object=project)
for mesh in self.dotbim.meshes:
self.create_mesh(mesh)
for dotbim_element in self.dotbim.elements:
ifc_class = "IfcBuildingElementProxy"
name = dotbim_element.info.get("Name", dotbim_element.info.get("name", "Unnamed"))
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class=ifc_class, name=name)
ifcopenshell.api.run("spatial.assign_container", self.file, product=element, relating_structure=site)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="Dotbim_Info")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties=dotbim_element.info)
representation = self.meshes[dotbim_element.mesh_id]
# IFC stores colours per mesh. Dotbim stores colours per element.
rgba = (dotbim_element.color.r, dotbim_element.color.g, dotbim_element.color.b, dotbim_element.color.a)
rgba_key = ",".join([str(v) for v in rgba])
self.mesh_colors.setdefault(dotbim_element.mesh_id, {})
mesh_rgba = self.mesh_colors[dotbim_element.mesh_id]
if not mesh_rgba:
self.assign_rgba(representation, rgba)
self.mesh_colors[dotbim_element.mesh_id][rgba_key] = representation
elif rgba_key in mesh_rgba:
representation = mesh_rgba[rgba_key]
else:
representation = ifcopenshell.util.element.copy(self.file, representation)
representation.Items = [ifcopenshell.util.element.copy_deep(self.file, representation.Items[0])]
self.assign_rgba(representation, rgba)
self.mesh_colors[dotbim_element.mesh_id][rgba_key] = representation
element.Representation = self.file.createIfcProductDefinitionShape(Representations=[representation])
matrix = pyquaternion.Quaternion(
a=dotbim_element.rotation.qw,
b=dotbim_element.rotation.qx,
c=dotbim_element.rotation.qy,
d=dotbim_element.rotation.qz,
).transformation_matrix
matrix[0][3] = dotbim_element.vector.x
matrix[1][3] = dotbim_element.vector.y
matrix[2][3] = dotbim_element.vector.z
ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=element, matrix=matrix)
def write(self, output):
self.file.write(output)
def create_mesh(self, mesh):
verts = mesh.coordinates
faces = mesh.indices
grouped_verts = [[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)]
grouped_faces = [[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)]
coordinates = self.file.createIfcCartesianPointList3D(grouped_verts)
polygons = [self.file.createIfcIndexedPolygonalFace([v + 1 for v in gf]) for gf in grouped_faces]
items = [self.file.createIfcPolygonalFaceSet(coordinates, None, polygons)]
self.meshes[mesh.mesh_id] = self.file.createIfcShapeRepresentation(
self.body,
self.body.ContextIdentifier,
"Tessellation",
items,
)
def assign_rgba(self, representation, rgba):
style = ifcopenshell.api.run("style.add_style", self.file)
surface_style = ifcopenshell.api.run(
"style.add_surface_style",
self.file,
style=style,
ifc_class="IfcSurfaceStyleShading",
attributes=self.get_rgba_attributes(rgba),
)
ifcopenshell.api.run(
"style.assign_representation_styles", self.file, shape_representation=representation, styles=[style]
)
def get_rgba_attributes(self, rgba):
return {
"SurfaceColour": {
"Red": rgba[0] / 255,
"Green": rgba[1] / 255,
"Blue": rgba[2] / 255,
},
"Transparency": 1 - (rgba[3] / 255),
}