From af278b5724e9d2f88c4f09fa4576ce7ce196d129 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 15 Jan 2020 17:17:03 +1100 Subject: [PATCH] Add UI for pset overrides without the need to create a CSV. --- src/ifcblenderexport/blenderbim/__init__.py | 2 ++ src/ifcblenderexport/blenderbim/export_ifc.py | 32 +++++++++++++------ src/ifcblenderexport/blenderbim/import_ifc.py | 8 ++--- src/ifcblenderexport/blenderbim/operator.py | 31 ++++++++++++++++-- src/ifcblenderexport/blenderbim/prop.py | 23 ++++++------- src/ifcblenderexport/blenderbim/schema.py | 23 ++++++++----- .../blenderbim/schema/Pset_Custom.ifc | 14 ++++++++ .../{IFC4_ADD2.ifc => Pset_IFC4_ADD2.ifc} | 0 src/ifcblenderexport/blenderbim/ui.py | 13 ++++++++ 9 files changed, 109 insertions(+), 37 deletions(-) create mode 100644 src/ifcblenderexport/blenderbim/schema/Pset_Custom.ifc rename src/ifcblenderexport/blenderbim/schema/{IFC4_ADD2.ifc => Pset_IFC4_ADD2.ifc} (100%) diff --git a/src/ifcblenderexport/blenderbim/__init__.py b/src/ifcblenderexport/blenderbim/__init__.py index e04338ca5b..6a668e7c2f 100644 --- a/src/ifcblenderexport/blenderbim/__init__.py +++ b/src/ifcblenderexport/blenderbim/__init__.py @@ -61,6 +61,8 @@ if bpy is not None: operator.UnassignPset, operator.AddPset, operator.RemovePset, + operator.AddOverridePset, + operator.RemoveOverridePset, operator.AddMaterialPset, operator.RemoveMaterialPset, operator.AddDocument, diff --git a/src/ifcblenderexport/blenderbim/export_ifc.py b/src/ifcblenderexport/blenderbim/export_ifc.py index 9c41869c1e..1827fc5fbe 100644 --- a/src/ifcblenderexport/blenderbim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/export_ifc.py @@ -7,6 +7,7 @@ import os from pathlib import Path from mathutils import Vector, Matrix from .helper import SIUnitHelper +from . import schema import ifcopenshell import addon_utils @@ -408,6 +409,18 @@ class IfcParser(): self.rel_defines_by_pset.setdefault( '{}/{}'.format(pset.name, pset.file), []).append(product) + for pset in obj.BIMObjectProperties.override_psets: + pset_key = '{}/{}'.format(pset.name, obj.name) + raw = {p.name: p.string_value for p in pset.properties if p.string_value} + if not raw: + continue + self.psets[pset_key] = { + 'ifc': None, + 'raw': raw, + 'attributes': { 'Name': pset.name } + } + self.rel_defines_by_pset.setdefault(pset_key, []).append(product) + for document in obj.BIMObjectProperties.documents: self.rel_associates_document_object.setdefault( document.file, []).append(product) @@ -943,10 +956,9 @@ class IfcParser(): class IfcExporter(): - def __init__(self, ifc_export_settings, ifc_schema, ifc_parser, qto_calculator): + def __init__(self, ifc_export_settings, ifc_parser, qto_calculator): self.template_file = '{}template.ifc'.format(ifc_export_settings.schema_dir) self.ifc_export_settings = ifc_export_settings - self.ifc_schema = ifc_schema self.ifc_parser = ifc_parser self.qto_calculator = qto_calculator @@ -1206,7 +1218,7 @@ class IfcExporter(): }) def create_pset_properties(self, pset): - if pset['attributes']['Name'] in self.ifc_schema.psets: + if pset['attributes']['Name'] in schema.ifc.psets: return self.create_templated_pset_properties(pset) return self.create_custom_pset_properties(pset) @@ -1222,7 +1234,7 @@ class IfcExporter(): def create_templated_pset_properties(self, pset): properties = [] - templates = self.ifc_schema.psets[pset['attributes']['Name']]['HasPropertyTemplates'] + templates = schema.ifc.psets[pset['attributes']['Name']]['HasPropertyTemplates'] for name, data in templates.items(): if name not in pset['raw']: continue @@ -1250,13 +1262,13 @@ class IfcExporter(): return properties def cast_to_base_type(self, var_type, value): - if var_type not in self.ifc_schema.type_map: + if var_type not in schema.ifc.type_map: return value - elif self.ifc_schema.type_map[var_type] == 'float': + elif schema.ifc.type_map[var_type] == 'float': return float(value) - elif self.ifc_schema.type_map[var_type] == 'integer': + elif schema.ifc.type_map[var_type] == 'integer': return int(value) - elif self.ifc_schema.type_map[var_type] == 'bool': + elif schema.ifc.type_map[var_type] == 'bool': return True if value.lower() in ['1', 't', 'true', 'yes', 'y', 'uh-huh'] else False return str(value) @@ -1551,11 +1563,11 @@ class IfcExporter(): ) def get_product_attribute_type(self, product_class, attribute_name): - element_schema = self.ifc_schema.elements[product_class] + element_schema = schema.ifc.elements[product_class] for a in element_schema['attributes']: if a['name'] == attribute_name: return a['type'] - if element_schema['parent'] in self.ifc_schema.elements: + if element_schema['parent'] in schema.ifc.elements: return self.get_product_attribute_type(element_schema['parent'], attribute_name) return None diff --git a/src/ifcblenderexport/blenderbim/import_ifc.py b/src/ifcblenderexport/blenderbim/import_ifc.py index a3405f5cb8..731ef2fcc6 100644 --- a/src/ifcblenderexport/blenderbim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/import_ifc.py @@ -7,9 +7,7 @@ import time import mathutils import multiprocessing from .helper import SIUnitHelper -from .schema import IfcSchema - -ifc_schema = IfcSchema() +from . import schema class MaterialCreator(): def __init__(self, ifc_import_settings): @@ -402,8 +400,8 @@ class IfcImporter(): def add_element_attributes(self, element, obj): attributes = element.get_info() - if element.is_a() in ifc_schema.elements: - applicable_attributes = [a['name'] for a in ifc_schema.elements[element.is_a()]['attributes']] + if element.is_a() in schema.ifc.elements: + applicable_attributes = [a['name'] for a in schema.ifc.elements[element.is_a()]['attributes']] for key, value in attributes.items(): if key not in applicable_attributes \ or value is None: diff --git a/src/ifcblenderexport/blenderbim/operator.py b/src/ifcblenderexport/blenderbim/operator.py index 07f98e9117..78efa1ee78 100644 --- a/src/ifcblenderexport/blenderbim/operator.py +++ b/src/ifcblenderexport/blenderbim/operator.py @@ -8,10 +8,10 @@ from . import export_ifc from . import import_ifc from . import cut_ifc from . import sheeter +from . import schema from bpy_extras.io_utils import ImportHelper from itertools import cycle from mathutils import Vector -from .schema import IfcSchema class ExportIFC(bpy.types.Operator): bl_idname = "export.ifc" @@ -54,9 +54,8 @@ class ExportIFC(bpy.types.Operator): ] }) ifc_parser = export_ifc.IfcParser(ifc_export_settings) - ifc_schema = IfcSchema() qto_calculator = export_ifc.QtoCalculator() - ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_schema, ifc_parser, qto_calculator) + ifc_exporter = export_ifc.IfcExporter(ifc_export_settings, ifc_parser, qto_calculator) ifc_exporter.export() ifc_export_settings.logger.info('Export finished in {:.2f} seconds'.format(time.time() - start)) return {'FINISHED'} @@ -323,6 +322,32 @@ class RemovePset(bpy.types.Operator): return {'FINISHED'} +class AddOverridePset(bpy.types.Operator): + bl_idname = 'bim.add_override_pset' + bl_label = 'Add Override Pset' + + def execute(self, context): + pset_name = bpy.context.active_object.BIMObjectProperties.override_pset_name + if pset_name not in schema.ifc.psets: + return {'FINISHED'} + pset = bpy.context.active_object.BIMObjectProperties.override_psets.add() + pset.name = pset_name + for prop_name in schema.ifc.psets[pset_name]['HasPropertyTemplates'].keys(): + prop = pset.properties.add() + prop.name = prop_name + return {'FINISHED'} + + +class RemoveOverridePset(bpy.types.Operator): + bl_idname = 'bim.remove_override_pset' + bl_label = 'Remove Override Pset' + pset_index: bpy.props.IntProperty() + + def execute(self, context): + bpy.context.active_object.BIMObjectProperties.override_psets.remove(self.pset_index) + return {'FINISHED'} + + class AddMaterialPset(bpy.types.Operator): bl_idname = 'bim.add_material_pset' bl_label = 'Add Material Pset' diff --git a/src/ifcblenderexport/blenderbim/prop.py b/src/ifcblenderexport/blenderbim/prop.py index a7f9f73914..9eaa6a3921 100644 --- a/src/ifcblenderexport/blenderbim/prop.py +++ b/src/ifcblenderexport/blenderbim/prop.py @@ -3,7 +3,7 @@ import os import csv from pathlib import Path from . import export_ifc -from .schema import IfcSchema +from . import schema import bpy from bpy.types import PropertyGroup from bpy.app.handlers import persistent @@ -18,8 +18,6 @@ from bpy.props import ( cwd = os.path.dirname(os.path.realpath(__file__)) -ifc_schema = IfcSchema() - diagram_scales_enum = [] products_enum = [] profiledef_enum = [] @@ -53,7 +51,7 @@ def setDefaultProperties(scene): def getIfcPredefinedTypes(self, context): global types_enum if len(types_enum) < 1: - for name, data in ifc_schema.elements.items(): + for name, data in schema.ifc.elements.items(): if name != self.ifc_class.strip(): continue for attribute in data['attributes']: @@ -106,14 +104,14 @@ def getIfcProducts(self, context): def getIfcClasses(self, context): global classes_enum if len(classes_enum) < 1: - classes_enum.extend([(e, e, '') for e in getattr(ifc_schema, self.ifc_product)]) + classes_enum.extend([(e, e, '') for e in getattr(schema.ifc, self.ifc_product)]) return classes_enum def getProfileDef(self, context): global profiledef_enum if len(profiledef_enum) < 1: - profiledef_enum.extend([(e, e, '') for e in getattr(ifc_schema, 'IfcParameterizedProfileDef')]) + profiledef_enum.extend([(e, e, '') for e in getattr(schema.ifc, 'IfcParameterizedProfileDef')]) return profiledef_enum @@ -206,9 +204,9 @@ def getApplicableAttributes(self, context): global attributes_enum attributes_enum.clear() if '/' in context.active_object.name \ - and context.active_object.name.split('/')[0] in ifc_schema.elements: + and context.active_object.name.split('/')[0] in schema.ifc.elements: attributes_enum.extend([(a['name'], a['name'], '') for a in - ifc_schema.elements[context.active_object.name.split('/')[0]]['attributes'] + schema.ifc.elements[context.active_object.name.split('/')[0]]['attributes'] if self.attributes.find(a['name']) == -1]) return attributes_enum @@ -217,12 +215,12 @@ def getApplicableMaterialAttributes(self, context): global materialattributes_enum materialattributes_enum.clear() if '/' in context.active_object.name \ - and context.active_object.name.split('/')[0] in ifc_schema.elements: + and context.active_object.name.split('/')[0] in schema.ifc.elements: material_type = context.active_object.BIMObjectProperties.material_type if material_type[-3:] == 'Set': material_type = material_type[0:-3] materialattributes_enum.extend([(a['name'], a['name'], '') for a in - ifc_schema.IfcMaterialDefinition[material_type]['attributes'] + schema.ifc.IfcMaterialDefinition[material_type]['attributes'] if self.attributes.find(a['name']) == -1]) return materialattributes_enum @@ -230,7 +228,7 @@ def getApplicableMaterialAttributes(self, context): def refreshProfileAttributes(self, context): while len(context.active_object.active_material.BIMMaterialProperties.profile_attributes) > 0: context.active_object.active_material.BIMMaterialProperties.profile_attributes.remove(0) - for attribute in ifc_schema.IfcParameterizedProfileDef[self.profile_def]['attributes']: + for attribute in schema.ifc.IfcParameterizedProfileDef[self.profile_def]['attributes']: profile_attribute = context.active_object.active_material.BIMMaterialProperties.profile_attributes.add() profile_attribute.name = attribute['name'] @@ -384,6 +382,7 @@ class Attribute(PropertyGroup): class Pset(PropertyGroup): name: StringProperty(name="Name") file: StringProperty(name="File") + properties: CollectionProperty(name="Properties", type=Attribute) class Document(PropertyGroup): file: StringProperty(name="File") @@ -406,6 +405,8 @@ class BIMObjectProperties(PropertyGroup): applicable_documents: EnumProperty(items=getApplicableDocuments, name="Available Documents") classifications: CollectionProperty(name="Classifications", type=Classification) material_type: EnumProperty(items=getMaterialTypes, name="Material Type") + override_psets: CollectionProperty(name="Override Psets", type=Pset) + override_pset_name: StringProperty(name="Override Pset Name") class BIMMaterialProperties(PropertyGroup): diff --git a/src/ifcblenderexport/blenderbim/schema.py b/src/ifcblenderexport/blenderbim/schema.py index f564a8403e..a17a39f607 100644 --- a/src/ifcblenderexport/blenderbim/schema.py +++ b/src/ifcblenderexport/blenderbim/schema.py @@ -1,6 +1,7 @@ import os import json import ifcopenshell +from pathlib import Path cwd = os.path.dirname(os.path.realpath(__file__)) @@ -15,7 +16,10 @@ class IfcSchema(): 'IfcParameterizedProfileDef' ] self.elements = {} - self.property_file = ifcopenshell.open(os.path.join(self.schema_dir, 'IFC4_ADD2.ifc')) + self.property_files = [] + property_paths = Path(self.schema_dir).glob('Pset_*.ifc') + for path in property_paths: + self.property_files.append(ifcopenshell.open(path)) self.psets = {} self.qtos = {} self.load() @@ -29,10 +33,13 @@ class IfcSchema(): with open(os.path.join(self.schema_dir, 'ifc_types_IFC4.json')) as f: self.type_map = json.load(f) - for property in self.property_file.by_type('IfcPropertySetTemplate'): - if property.Name[0:4] == 'Qto_': - # self.qtos.append({ }) - pass - else: - self.psets[property.Name] = { - 'HasPropertyTemplates': {p.Name: p for p in property.HasPropertyTemplates}} + for property_file in self.property_files: + for property in property_file.by_type('IfcPropertySetTemplate'): + if property.Name[0:4] == 'Qto_': + # self.qtos.append({ }) + pass + else: + self.psets[property.Name] = { + 'HasPropertyTemplates': {p.Name: p for p in property.HasPropertyTemplates}} + +ifc = IfcSchema() diff --git a/src/ifcblenderexport/blenderbim/schema/Pset_Custom.ifc b/src/ifcblenderexport/blenderbim/schema/Pset_Custom.ifc new file mode 100644 index 0000000000..5b716b5de6 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/schema/Pset_Custom.ifc @@ -0,0 +1,14 @@ +ISO-10303-21; +HEADER; +FILE_DESCRIPTION((),'2;1'); +FILE_NAME('Pset_Custom.ifc','2020-01-01T00:00:00',(),(),'Sample','Sample',$); +FILE_SCHEMA(('IFC4')); +ENDSEC; + +DATA; +#1= IFCPROPERTYSETTEMPLATE('05N2Dhepb9ouTCSmWBh22D',$,'Custom_Pset','An example custom pset.',.PSET_TYPEDRIVENOVERRIDE.,'IfcObject',(#2,#3)); +#2= IFCSIMPLEPROPERTYTEMPLATE('37EpTDq4v96gTZh196BBXQ',$,'NumericProperty','An example numeric property',.P_SINGLEVALUE.,'IfcCountMeasure','',$,$,$,$,.READWRITE.); +#3= IFCSIMPLEPROPERTYTEMPLATE('22H7CzDvz1XfVHKkGWoYUK',$,'StringProperty','An example string property',.P_SINGLEVALUE.,'IfcLabel','',$,$,$,$,.READWRITE.); +ENDSEC; + +END-ISO-10303-21; diff --git a/src/ifcblenderexport/blenderbim/schema/IFC4_ADD2.ifc b/src/ifcblenderexport/blenderbim/schema/Pset_IFC4_ADD2.ifc similarity index 100% rename from src/ifcblenderexport/blenderbim/schema/IFC4_ADD2.ifc rename to src/ifcblenderexport/blenderbim/schema/Pset_IFC4_ADD2.ifc diff --git a/src/ifcblenderexport/blenderbim/ui.py b/src/ifcblenderexport/blenderbim/ui.py index 3e21455260..8f0fdc0a0f 100644 --- a/src/ifcblenderexport/blenderbim/ui.py +++ b/src/ifcblenderexport/blenderbim/ui.py @@ -54,6 +54,19 @@ class BIM_PT_object(Panel): row = layout.row() row.prop(props, 'psets') + row = layout.row(align=True) + row.prop(props, 'override_pset_name', text='') + row.operator('bim.add_override_pset') + + for index, pset in enumerate(props.override_psets): + row = layout.row(align=True) + row.prop(pset, 'name', text='') + row.operator('bim.remove_override_pset', icon='X', text='').pset_index = index + for prop in pset.properties: + row = layout.row(align=True) + row.prop(prop, 'name', text='') + row.prop(prop, 'string_value', text='') + layout.label(text="Documents:") row = layout.row(align=True) row.prop(props, 'applicable_documents', text='')