diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index a583344a54..60b979e42f 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -160,14 +160,7 @@ if bpy is not None: operator.PropagateTextData, operator.PushRepresentation, operator.ConvertLocalToGlobal, - operator.GetObjectLinearLength, - operator.GetObjectLength, - operator.GetObjectWidth, - operator.GetObjectHeight, - operator.GetObjectFootprintArea, - operator.GetObjectSideArea, - operator.GetObjectArea, - operator.GetObjectVolume, + operator.GuessQuantity, 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 c66a88cc9d..1ea5ffa999 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -20,8 +20,9 @@ class ArrayModifier: class IfcParser(): - def __init__(self, ifc_export_settings): + def __init__(self, ifc_export_settings, qto_calculator): self.data_dir = ifc_export_settings.data_dir + self.qto_calculator = qto_calculator self.ifc_export_settings = ifc_export_settings @@ -447,6 +448,9 @@ class IfcParser(): relationships = self.rel_defines_by_pset if is_qto: psets_qtos = obj.BIMObjectProperties.qtos + if not psets_qtos and self.ifc_export_settings.should_guess_quantities: + self.add_automatic_qtos(product['class'], obj) + psets_qtos = obj.BIMObjectProperties.qtos results = self.qtos relationships = self.rel_defines_by_qto for item in psets_qtos: @@ -461,6 +465,37 @@ class IfcParser(): } relationships.setdefault(item_key, []).append(product) + def add_automatic_qtos(self, ifc_class, obj): + qto_names = self.get_applicable_qtos(ifc_class) + for name in qto_names: + if name not in schema.ifc.qtos: + continue + has_automatic_value = False + props = schema.ifc.qtos[name]['HasPropertyTemplates'].keys() + guessed_values = {} + for prop_name in props: + value = self.qto_calculator.guess_quantity(prop_name, props, obj) + if value: + guessed_values[prop_name] = value + has_automatic_value = True + if has_automatic_value: + qto = obj.BIMObjectProperties.qtos.add() + qto.name = name + for prop_name in props: + prop = qto.properties.add() + prop.name = prop_name + if prop_name in guessed_values: + prop.string_value = str(guessed_values[prop_name]) + + def get_applicable_qtos(self, ifc_class): + results = [] + empty = ifcopenshell.file() + element = empty.create_entity(ifc_class) + for ifc_class, qto_names in schema.ifc.applicable_qtos.items(): + if element.is_a(ifc_class): + results.extend(qto_names) + return results + def get_product_relating_structure(self, product, obj): relating_structure = obj.BIMObjectProperties.relating_structure if relating_structure: @@ -1205,11 +1240,10 @@ class IfcParser(): class IfcExporter(): - def __init__(self, ifc_export_settings, ifc_parser, qto_calculator): + def __init__(self, ifc_export_settings, ifc_parser): self.template_file = '{}template.ifc'.format(ifc_export_settings.schema_dir) self.ifc_export_settings = ifc_export_settings self.ifc_parser = ifc_parser - self.qto_calculator = qto_calculator def export(self, selected_objects): self.schema = self.ifc_export_settings.schema @@ -2063,31 +2097,6 @@ class IfcExporter(): 'MappedRepresentation', [mapped_item]) - def calculate_quantities(self, qto_name, obj): - quantities = [] - for index, vg in enumerate(obj.vertex_groups): - if qto_name not in vg.name: - continue - if 'length' in vg.name.lower(): - quantity = float(self.qto_calculator.get_length(obj, index)) - quantities.append(self.file.createIfcQuantityLength( - vg.name.split('/')[1], None, - self.ifc_parser.units['length']['ifc'], quantity)) - elif 'area' in vg.name.lower(): - quantity = float(self.qto_calculator.get_area(obj, index)) - quantities.append(self.file.createIfcQuantityArea( - vg.name.split('/')[1], None, - self.ifc_parser.units['area']['ifc'], quantity)) - elif 'volume' in vg.name.lower(): - quantity = float(self.qto_calculator.get_volume(obj, index)) - quantities.append(self.file.createIfcQuantityVolume( - vg.name.split('/')[1], None, - self.ifc_parser.units['volume']['ifc'], quantity)) - if not quantity: - self.ifc_export_settings.logger.warning('The calculated quantity {} for {} is zero.'.format( - vg.name, obj.name)) - return quantities - def create_ifc_axis_2_placement_3d(self, point, up, forward): return self.file.createIfcAxis2Placement3D( self.create_cartesian_point(point.x, point.y, point.z), @@ -2854,6 +2863,7 @@ class IfcExportSettings: self.target_views = ['GRAPH_VIEW', 'SKETCH_VIEW', 'MODEL_VIEW', 'PLAN_VIEW', 'REFLECTED_PLAN_VIEW', 'SECTION_VIEW', 'ELEVATION_VIEW', 'USERDEFINED', 'NOTDEFINED'] self.should_use_presentation_style_assignment = False + self.should_guess_quantities = False self.context_tree = [] @staticmethod @@ -2867,6 +2877,7 @@ class IfcExportSettings: settings.has_representations = scene_bim.export_has_representations settings.schema = scene_bim.export_schema settings.should_use_presentation_style_assignment = scene_bim.export_should_use_presentation_style_assignment + settings.should_guess_quantities = scene_bim.export_should_guess_quantities settings.context_tree = [] for ifc_context in ['model', 'plan']: if getattr(scene_bim, 'has_{}_context'.format(ifc_context)): diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 8eaf9f1a81..1d4a9faeed 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -63,9 +63,9 @@ class ExportIFC(bpy.types.Operator): else: 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 = qto.QtoCalculator() - ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser, qto_calculator) + ifc_parser = export_ifc.IfcParser(ifc_export_settings, qto_calculator) + ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser) ifc_export_settings.logger.info('Starting export') ifc_exporter.export(context.selected_objects) ifc_export_settings.logger.info('Export finished in {:.2f} seconds'.format(time.time() - start)) @@ -3025,10 +3025,10 @@ class PushRepresentation(bpy.types.Operator): logger = logging.getLogger('ExportIFC') output_file = 'tmp.ifc' 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 = qto.QtoCalculator() - self.ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser, qto_calculator) + ifc_parser = export_ifc.IfcParser(ifc_export_settings, qto_calculator) + ifc_parser.parse([bpy.context.active_object]) + self.ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser) self.ifc_exporter.file = ifcopenshell.file(schema=self.file.schema) self.ifc_exporter.create_origin() self.ifc_exporter.create_rep_context() @@ -3148,97 +3148,16 @@ class ConvertLocalToGlobal(bpy.types.Operator): return {'FINISHED'} -class GetObjectVolume(bpy.types.Operator): - bl_idname = 'bim.get_object_volume' - bl_label = 'Get Object Volume' +class GuessQuantity(bpy.types.Operator): + bl_idname = 'bim.guess_quantity' + bl_label = 'Guess Quantity' 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 GetObjectLinearLength(bpy.types.Operator): - bl_idname = 'bim.get_object_linear_length' - bl_label = 'Get Object Linear 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_linear_length(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)) + props = bpy.context.active_object.BIMObjectProperties.qtos[self.qto_index].properties + prop = props[self.prop_index] + prop.string_value = str(round(qto_calculator.guess_quantity( + prop.name, [p.name for p in props], bpy.context.active_object), 3)) return {'FINISHED'} diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 33312453c0..c121824e24 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -991,6 +991,7 @@ class BIMProperties(PropertyGroup): ifc_userdefined_type: StringProperty(name="Userdefined Type") export_schema: EnumProperty(items=[('IFC4', 'IFC4', ''), ('IFC2X3', 'IFC2X3', '')], name='IFC Schema') export_has_representations: BoolProperty(name="Export Representations", default=True) + export_should_guess_quantities: BoolProperty(name="Export with Guessed Quantities", default=False) export_should_use_presentation_style_assignment: BoolProperty(name="Export with Presentation Style Assignment", default=False) import_should_ignore_site_coordinates: BoolProperty(name="Import Ignoring Site Coordinates", default=False) import_should_ignore_building_coordinates: BoolProperty(name="Import Ignoring Building Coordinates", default=False) diff --git a/src/ifcblenderexport/blenderbim/bim/qto.py b/src/ifcblenderexport/blenderbim/bim/qto.py index 371e0f1e5f..bac8f6d7b0 100644 --- a/src/ifcblenderexport/blenderbim/bim/qto.py +++ b/src/ifcblenderexport/blenderbim/bim/qto.py @@ -1,6 +1,33 @@ from mathutils import Vector class QtoCalculator(): + def guess_quantity(self, prop_name, alternative_prop_names, obj): + prop_name = prop_name.lower() + alternative_prop_names = [p.lower() for p in alternative_prop_names] + if 'length' in prop_name \ + and 'width' not in alternative_prop_names \ + and 'height' not in alternative_prop_names: + return self.get_linear_length(obj) + elif 'length' in prop_name: + return self.get_length(obj) + elif 'width' in prop_name \ + and 'length' not in alternative_prop_names: + return self.get_length(obj) + elif 'width' in prop_name: + return self.get_width(obj) + elif 'height' in prop_name: + return self.get_height(obj) + elif 'area' in prop_name \ + and ('footprint' in prop_name or 'section' in prop_name): + return self.get_footprint_area(obj) + elif 'area' in prop_name \ + and 'side' in prop_name: + return self.get_side_area(obj) + elif 'area' in prop_name: + return self.get_area(obj) + elif 'volume' in prop_name: + return self.get_volume(obj) + 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]]) diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index 9b098f920c..d7417197bf 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -104,44 +104,17 @@ class BIM_PT_object(Panel): row.prop(prop, 'name', text='') row.prop(prop, 'string_value', text='') if 'length' in prop.name.lower() \ - and 'width' not in [p.name.lower() for p in qto.properties] \ - and 'height' not in [p.name.lower() for p in qto.properties]: - op = row.operator('bim.get_object_linear_length', icon='IPO_EASE_IN_OUT', text='') - op.qto_index = index - op.prop_index = index2 - elif '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.lower() 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() or 'section' 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='') + or 'width' in prop.name.lower() \ + or 'height' in prop.name.lower(): + op = row.operator('bim.guess_quantity', icon='IPO_EASE_IN_OUT', 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 = row.operator('bim.guess_quantity', 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 = row.operator('bim.guess_quantity', icon='SPHERE', text='') op.qto_index = index op.prop_index = index2 @@ -1484,6 +1457,8 @@ class BIM_PT_mvd(Panel): row = layout.row() row.prop(bim_properties, 'export_has_representations') row = layout.row() + row.prop(bim_properties, 'export_should_guess_quantities') + row = layout.row() row.prop(bim_properties, 'import_should_import_type_representations') row = layout.row() row.prop(bim_properties, 'import_should_import_curves') diff --git a/src/ifcblenderexport/bootstrap.py b/src/ifcblenderexport/bootstrap.py deleted file mode 100644 index 2e1b1bcd1a..0000000000 --- a/src/ifcblenderexport/bootstrap.py +++ /dev/null @@ -1,19 +0,0 @@ -import sys -sys.path.append('C:/cygwin64/home/moud308/Projects/IfcOpenShell/src/ifcblenderexport/io_export_ifc/') -import bpy -import time -import export -import os - -print('# Starting export') -start = time.time() -ifc_export_settings = export.IfcExportSettings() -ifc_export_settings.bim_path = 'C:/cygwin64/home/moud308/Projects/IfcOpenShell/src/ifcblenderexport/io_export_ifc/' -ifc_export_settings.output_file = 'C:/cygwin64/home/moud308/Projects/New Folder/energy.ifc' -ifc_parser = export.IfcParser(ifc_export_settings) -ifc_schema = export.IfcSchema(ifc_export_settings) -qto_calculator = export.QtoCalculator() -ifc_exporter = export.IfcExporter(ifc_export_settings, ifc_schema, ifc_parser, qto_calculator) -ifc_exporter.export() -print('# Export finished in {:.2f} seconds'.format(time.time() - start)) -