mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 10:33:20 +00:00
Add basic support for some forms of quantity take-off
This commit is contained in:
@@ -10,6 +10,58 @@ class ArrayModifier:
|
||||
count: int
|
||||
offset: Vector
|
||||
|
||||
class QtoCalculator():
|
||||
def get_units(self, o, vg_index):
|
||||
return len([ v for v in o.data.vertices if vg_index in [ g.group for g in v.groups ] ])
|
||||
|
||||
def get_length(self, o, vg_index):
|
||||
length = 0
|
||||
edges = [ e for e in o.data.edges if (
|
||||
vg_index in [ g.group for g in o.data.vertices[e.vertices[0]].groups ] and
|
||||
vg_index in [ g.group for g in o.data.vertices[e.vertices[1]].groups ]
|
||||
) ]
|
||||
for e in edges:
|
||||
length += self.get_edge_distance(o, e)
|
||||
return length
|
||||
|
||||
def get_edge_distance(self, object, edge):
|
||||
return (object.data.vertices[edge.vertices[1]].co - object.data.vertices[edge.vertices[0]].co).length
|
||||
|
||||
def get_area(self, o, vg_index):
|
||||
area = 0
|
||||
vertices_in_vg = [ v.index for v in o.data.vertices if vg_index in [ g.group for g in v.groups ] ]
|
||||
for polygon in o.data.polygons:
|
||||
if self.is_polygon_in_vg(polygon, vertices_in_vg):
|
||||
area += polygon.area
|
||||
return area
|
||||
|
||||
def is_polygon_in_vg(self, polygon, vertices_in_vg):
|
||||
for v in polygon.vertices:
|
||||
if v not in vertices_in_vg:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_volume(self, o, vg_index):
|
||||
volume = 0
|
||||
ob_mat = o.matrix_world
|
||||
me = o.data
|
||||
me.calc_loop_triangles()
|
||||
for tf in me.loop_triangles:
|
||||
tfv = tf.vertices
|
||||
if len(tf.vertices) == 3:
|
||||
tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),
|
||||
else:
|
||||
tf_tris = (me.vertices[tfv[0]], me.vertices[tfv[1]], me.vertices[tfv[2]]),\
|
||||
(me.vertices[tfv[2]], me.vertices[tfv[3]], me.vertices[tfv[0]])
|
||||
|
||||
for tf_iter in tf_tris:
|
||||
v1 = ob_mat @ tf_iter[0].co
|
||||
v2 = ob_mat @ tf_iter[1].co
|
||||
v3 = ob_mat @ tf_iter[2].co
|
||||
|
||||
volume += v1.dot(v2.cross(v3)) / 6.0
|
||||
return volume
|
||||
|
||||
class IfcParser():
|
||||
def __init__(self):
|
||||
self.data_dir = '/home/dion/Projects/blender-bim-ifc/data/'
|
||||
@@ -24,11 +76,13 @@ class IfcParser():
|
||||
self.selected_types = []
|
||||
|
||||
self.psets = []
|
||||
self.qtos = {}
|
||||
self.aggregates = {}
|
||||
self.spatial_structure_elements = []
|
||||
self.spatial_structure_elements_tree = []
|
||||
self.rel_contained_in_spatial_structure = {}
|
||||
self.rel_defines_by_type = {}
|
||||
self.rel_defines_by_qto = {}
|
||||
self.rel_aggregates = {}
|
||||
self.representations = []
|
||||
self.type_products = []
|
||||
@@ -40,6 +94,7 @@ class IfcParser():
|
||||
self.psets = self.get_psets()
|
||||
self.spatial_structure_elements = self.get_spatial_structure_elements()
|
||||
self.representations = self.get_representations()
|
||||
self.qtos = self.get_qtos()
|
||||
self.type_products = self.get_type_products()
|
||||
|
||||
self.collection_name_filter = []
|
||||
@@ -81,6 +136,7 @@ class IfcParser():
|
||||
'forward_axis': object.matrix_world.to_quaternion() @ Vector((1, 0, 0)),
|
||||
'class': self.get_ifc_class(object.name),
|
||||
'relating_structure': None,
|
||||
'relating_qtos_key': None,
|
||||
'representation': self.get_representation_reference_from_object(object),
|
||||
'attributes': self.get_object_attributes(object)
|
||||
}
|
||||
@@ -93,6 +149,9 @@ class IfcParser():
|
||||
and self.is_a_rel_aggregates(self.get_ifc_class(object.instance_collection.name)):
|
||||
self.rel_aggregates[index] = object.instance_collection.name
|
||||
|
||||
if object.name in self.qtos:
|
||||
self.rel_defines_by_qto.setdefault(object.name, []).append(product)
|
||||
|
||||
if object.parent \
|
||||
and self.is_a_type(self.get_ifc_class(object.parent.name)):
|
||||
reference = self.get_type_product_reference(object.parent.name)
|
||||
@@ -196,6 +255,25 @@ class IfcParser():
|
||||
})
|
||||
return results
|
||||
|
||||
def get_qtos(self):
|
||||
results = {}
|
||||
for object in self.selected_products + self.selected_types:
|
||||
if not object.data:
|
||||
continue
|
||||
for property in object.keys():
|
||||
if property[0:4] != 'Qto_':
|
||||
continue
|
||||
results[object.name] = {
|
||||
'ifc': None,
|
||||
'raw': object,
|
||||
'class': property,
|
||||
'attributes': {
|
||||
'Name': property,
|
||||
'MethodOfMeasurement': object[property]
|
||||
}
|
||||
}
|
||||
return results
|
||||
|
||||
def get_type_products(self):
|
||||
if not self.selected_types:
|
||||
return []
|
||||
@@ -274,6 +352,7 @@ class IfcExporter():
|
||||
self.template_file = '/home/dion/Projects/blender-bim-ifc/template.ifc'
|
||||
self.output_file = '/home/dion/Projects/blender-bim-ifc/output.ifc'
|
||||
self.ifc_parser = ifc_parser
|
||||
self.qto_calculator = qto_calculator
|
||||
|
||||
def export(self):
|
||||
self.file = ifcopenshell.open(self.template_file)
|
||||
@@ -285,16 +364,23 @@ class IfcExporter():
|
||||
self.create_representations()
|
||||
self.create_type_products()
|
||||
self.create_spatial_structure_elements(self.ifc_parser.spatial_structure_elements_tree)
|
||||
self.create_qtos()
|
||||
self.create_products()
|
||||
self.relate_objects_to_objects()
|
||||
self.relate_elements_to_spatial_structures()
|
||||
self.relate_objects_to_types()
|
||||
self.relate_objects_to_qtos()
|
||||
self.file.write(self.output_file)
|
||||
|
||||
def set_common_definitions(self):
|
||||
self.origin = self.file.by_type("IfcAxis2Placement3D")[0]
|
||||
self.origin = self.file.by_type('IfcAxis2Placement3D')[0]
|
||||
# Owner history doesn't actually work like this, but for now, it does :)
|
||||
self.owner_history = self.file.by_type("ifcownerhistory")[0]
|
||||
self.owner_history = self.file.by_type('IfcOwnerHistory')[0]
|
||||
# TODO: unhardcode units
|
||||
units = self.file.by_type('IfcSIUnit')
|
||||
self.length_unit = units[0]
|
||||
self.area_unit = units[1]
|
||||
self.volume_unit = units[2]
|
||||
|
||||
def create_psets(self):
|
||||
for pset in self.ifc_parser.psets:
|
||||
@@ -403,6 +489,16 @@ class IfcExporter():
|
||||
for product in self.ifc_parser.products:
|
||||
self.create_product(product)
|
||||
|
||||
def create_qtos(self):
|
||||
for object_name, qto in self.ifc_parser.qtos.items():
|
||||
quantities = self.calculate_quantities(qto['class'], qto['raw'])
|
||||
qto['attributes'].update({
|
||||
'GlobalId': ifcopenshell.guid.new(),
|
||||
'OwnerHistory': self.owner_history,
|
||||
'Quantities': quantities
|
||||
})
|
||||
qto['ifc'] = self.file.create_entity('IfcElementQuantity', **qto['attributes'])
|
||||
|
||||
def create_product(self, product):
|
||||
if product['relating_structure']:
|
||||
placement_rel_to = self.ifc_parser.spatial_structure_elements[product['relating_structure']]['ifc'].ObjectPlacement
|
||||
@@ -432,6 +528,31 @@ class IfcExporter():
|
||||
except RuntimeError as e:
|
||||
print('The product "{}/{}" could not be created: {}'.format(product['class'], product['attributes']['Name'], e.args))
|
||||
|
||||
def calculate_quantities(self, qto_name, object):
|
||||
quantities = []
|
||||
for index, vg in enumerate(object.vertex_groups):
|
||||
if qto_name not in vg.name:
|
||||
continue
|
||||
if 'length' in vg.name.lower():
|
||||
quantity = float(self.qto_calculator.get_length(object, index))
|
||||
quantities.append(self.file.createIfcQuantityLength(
|
||||
vg.name.split('/')[1], None,
|
||||
self.length_unit, quantity))
|
||||
elif 'area' in vg.name.lower():
|
||||
quantity = float(self.qto_calculator.get_area(object, index))
|
||||
quantities.append(self.file.createIfcQuantityArea(
|
||||
vg.name.split('/')[1], None,
|
||||
self.area_unit, quantity))
|
||||
elif 'volume' in vg.name.lower():
|
||||
quantity = float(self.qto_calculator.get_volume(object, index))
|
||||
quantities.append(self.file.createIfcQuantityVolume(
|
||||
vg.name.split('/')[1], None,
|
||||
self.volume_unit, quantity))
|
||||
if not quantity:
|
||||
print('Warning: the calculated quantity {} for {} is zero.'.format(
|
||||
vg.name, object.name))
|
||||
return quantities
|
||||
|
||||
def create_ifc_axis_2_placement_3d(self, point, up, forward):
|
||||
return self.file.createIfcAxis2Placement3D(
|
||||
self.file.createIfcCartesianPoint((point.x, point.y, point.z)),
|
||||
@@ -469,9 +590,19 @@ class IfcExporter():
|
||||
[ self.ifc_parser.products[o]['ifc'] for o in related_objects],
|
||||
self.ifc_parser.type_products[relating_type]['ifc'])
|
||||
|
||||
def relate_objects_to_qtos(self):
|
||||
for relating_property_key, related_objects in self.ifc_parser.rel_defines_by_qto.items():
|
||||
relating_property = self.ifc_parser.qtos[relating_property_key]['ifc']
|
||||
self.file.createIfcRelDefinesByProperties(
|
||||
ifcopenshell.guid.new(), self.owner_history, None, None,
|
||||
[o['ifc'] for o in related_objects],
|
||||
relating_property)
|
||||
|
||||
|
||||
print('# Starting export')
|
||||
start = time.time()
|
||||
ifc_parser = IfcParser()
|
||||
ifc_exporter = IfcExporter(ifc_parser)
|
||||
qto_calculator = QtoCalculator()
|
||||
ifc_exporter = IfcExporter(ifc_parser, qto_calculator)
|
||||
ifc_exporter.export()
|
||||
print('# Export finished in {:.2f} seconds'.format(time.time() - start))
|
||||
|
||||
Reference in New Issue
Block a user