From d206dff9c0d4869e8202a94e05a076f58305e39e Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 6 Jan 2021 15:19:55 +1100 Subject: [PATCH] WIP class dropdowns are now schema version specific and generated. See #1222. --- .../bim/module/aggregate/operator.py | 31 +++++- .../bim/module/geometry/add_representation.py | 19 ++++ .../blenderbim/bim/module/geometry/data.py | 2 +- .../blenderbim/bim/module/project/operator.py | 43 ++++---- .../blenderbim/bim/module/root/operator.py | 97 +++++++++++-------- src/ifcblenderexport/blenderbim/bim/prop.py | 35 ++++--- 6 files changed, 143 insertions(+), 84 deletions(-) diff --git a/src/ifcblenderexport/blenderbim/bim/module/aggregate/operator.py b/src/ifcblenderexport/blenderbim/bim/module/aggregate/operator.py index ded3decc1d..7f5a57aac0 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/aggregate/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/aggregate/operator.py @@ -1,4 +1,5 @@ import bpy +import ifcopenshell.util.schema import blenderbim.bim.module.aggregate.assign_object as assign_object from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.aggregate.data import Data @@ -12,22 +13,44 @@ class AssignObject(bpy.types.Operator): def execute(self, context): self.file = IfcStore.get_file() - obj = bpy.data.objects.get(self.related_object) if self.related_object else bpy.context.active_object - props = obj.BIMObjectProperties + related_object = bpy.data.objects.get(self.related_object) if self.related_object else bpy.context.active_object + props = related_object.BIMObjectProperties relating_object = bpy.data.objects.get(self.relating_object) if self.relating_object else props.relating_object if not relating_object or not relating_object.BIMObjectProperties.ifc_definition_id: return {"FINISHED"} + product = self.file.by_id(props.ifc_definition_id) assign_object.Usecase( self.file, { - "product": self.file.by_id(props.ifc_definition_id), + "product": product, "relating_object": self.file.by_id(relating_object.BIMObjectProperties.ifc_definition_id), }, ).execute() Data.load(props.ifc_definition_id) - bpy.ops.bim.disable_editing_aggregate(obj=self.related_object) + bpy.ops.bim.disable_editing_aggregate(obj=related_object.name) + + file = IfcStore.get_file() + ifc_schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema) + declaration = ifc_schema.declaration_by_name(product.is_a()) + + # TODO: we may not need this conditional if aggregates stop using collection instances + if ifcopenshell.util.schema.is_a(declaration, "IfcSpatialElement"): + related_collection = related_object.users_collection[0] + relating_collection = relating_object.users_collection[0] + self.remove_collection(bpy.context.scene.collection, related_collection) + for collection in bpy.data.collections: + if collection == relating_collection: + collection.children.link(related_collection) + continue + self.remove_collection(collection, related_collection) return {"FINISHED"} + def remove_collection(self, parent, child): + try: + parent.children.unlink(child) + except: + pass + class EnableEditingAggregate(bpy.types.Operator): bl_idname = "bim.enable_editing_aggregate" diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/add_representation.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/add_representation.py index eb39fc524d..b425a18f7b 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/geometry/add_representation.py +++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/add_representation.py @@ -119,6 +119,25 @@ class Usecase: items, ) + def create_polygonal_face_set(self): + ifc_raw_items = [None] * self.settings["total_items"] + for i, value in enumerate(ifc_raw_items): + ifc_raw_items[i] = [] + for polygon in self.settings["geometry"].polygons: + ifc_raw_items[polygon.material_index % self.settings["total_items"]].append( + self.file.createIfcIndexedPolygonalFace([v + 1 for v in polygon.vertices]) + ) + coordinates = self.file.createIfcCartesianPointList3D( + [self.convert_si_to_unit(v.co) for v in self.settings["geometry"].vertices] + ) + items = [self.file.createIfcPolygonalFaceSet(coordinates, None, i) for i in ifc_raw_items if i] + return self.file.createIfcShapeRepresentation( + self.settings["context"], + self.settings["context"].ContextIdentifier, + "Tessellation", + items, + ) + def create_vertices(self, is_2d=False): if is_2d: for v in self.settings["geometry"].vertices: diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/data.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/data.py index 4283d58ac5..bce0641540 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/geometry/data.py +++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/data.py @@ -12,7 +12,7 @@ class Data: return cls.products[product_id] = [] product = file.by_id(product_id) - if not product.Representation: + if not hasattr(product, "Representation") or not product.Representation: return for representation in product.Representation.Representations: c = representation.ContextOfItems diff --git a/src/ifcblenderexport/blenderbim/bim/module/project/operator.py b/src/ifcblenderexport/blenderbim/bim/module/project/operator.py index 4abb668f7d..a16b75cc38 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/project/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/project/operator.py @@ -22,39 +22,30 @@ class CreateProject(bpy.types.Operator): IfcStore.file = create_file.Usecase({"version": bpy.context.scene.BIMProperties.export_schema}).execute() self.file = IfcStore.get_file() - project = bpy.data.collections.new("My Project") - site = bpy.data.collections.new("My Site") - building = bpy.data.collections.new("My Building") - building_storey = bpy.data.collections.new("Ground Floor") + project = bpy.data.objects.new("My Project", None) + site = bpy.data.objects.new("My Site", None) + building = bpy.data.objects.new("My Building", None) + building_storey = bpy.data.objects.new("Ground Floor", None) - project_obj = bpy.data.objects.new("My Project", None) - site_obj = bpy.data.objects.new("My Site", None) - building_obj = bpy.data.objects.new("My Building", None) - building_storey_obj = bpy.data.objects.new("Ground Floor", None) - - bpy.context.scene.collection.children.link(project) - project.children.link(site) - site.children.link(building) - building.children.link(building_storey) - - project.objects.link(project_obj) - site.objects.link(site_obj) - building.objects.link(building_obj) - building_storey.objects.link(building_storey_obj) - - bpy.ops.bim.assign_class(obj=project_obj.name, ifc_class="IfcProject") + bpy.ops.bim.assign_class(obj=project.name, ifc_class="IfcProject") bpy.ops.bim.assign_unit() bpy.ops.bim.add_subcontext(context="Model") bpy.ops.bim.add_subcontext(context="Model", subcontext="Body", target_view="MODEL_VIEW") bpy.ops.bim.add_subcontext(context="Model", subcontext="Box", target_view="MODEL_VIEW") bpy.ops.bim.add_subcontext(context="Plan") bpy.ops.bim.add_subcontext(context="Plan", subcontext="Annotation", target_view="PLAN_VIEW") - bpy.ops.bim.assign_class(obj=site_obj.name, ifc_class="IfcSite") - bpy.ops.bim.assign_class(obj=building_obj.name, ifc_class="IfcBuilding") - bpy.ops.bim.assign_class(obj=building_storey_obj.name, ifc_class="IfcBuildingStorey") - bpy.ops.bim.assign_object(related_object=site_obj.name, relating_object=project_obj.name) - bpy.ops.bim.assign_object(related_object=building_obj.name, relating_object=site_obj.name) - bpy.ops.bim.assign_object(related_object=building_storey_obj.name, relating_object=building_obj.name) + + for subcontext in self.file.by_type("IfcGeometricRepresentationSubContext"): + if subcontext.ContextIdentifier == "Body": + bpy.context.scene.BIMProperties.contexts = str(subcontext.id()) + break + + bpy.ops.bim.assign_class(obj=site.name, ifc_class="IfcSite") + bpy.ops.bim.assign_class(obj=building.name, ifc_class="IfcBuilding") + bpy.ops.bim.assign_class(obj=building_storey.name, ifc_class="IfcBuildingStorey") + bpy.ops.bim.assign_object(related_object=site.name, relating_object=project.name) + bpy.ops.bim.assign_object(related_object=building.name, relating_object=site.name) + bpy.ops.bim.assign_object(related_object=building_storey.name, relating_object=building.name) # Data.load() return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/operator.py b/src/ifcblenderexport/blenderbim/bim/module/root/operator.py index 85594864cb..67a128ed98 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/root/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/root/operator.py @@ -1,6 +1,7 @@ import bpy import numpy as np import ifcopenshell +import ifcopenshell.util.schema import blenderbim.bim.module.root.create_product as create_product import blenderbim.bim.module.root.remove_product as remove_product import blenderbim.bim.module.root.reassign_class as reassign_class @@ -19,7 +20,8 @@ class EnableReassignClass(bpy.types.Operator): def execute(self, context): obj = bpy.context.active_object ifc_class = obj.name.split("/")[0] - ifc_schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(bpy.context.scene.BIMProperties.export_schema) + file = IfcStore.get_file() + ifc_schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema) bpy.context.active_object.BIMObjectProperties.is_reassigning_class = True ifc_products = [ "IfcElement", @@ -85,49 +87,39 @@ class AssignClass(bpy.types.Operator): def execute(self, context): objects = [bpy.data.objects.get(self.obj)] if self.obj else bpy.context.selected_objects self.file = IfcStore.get_file() + ifc_schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(self.file.schema) + self.declaration = ifc_schema.declaration_by_name(self.ifc_class) + if self.predefined_type == "USERDEFINED": + self.predefined_type = self.ifc_userdefined_type + elif self.predefined_type == "": + predefined_type = None for obj in objects: - if obj.BIMObjectProperties.ifc_definition_id: - continue - if self.predefined_type == "USERDEFINED": - predefined_type = self.ifc_userdefined_type - elif self.predefined_type == "": - predefined_type = None - else: - predefined_type = self.predefined_type - product = create_product.Usecase( - self.file, - { - "ifc_class": self.ifc_class, - "predefined_type": predefined_type, - "name": obj.name, - }, - ).execute() - obj.name = "{}/{}".format(product.is_a(), obj.name) - obj.BIMObjectProperties.ifc_definition_id = int(product.id()) - - bpy.ops.bim.add_representation(obj=obj.name) - - relating_structure = None - for collection in obj.users_collection: - if "Ifc" in collection.name: - relating_structure = self.file.by_id( - bpy.data.objects.get(collection.name).BIMObjectProperties.ifc_definition_id - ) - break - - if relating_structure: - assign_container.Usecase( - self.file, - { - "product": product, - "relating_structure": relating_structure, - }, - ).execute() - - if bpy.context.scene.BIMProperties.ifc_product == "IfcElementType": - self.place_in_types_collection(obj) + self.assign_class(obj) return {"FINISHED"} + def assign_class(self, obj): + if obj.BIMObjectProperties.ifc_definition_id: + return + product = create_product.Usecase( + self.file, + { + "ifc_class": self.ifc_class, + "predefined_type": self.predefined_type, + "name": obj.name, + }, + ).execute() + obj.name = "{}/{}".format(product.is_a(), obj.name) + obj.BIMObjectProperties.ifc_definition_id = int(product.id()) + + bpy.ops.bim.add_representation(obj=obj.name) + + if ifcopenshell.util.schema.is_a(self.declaration, "IfcElementType"): + self.place_in_types_collection(obj) + elif ifcopenshell.util.schema.is_a(self.declaration, "IfcSpatialElement") or self.ifc_class == "IfcProject": + self.place_in_spatial_collection(obj) + else: + self.assign_potential_spatial_container(obj) + def place_in_types_collection(self, obj): for project in [c for c in bpy.context.view_layer.layer_collection.children if "IfcProject" in c.name]: if not [c for c in project.children if "Types" in c.name]: @@ -140,6 +132,29 @@ class AssignClass(bpy.types.Operator): break break + def place_in_spatial_collection(self, obj): + for collection in obj.users_collection: + if collection.name == obj.name: + return + parent_collection = None + for collection in obj.users_collection: + collection.objects.unlink(obj) + if "Ifc" in collection.name: + parent_collection = collection + collection = bpy.data.collections.new(obj.name) + collection.objects.link(obj) + if parent_collection: + parent_collection.children.link(collection) + else: + bpy.context.scene.collection.children.link(collection) + + def assign_potential_spatial_container(self, obj): + for collection in obj.users_collection: + if "Ifc" not in collection.name: + continue + bpy.ops.bim.assign_container(relating_structure=collection.name, related_element=obj.name) + break + class UnassignClass(bpy.types.Operator): bl_idname = "bim.unassign_class" diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 24b68047c1..a9b4f8a17e 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -8,6 +8,7 @@ from . import ifc from . import annotation from . import decoration import bpy +from blenderbim.bim.ifc import IfcStore from bpy.types import PropertyGroup from bpy.app.handlers import persistent from bpy.props import ( @@ -125,14 +126,14 @@ def setDefaultProperties(scene): def getIfcPredefinedTypes(self, context): global types_enum - if len(types_enum) < 1: - for name, data in schema.ifc.elements.items(): - if name != self.ifc_class.strip(): - continue - for attribute in data["attributes"]: - if attribute["name"] != "PredefinedType": - continue - types_enum.extend([(e, e, "") for e in attribute["enum_values"]]) + file = IfcStore.get_file() + if len(types_enum) < 1 and file: + ifc_schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema) + declaration = ifc_schema.declaration_by_name(self.ifc_class) + for attribute in declaration.attributes(): + if attribute.name() == "PredefinedType": + types_enum.extend([(e, e, "") for e in attribute.type_of_attribute().declared_type().enumeration_items()]) + break return types_enum @@ -257,8 +258,7 @@ def getIfcProducts(self, context): "IfcElementType", "IfcSpatialElement", "IfcGroup", - "IfcStructural", - "IfcPositioningElement", + "IfcStructuralItem", "IfcContext", "IfcAnnotation", ] @@ -269,8 +269,19 @@ 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(schema.ifc, self.ifc_product)]) + file = IfcStore.get_file() + if len(classes_enum) < 1 and file: + ifc_schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema) + declaration = ifc_schema.declaration_by_name(self.ifc_product) + def get_classes(declaration): + results = [] + if not declaration.is_abstract(): + results.append(declaration.name()) + for subtype in declaration.subtypes(): + results.extend(get_classes(subtype)) + return results + classes = get_classes(declaration) + classes_enum.extend([(c, c, "") for c in sorted(classes)]) return classes_enum