diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index 11beb5030d..0db82fa676 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -160,6 +160,13 @@ if bpy is not None: operator.PropagateTextData, operator.PushRepresentation, operator.ConvertLocalToGlobal, + operator.GetObjectLength, + operator.GetObjectWidth, + operator.GetObjectHeight, + operator.GetObjectFootprintArea, + operator.GetObjectSideArea, + operator.GetObjectArea, + operator.GetObjectVolume, prop.StrProperty, prop.Variable, prop.Role, diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index ed01cd3b30..69ff4d7b55 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -19,59 +19,6 @@ class ArrayModifier: 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, obj, edge): - return (obj.data.vertices[edge.vertices[1]].co - obj.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, ifc_export_settings): self.data_dir = ifc_export_settings.data_dir diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 2f558c28a8..b369ccd925 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -8,6 +8,7 @@ import ifcopenshell import tempfile from . import export_ifc from . import import_ifc +from . import qto from . import cut_ifc from . import svgwriter from . import sheeter @@ -63,7 +64,7 @@ class ExportIFC(bpy.types.Operator): output_file = bpy.path.ensure_ext(self.filepath, '.ifc') ifc_export_settings = export_ifc.IfcExportSettings.factory(context, output_file, logger) ifc_parser = export_ifc.IfcParser(ifc_export_settings) - qto_calculator = export_ifc.QtoCalculator() + qto_calculator = qto.QtoCalculator() ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser, qto_calculator) ifc_export_settings.logger.info('Starting export') ifc_exporter.export(context.selected_objects) @@ -3026,7 +3027,7 @@ class PushRepresentation(bpy.types.Operator): ifc_export_settings = export_ifc.IfcExportSettings.factory(context, output_file, logger) ifc_parser = export_ifc.IfcParser(ifc_export_settings) ifc_parser.parse([bpy.context.active_object]) - qto_calculator = export_ifc.QtoCalculator() + qto_calculator = qto.QtoCalculator() self.ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser, qto_calculator) self.ifc_exporter.file = ifcopenshell.file(schema=self.file.schema) self.ifc_exporter.create_origin() @@ -3145,3 +3146,87 @@ class ConvertLocalToGlobal(bpy.types.Operator): bpy.context.scene.cursor.location = (eastings, northings, height) return {'FINISHED'} + + +class GetObjectVolume(bpy.types.Operator): + bl_idname = 'bim.get_object_volume' + bl_label = 'Get Object Volume' + qto_index: bpy.props.IntProperty() + prop_index: bpy.props.IntProperty() + + def execute(self, context): + qto_calculator = qto.QtoCalculator() + bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_volume(bpy.context.active_object), 3)) + return {'FINISHED'} + + +class GetObjectFootprintArea(bpy.types.Operator): + bl_idname = 'bim.get_object_footprint_area' + bl_label = 'Get Object Footprint Area' + qto_index: bpy.props.IntProperty() + prop_index: bpy.props.IntProperty() + + def execute(self, context): + qto_calculator = qto.QtoCalculator() + bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_footprint_area(bpy.context.active_object), 3)) + return {'FINISHED'} + + +class GetObjectSideArea(bpy.types.Operator): + bl_idname = 'bim.get_object_side_area' + bl_label = 'Get Object Side Area' + qto_index: bpy.props.IntProperty() + prop_index: bpy.props.IntProperty() + + def execute(self, context): + qto_calculator = qto.QtoCalculator() + bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_side_area(bpy.context.active_object), 3)) + return {'FINISHED'} + + +class GetObjectArea(bpy.types.Operator): + bl_idname = 'bim.get_object_area' + bl_label = 'Get Object Area' + qto_index: bpy.props.IntProperty() + prop_index: bpy.props.IntProperty() + + def execute(self, context): + qto_calculator = qto.QtoCalculator() + bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_area(bpy.context.active_object), 3)) + return {'FINISHED'} + + +class GetObjectLength(bpy.types.Operator): + bl_idname = 'bim.get_object_length' + bl_label = 'Get Object Length' + qto_index: bpy.props.IntProperty() + prop_index: bpy.props.IntProperty() + + def execute(self, context): + qto_calculator = qto.QtoCalculator() + bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_length(bpy.context.active_object), 3)) + return {'FINISHED'} + + +class GetObjectWidth(bpy.types.Operator): + bl_idname = 'bim.get_object_width' + bl_label = 'Get Object Width' + qto_index: bpy.props.IntProperty() + prop_index: bpy.props.IntProperty() + + def execute(self, context): + qto_calculator = qto.QtoCalculator() + bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_width(bpy.context.active_object), 3)) + return {'FINISHED'} + + +class GetObjectHeight(bpy.types.Operator): + bl_idname = 'bim.get_object_height' + bl_label = 'Get Object Height' + qto_index: bpy.props.IntProperty() + prop_index: bpy.props.IntProperty() + + def execute(self, context): + qto_calculator = qto.QtoCalculator() + bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties[self.prop_index].string_value = str(round(qto_calculator.get_height(bpy.context.active_object), 3)) + return {'FINISHED'} diff --git a/src/ifcblenderexport/blenderbim/bim/qto.py b/src/ifcblenderexport/blenderbim/bim/qto.py new file mode 100644 index 0000000000..fa798ccb32 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/qto.py @@ -0,0 +1,88 @@ +from mathutils import 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=None): + if vg_index is None: + x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length + y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length + return x if x > y else y + 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_width(self, o): + x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length + y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length + return x if x < y else y + + def get_height(self, o): + return (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length + + def get_edge_distance(self, obj, edge): + return (obj.data.vertices[edge.vertices[1]].co - obj.data.vertices[edge.vertices[0]].co).length + + def get_footprint_area(self, o): + area = 0 + for polygon in o.data.polygons: + if round(polygon.center[2], 3) == 0.0 \ + and round(o.data.vertices[polygon.vertices[0]].co[2], 3) == 0.0: + area += polygon.area + return area + + def get_side_area(self, o): + # There are a few dumb options for this, but this seems the dumbest + # until I get more practical experience on what works best. + x = (Vector(o.bound_box[4]) - Vector(o.bound_box[0])).length + y = (Vector(o.bound_box[3]) - Vector(o.bound_box[0])).length + z = (Vector(o.bound_box[1]) - Vector(o.bound_box[0])).length + a1 = x * z + a2 = y * z + return a1 if a1 > a2 else a2 + + def get_area(self, o, vg_index=None): + if vg_index is None: + area = 0 + for polygon in o.data.polygons: + area += polygon.area + return area + 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=None): + 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 diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 47cc3b77ca..d88c418ed1 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -99,10 +99,45 @@ class BIM_PT_object(Panel): row = layout.row(align=True) row.prop(qto, 'name', text='') row.operator('bim.remove_qto', icon='X', text='').index = index - for prop in qto.properties: + for index2, prop in enumerate(qto.properties): row = layout.row(align=True) row.prop(prop, 'name', text='') row.prop(prop, 'string_value', text='') + if 'length' in prop.name.lower(): + op = row.operator('bim.get_object_length', icon='IPO_EASE_IN_OUT', text='') + op.qto_index = index + op.prop_index = index2 + elif 'width' in prop.name.lower() \ + and 'length' not in [p.name for p in qto.properties]: + op = row.operator('bim.get_object_length', icon='IPO_EASE_IN_OUT', text='') + op.qto_index = index + op.prop_index = index2 + elif 'width' in prop.name.lower(): + op = row.operator('bim.get_object_width', icon='IPO_EASE_IN_OUT', text='') + op.qto_index = index + op.prop_index = index2 + elif 'height' in prop.name.lower(): + op = row.operator('bim.get_object_height', icon='IPO_EASE_IN_OUT', text='') + op.qto_index = index + op.prop_index = index2 + elif 'area' in prop.name.lower() \ + and 'footprint' in prop.name.lower(): + op = row.operator('bim.get_object_footprint_area', icon='MESH_CIRCLE', text='') + op.qto_index = index + op.prop_index = index2 + elif 'area' in prop.name.lower() \ + and 'side' in prop.name.lower(): + op = row.operator('bim.get_object_side_area', icon='MESH_CIRCLE', text='') + op.qto_index = index + op.prop_index = index2 + elif 'area' in prop.name.lower(): + op = row.operator('bim.get_object_area', icon='MESH_CIRCLE', text='') + op.qto_index = index + op.prop_index = index2 + elif 'volume' in prop.name.lower(): + op = row.operator('bim.get_object_volume', icon='SPHERE', text='') + op.qto_index = index + op.prop_index = index2 row = layout.row() row.prop(props, 'material_type')