From af5350bb9f4f2431b3ecd609754817262c4ee670 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 3 Jan 2021 19:09:01 +1100 Subject: [PATCH] WIP refactor representation editing into geometry module. See #1222. Some new functionality as a result: - Representations can now be added to any context, even invalid ones, non-subcontext, or missing target views - Switching representations now change all linked data objects --- .../blenderbim/bim/__init__.py | 13 +- .../blenderbim/bim/export_ifc.py | 11 - .../blenderbim/bim/import_ifc.py | 70 +---- .../blenderbim/bim/module/context/data.py | 5 +- .../blenderbim/bim/module/context/operator.py | 26 +- .../bim/module/geometry/__init__.py | 20 ++ ...epresentation.py => add_representation.py} | 0 .../module/geometry/assign_representation.py | 17 ++ .../blenderbim/bim/module/geometry/data.py | 26 ++ .../bim/module/geometry/operator.py | 185 +++++++++++++ .../module/geometry/remove_representation.py | 9 + .../blenderbim/bim/module/geometry/ui.py | 37 +++ .../blenderbim/bim/operator.py | 253 ------------------ src/ifcblenderexport/blenderbim/bim/prop.py | 47 +--- src/ifcblenderexport/blenderbim/bim/ui.py | 38 --- 15 files changed, 334 insertions(+), 423 deletions(-) create mode 100644 src/ifcblenderexport/blenderbim/bim/module/geometry/__init__.py rename src/ifcblenderexport/blenderbim/bim/module/geometry/{add_shape_representation.py => add_representation.py} (100%) create mode 100644 src/ifcblenderexport/blenderbim/bim/module/geometry/assign_representation.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/geometry/data.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/geometry/operator.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/geometry/remove_representation.py create mode 100644 src/ifcblenderexport/blenderbim/bim/module/geometry/ui.py diff --git a/src/ifcblenderexport/blenderbim/bim/__init__.py b/src/ifcblenderexport/blenderbim/bim/__init__.py index cb8940f1f8..88266caaf4 100644 --- a/src/ifcblenderexport/blenderbim/bim/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/__init__.py @@ -8,6 +8,7 @@ if bpy is not None: import blenderbim.bim.module.bcf as module_bcf import blenderbim.bim.module.context as module_context import blenderbim.bim.module.covetool as module_covetool + import blenderbim.bim.module.geometry as module_geometry import blenderbim.bim.module.model as module_model from . import ui, prop, operator @@ -132,9 +133,6 @@ if bpy is not None: operator.SmartClashGroup, operator.SelectSmartGroup, operator.LoadSmartGroupsForActiveClashSet, - operator.AddRepresentation, - operator.SwitchRepresentation, - operator.RemoveRepresentation, operator.OpenUpstream, operator.BIM_OT_ChangeClassificationLevel, operator.AddPropertySetTemplate, @@ -208,9 +206,6 @@ if bpy is not None: operator.InspectFromObject, operator.RewindInspector, operator.RefreshDrawingList, - operator.GetRepresentationIfcParameters, - operator.BakeParametricGeometry, - operator.UpdateIfcRepresentation, operator.SetBlenderClashSetA, operator.SetBlenderClashSetB, operator.ExecuteBlenderClash, @@ -244,8 +239,6 @@ if bpy is not None: prop.Schedule, prop.DrawingStyle, prop.Sheet, - prop.Subcontext, - prop.Representation, prop.PresentationLayer, prop.BIMProperties, prop.BIMDebugProperties, @@ -296,7 +289,6 @@ if bpy is not None: ui.BIM_PT_object_material, ui.BIM_PT_object_psets, ui.BIM_PT_object_qto, - ui.BIM_PT_representations, ui.BIM_PT_classification_references, ui.BIM_PT_documents, ui.BIM_PT_constraint_relations, @@ -323,6 +315,7 @@ if bpy is not None: classes.extend(module_bcf.classes) classes.extend(module_context.classes) classes.extend(module_covetool.classes) + classes.extend(module_geometry.classes) classes.extend(module_model.classes) def menu_func_export(self, context): @@ -358,6 +351,7 @@ if bpy is not None: module_bcf.register() module_context.register() module_covetool.register() + module_geometry.register() module_model.register() bpy.app.handlers.depsgraph_update_pre.append(operator.depsgraph_update_pre_handler) @@ -380,6 +374,7 @@ if bpy is not None: del bpy.types.TextCurve.BIMTextProperties bpy.types.SCENE_PT_unit.remove(ui.ifc_units) module_model.unregister() + module_geometry.unregister() module_covetool.unregister() module_context.unregister() module_bcf.unregister() diff --git a/src/ifcblenderexport/blenderbim/bim/export_ifc.py b/src/ifcblenderexport/blenderbim/bim/export_ifc.py index 6da3335d67..a6e150bbb1 100644 --- a/src/ifcblenderexport/blenderbim/bim/export_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/export_ifc.py @@ -3553,15 +3553,4 @@ class IfcExportSettings: settings.should_roundtrip_native = scene_bim.import_export_should_roundtrip_native settings.should_export_from_memory = scene_bim.export_should_export_from_memory settings.context_tree = [] - for ifc_context in ["model", "plan"]: - if getattr(scene_bim, "has_{}_context".format(ifc_context)): - subcontexts = {} - for subcontext in getattr(scene_bim, "{}_subcontexts".format(ifc_context)): - subcontexts.setdefault(subcontext.name, []).append(subcontext.target_view) - settings.context_tree.append( - { - "name": ifc_context.title(), - "subcontexts": [{"name": key, "target_views": value} for key, value in subcontexts.items()], - } - ) return settings diff --git a/src/ifcblenderexport/blenderbim/bim/import_ifc.py b/src/ifcblenderexport/blenderbim/bim/import_ifc.py index 0eff59bb59..e83cca9092 100644 --- a/src/ifcblenderexport/blenderbim/bim/import_ifc.py +++ b/src/ifcblenderexport/blenderbim/bim/import_ifc.py @@ -411,8 +411,6 @@ class IfcImporter: self.profile_code("Patching ifc") self.set_units() self.profile_code("Set units") - self.create_geometric_representation_contexts() - self.profile_code("Create contexts") self.create_project() self.profile_code("Create project") self.create_classifications() @@ -853,7 +851,6 @@ class IfcImporter: self.add_element_classifications(element, obj) self.add_element_document_relations(element, obj) self.add_type_product_psets(element, obj) - self.add_product_representations(element, obj) self.type_collection.objects.link(obj) self.type_products[element.GlobalId] = obj @@ -984,7 +981,6 @@ class IfcImporter: self.add_defines_by_type_relation(element, obj) self.add_opening_relation(element, obj) self.add_product_definitions(element, obj) - self.add_product_representations(element, obj) self.added_data[element.GlobalId] = obj if element.is_a("IfcOpeningElement"): @@ -1365,24 +1361,6 @@ class IfcImporter: bpy.ops.mesh.normals_make_consistent(context_override) bpy.ops.object.editmode_toggle(context_override) - def add_product_representations(self, element, obj): - if element.is_a("IfcProduct"): - if not element.Representation: - return - for r in element.Representation.Representations: - new = obj.BIMObjectProperties.representations.add() - new.name = r.RepresentationIdentifier - new.type = r.RepresentationType - new.ifc_definition_id = r.id() - elif element.is_a("IfcTypeProduct"): - if not element.RepresentationMaps: - return - for r in element.RepresentationMaps: - new = obj.BIMObjectProperties.representations.add() - new.name = r.MappedRepresentation.RepresentationIdentifier - new.type = r.MappedRepresentation.RepresentationType - new.ifc_definition_id = r.MappedRepresentation.id() - def add_product_definitions(self, element, obj): if not hasattr(element, "IsDefinedBy") or not element.IsDefinedBy: return @@ -1540,28 +1518,6 @@ class IfcImporter: unit.Prefix + "/" if hasattr(unit, "Prefix") and unit.Prefix else "", name ) - def create_geometric_representation_contexts(self): - bpy.context.scene.BIMProperties.has_model_context = False - for context in self.file.by_type("IfcGeometricRepresentationContext"): - if context.is_a("IfcGeometricRepresentationSubContext"): - if not context.ContextIdentifier: - # Revit creates invalid contexts, so we just ignore them - continue - if context.ContextType == "Model": - subcontexts = bpy.context.scene.BIMProperties.model_subcontexts - elif context.ContextType == "Plan": - subcontexts = bpy.context.scene.BIMProperties.plan_subcontexts - if subcontexts.get(context.ContextIdentifier): - continue - subcontext = subcontexts.add() - subcontext.name = context.ContextIdentifier - subcontext.target_view = context.TargetView - subcontext.ifc_definition_id = context.id() - elif context.ContextType == "Model": - bpy.context.scene.BIMProperties.has_model_context = True - elif context.ContextType == "Plan": - bpy.context.scene.BIMProperties.has_plan_context = True - def create_project(self): self.project = {"ifc": self.file.by_type("IfcProject")[0]} if self.project["ifc"].GlobalId in self.existing_elements: @@ -2064,23 +2020,6 @@ class IfcImporter: ): return representation.Items[0].MappingTarget - def get_geometry_type(self, element): - tree = [] - if hasattr(element, "Representation"): - tree = self.file.traverse(element.Representation) - elif hasattr(element, "RepresentationMaps"): - for representation_map in element.RepresentationMaps: - tree.extend(self.file.traverse(representation_map)) - representations = [ - e - for e in tree - if e.is_a("IfcRepresentation") - and e.RepresentationIdentifier == "Body" - and e.RepresentationType != "MappedRepresentation" - ] - for representation in representations: - return representation.Items[0].is_a() - def create_mesh(self, element, shape, is_curve=False): try: if hasattr(shape, "geometry"): @@ -2091,7 +2030,13 @@ class IfcImporter: if is_curve: return self.create_curve(geometry) - mesh = bpy.data.meshes.new(geometry.id) + representation_id = geometry.id + if "-" in representation_id: + representation_id = int(re.sub(r"\D", "", representation_id.split("-")[0])) + else: + representation_id = int(re.sub(r"\D", "", representation_id)) + mesh = bpy.data.meshes.new("{}/{}".format( + self.file.by_id(representation_id).ContextOfItems.id(), geometry.id)) if geometry.faces: num_vertices = len(geometry.verts) // 3 @@ -2132,7 +2077,6 @@ class IfcImporter: ios_materials.append(mat.name) mesh["ios_materials"] = ios_materials mesh["ios_material_ids"] = geometry.material_ids - mesh.BIMMeshProperties.geometry_type = str(self.get_geometry_type(element)) return mesh except: self.ifc_import_settings.logger.error("Could not create mesh for %s", element) diff --git a/src/ifcblenderexport/blenderbim/bim/module/context/data.py b/src/ifcblenderexport/blenderbim/bim/module/context/data.py index e313d3192a..0832b264e5 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/context/data.py +++ b/src/ifcblenderexport/blenderbim/bim/module/context/data.py @@ -1,6 +1,5 @@ -import blenderbim.bim.ifc +from blenderbim.bim.ifc import IfcStore -is_loaded = False class Data: is_loaded = False @@ -8,7 +7,7 @@ class Data: @classmethod def load(cls): - file = blenderbim.bim.ifc.IfcStore.get_file() + file = IfcStore.get_file() if not file: return cls.contexts = {} diff --git a/src/ifcblenderexport/blenderbim/bim/module/context/operator.py b/src/ifcblenderexport/blenderbim/bim/module/context/operator.py index 9b2a3099ec..9db240c84d 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/context/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/context/operator.py @@ -1,22 +1,25 @@ import bpy -import blenderbim.bim.ifc import blenderbim.bim.module.context.add_context as add_context import blenderbim.bim.module.context.remove_context as remove_context +from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.context.data import Data + class AddSubcontext(bpy.types.Operator): bl_idname = "bim.add_subcontext" bl_label = "Add Subcontext" def execute(self, context): - self.file = blenderbim.bim.ifc.IfcStore.get_file() - usecase = add_context.Usecase(self.file, { - "context": bpy.context.scene.BIMProperties.available_contexts, - "subcontext": bpy.context.scene.BIMProperties.available_subcontexts, - "target_view": bpy.context.scene.BIMProperties.available_target_views, - }) + self.file = IfcStore.get_file() + usecase = add_context.Usecase( + self.file, + { + "context": bpy.context.scene.BIMProperties.available_contexts, + "subcontext": bpy.context.scene.BIMProperties.available_subcontexts, + "target_view": bpy.context.scene.BIMProperties.available_target_views, + }, + ) result = usecase.execute() - Data.load() return {"FINISHED"} @@ -27,11 +30,8 @@ class RemoveSubcontext(bpy.types.Operator): ifc_definition_id: bpy.props.IntProperty() def execute(self, context): - self.file = blenderbim.bim.ifc.IfcStore.get_file() - usecase = remove_context.Usecase(self.file, { - "context": self.file.by_id(self.ifc_definition_id) - }) + self.file = IfcStore.get_file() + usecase = remove_context.Usecase(self.file, {"context": self.file.by_id(self.ifc_definition_id)}) usecase.execute() - Data.load() return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/__init__.py new file mode 100644 index 0000000000..525f7aa8d5 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/__init__.py @@ -0,0 +1,20 @@ +import bpy +from . import ui, operator + +classes = ( + operator.AddRepresentation, + operator.SwitchRepresentation, + operator.RemoveRepresentation, + operator.BakeParametricGeometry, + operator.UpdateIfcRepresentation, + operator.GetRepresentationIfcParameters, + ui.BIM_PT_representations, +) + + +def register(): + pass + + +def unregister(): + pass diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/add_shape_representation.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/add_representation.py similarity index 100% rename from src/ifcblenderexport/blenderbim/bim/module/geometry/add_shape_representation.py rename to src/ifcblenderexport/blenderbim/bim/module/geometry/add_representation.py diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/assign_representation.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/assign_representation.py new file mode 100644 index 0000000000..a6f724fe32 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/assign_representation.py @@ -0,0 +1,17 @@ +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = { + "product": None, + "representation": None + } + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + definition = self.settings["product"].Representation + if not definition: + definition = self.file.createIfcProductDefinitionShape() + representations = list(definition.Representations) + representations.append(self.settings["representation"]) + definition.Representations = representations diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/data.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/data.py new file mode 100644 index 0000000000..2e7837f9e6 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/data.py @@ -0,0 +1,26 @@ +from blenderbim.bim.ifc import IfcStore + + +class Data: + products = {} + + @classmethod + def load(cls, product_id): + file = IfcStore.get_file() + if not file: + return + cls.products[product_id] = {"Representations": {}} + product = file.by_id(product_id) + if not product.Representation: + return + for representation in product.Representation.Representations: + c = representation.ContextOfItems + cls.products[product_id]["Representations"][int(representation.id())] = { + "RepresentationIdentifier": representation.RepresentationIdentifier, + "RepresentationType": representation.RepresentationType, + "ContextOfItems": { + "ContextType": c.ContextType, + "ContextIdentifier": c.ContextIdentifier, + "TargetView": c.TargetView if c.is_a("IfcGeometricRepresentationSubContext") else "", + } + } diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/operator.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/operator.py new file mode 100644 index 0000000000..92af92695d --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/operator.py @@ -0,0 +1,185 @@ +import bpy +import numpy as np +import ifcopenshell +import logging +import blenderbim.bim.module.geometry.add_object_placement as add_object_placement +import blenderbim.bim.module.geometry.add_representation as add_representation +import blenderbim.bim.module.geometry.assign_styles as assign_styles +import blenderbim.bim.module.geometry.assign_representation as assign_representation +import blenderbim.bim.module.geometry.remove_representation as remove_representation +from blenderbim.bim.ifc import IfcStore +from blenderbim.bim import import_ifc +from blenderbim.bim.module.geometry.data import Data + + +class AddRepresentation(bpy.types.Operator): + bl_idname = "bim.add_representation" + bl_label = "Add Representation" + + def execute(self, context): + obj = bpy.context.active_object + self.file = IfcStore.get_file() + self.context_id = bpy.context.scene.BIMProperties.contexts + + element = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id) + usecase = add_representation.Usecase(self.file, { + "context": self.file.by_id(int(self.context_id)), + "geometry": obj.data, + "total_items": max(1, len(obj.material_slots)), + }) + result = usecase.execute() + if not result: + print("Failed to write shape representation") + return {"FINISHED"} + + usecase = assign_representation.Usecase(self.file, { + "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), + "representation": result + }) + usecase.execute() + + existing_mesh = obj.data + existing_mesh.use_fake_user = True + mesh = obj.data.copy() + mesh.name = "{}/{}".format(self.context_id, result.id()) + mesh.BIMMeshProperties.ifc_definition_id = int(result.id()) + obj.data = mesh + Data.load(obj.BIMObjectProperties.ifc_definition_id) + return {"FINISHED"} + + +class SwitchRepresentation(bpy.types.Operator): + bl_idname = "bim.switch_representation" + bl_label = "Switch Representation" + ifc_definition_id: bpy.props.IntProperty() + + def execute(self, context): + self.obj = bpy.context.active_object + + self.file = IfcStore.get_file() + context_of_items = self.file.by_id(self.ifc_definition_id).ContextOfItems + self.mesh_name = "{}/{}".format(context_of_items.id(), self.ifc_definition_id) + + mesh = bpy.data.meshes.get(self.mesh_name) + if mesh: + self.obj.data.user_remap(mesh) + self.pull_mesh_from_ifc() + return {"FINISHED"} + + def pull_mesh_from_ifc(self): + self.file = IfcStore.get_file() + logger = logging.getLogger("ImportIFC") + ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, IfcStore.path, logger) + element = self.file.by_id(self.obj.BIMObjectProperties.ifc_definition_id) + settings = ifcopenshell.geom.settings() + settings.set(settings.INCLUDE_CURVES, True) + shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id)) + ifc_importer = import_ifc.IfcImporter(ifc_import_settings) + ifc_importer.file = self.file + mesh = ifc_importer.create_mesh(element, shape) + mesh.name = self.mesh_name + mesh.BIMMeshProperties.ifc_definition_id = self.ifc_definition_id + self.obj.data.user_remap(mesh) + material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer) + material_creator.create(element, self.obj, mesh) + + +class RemoveRepresentation(bpy.types.Operator): + bl_idname = "bim.remove_representation" + bl_label = "Remove Representation" + ifc_definition_id: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + representation = self.file.by_id(self.ifc_definition_id) + obj = bpy.context.active_object + mesh = bpy.data.meshes.get("{}/{}".format(representation.ContextOfItems.id(), representation.id())) + if mesh: + if obj.data == mesh: + # TODO we can do better than this + void_mesh = bpy.data.meshes.get("Void") + if not void_mesh: + void_mesh = bpy.data.meshes.new("Void") + obj.data = void_mesh + bpy.data.meshes.remove(mesh) + usecase = remove_representation.Usecase(self.file, {"representation": representation}) + result = usecase.execute() + Data.load(obj.BIMObjectProperties.ifc_definition_id) + return {"FINISHED"} + + +class BakeParametricGeometry(bpy.types.Operator): + bl_idname = "bim.bake_parametric_geometry" + bl_label = "Bake Parametric Geometry" + + def execute(self, context): + obj = bpy.context.active_object + self.file = IfcStore.get_file() + + usecase = add_object_placement.Usecase(self.file, { + "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), + "matrix": np.array(obj.matrix_world) + }) + result = usecase.execute() + + element = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id) + usecase = add_representation.Usecase(self.file, { + "context": element.ContextOfItems, + "geometry": obj.data, + "total_items": max(1, len(obj.material_slots)), + }) + result = usecase.execute() + if not result: + print("Failed to write shape representation") + return {"FINISHED"} + + usecase = assign_styles.Usecase(self.file, { + "shape_representation": result, + "styles": [self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id) for s in obj.material_slots if s.material] + }) + usecase.execute() + + for inverse in self.file.get_inverse(element): + ifcopenshell.util.element.replace_attribute(inverse, element, result) + obj.data.BIMMeshProperties.ifc_definition_id = int(result.id()) + Data.load(obj.BIMObjectProperties.ifc_definition_id) + return {"FINISHED"} + + +class UpdateIfcRepresentation(bpy.types.Operator): + bl_idname = "bim.update_ifc_representation" + bl_label = "Update IFC Representation" + index: bpy.props.IntProperty() + + def execute(self, context): + self.file = IfcStore.get_file() + obj = bpy.context.active_object + props = obj.data.BIMMeshProperties + parameter = props.ifc_parameters[self.index] + element = IfcStore.get_file().by_id(parameter.step_id)[parameter.index] = parameter.value + bpy.ops.bim.switch_representation(ifc_definition_id = props.ifc_definition_id) + return {"FINISHED"} + + +class GetRepresentationIfcParameters(bpy.types.Operator): + bl_idname = "bim.get_representation_ifc_parameters" + bl_label = "Get Representation IFC Parameters" + + def execute(self, context): + self.file = IfcStore.get_file() + obj = bpy.context.active_object + props = obj.data.BIMMeshProperties + elements = IfcStore.get_file().traverse(IfcStore.get_file().by_id(props.ifc_definition_id)) + for element in elements: + if not element.is_a("IfcRepresentationItem"): + continue + for i in range(0, len(element)): + if element.attribute_type(i) == "DOUBLE": + new = props.ifc_parameters.add() + new.name = "{}/{}".format(element.is_a(), element.attribute_name(i)) + new.step_id = element.id() + new.type = element.attribute_type(i) + new.index = i + if element[i]: + new.value = element[i] + return {"FINISHED"} diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/remove_representation.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/remove_representation.py new file mode 100644 index 0000000000..995314dba6 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/remove_representation.py @@ -0,0 +1,9 @@ +class Usecase: + def __init__(self, file, settings=None): + self.file = file + self.settings = {"representation": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + self.file.remove(self.settings["representation"]) diff --git a/src/ifcblenderexport/blenderbim/bim/module/geometry/ui.py b/src/ifcblenderexport/blenderbim/bim/module/geometry/ui.py new file mode 100644 index 0000000000..61f0303af8 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/geometry/ui.py @@ -0,0 +1,37 @@ +import bpy +from bpy.types import Panel +from blenderbim.bim.module.geometry.data import Data + + +class BIM_PT_representations(Panel): + bl_label = "IFC Representations" + bl_idname = "BIM_PT_representations" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + + def draw(self, context): + layout = self.layout + props = context.active_object.BIMObjectProperties + + if props.ifc_definition_id not in Data.products: + Data.load(props.ifc_definition_id) + + representations = Data.products[props.ifc_definition_id]["Representations"] + if not representations: + layout.label(text="No representations found") + + row = layout.row(align=True) + row.prop(bpy.context.scene.BIMProperties, "contexts", text="") + row.operator("bim.add_representation", icon="ADD", text="") + + for ifc_definition_id, representation in representations.items(): + row = self.layout.row(align=True) + row.label(text=representation["ContextOfItems"]["ContextType"]) + row.label(text=representation["ContextOfItems"]["ContextIdentifier"]) + row.label(text=representation["ContextOfItems"]["TargetView"]) + row.label(text=representation["RepresentationType"]) + row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="").ifc_definition_id = ifc_definition_id + row.operator("bim.remove_representation", icon="X", text="").ifc_definition_id = ifc_definition_id + diff --git a/src/ifcblenderexport/blenderbim/bim/operator.py b/src/ifcblenderexport/blenderbim/bim/operator.py index 4b15f26fd5..ad4cccdac5 100644 --- a/src/ifcblenderexport/blenderbim/bim/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/operator.py @@ -2441,143 +2441,6 @@ class ActivateView(bpy.types.Operator): return {"FINISHED"} -class AddRepresentation(bpy.types.Operator): - bl_idname = "bim.add_representation" - bl_label = "Add Representation" - - def execute(self, context): - self.obj = bpy.context.active_object - - self.file = ifc.IfcStore.get_file() - if "/" not in self.obj.data.name: - self.obj.data.name = ifcopenshell.guid.compress(str(uuid.uuid4()).replace("-", "")) - self.obj.data.name = "Model/Body/MODEL_VIEW/" + self.obj.data.name - - self.context = bpy.context.scene.BIMProperties.available_contexts - self.subcontext = bpy.context.scene.BIMProperties.available_subcontexts - self.target_view = bpy.context.scene.BIMProperties.available_target_views - - existing_mesh = self.obj.data - existing_mesh.use_fake_user = True - mesh = self.obj.data.copy() - mesh.name = "{}/{}/{}/{}".format( - self.context, self.subcontext, self.target_view, self.obj.data.name.split("/")[3] - ) - representation = self.obj.BIMObjectProperties.representations.add() - representation.name = self.subcontext - representation.type = "Brep" if self.file.schema == "IFC2X3" else "Tessellation" - mesh.use_fake_user = True - self.obj.data = mesh - - # TODO: push representation - return {"FINISHED"} - - def push_mesh_to_ifc(self): - self.file = ifc.IfcStore.get_file() - logger = logging.getLogger("ImportIFC") - ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger) - element = self.file.by_id(self.obj.BIMObjectProperties.ifc_definition_id) - settings = ifcopenshell.geom.settings() - settings.set(settings.INCLUDE_CURVES, True) - shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id)) - ifc_importer = import_ifc.IfcImporter(ifc_import_settings) - ifc_importer.file = self.file - mesh = ifc_importer.create_mesh(element, shape) - mesh.name = "{}/{}/{}/{}".format( - self.context, self.subcontext, self.target_view, self.obj.data.name.split("/")[3] - ) - self.obj.data = mesh - material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer) - material_creator.create(element, self.obj, mesh) - return mesh - - -class SwitchRepresentation(bpy.types.Operator): - bl_idname = "bim.switch_representation" - bl_label = "Switch Representation" - ifc_definition_id: bpy.props.IntProperty() - - def execute(self, context): - self.obj = bpy.context.active_object - - self.file = ifc.IfcStore.get_file() - if "/" not in self.obj.data.name: - self.obj.data.name = ifcopenshell.guid.compress(str(uuid.uuid4()).replace("-", "")) - self.obj.data.name = "Model/Body/MODEL_VIEW/" + self.obj.data.name - - context_of_items = self.file.by_id(self.ifc_definition_id).ContextOfItems - self.context = context_of_items.ContextType - self.subcontext = context_of_items.ContextIdentifier - self.target_view = context_of_items.TargetView - - existing_mesh = self.obj.data - existing_mesh.use_fake_user = True - mesh = bpy.data.meshes.get( - "{}/{}/{}/{}".format(self.context, self.subcontext, self.target_view, self.obj.data.name.split("/")[3]) - ) - if not mesh: - mesh = self.pull_mesh_from_ifc() - mesh.use_fake_user = True - self.obj.data = mesh - return {"FINISHED"} - - def pull_mesh_from_ifc(self): - self.file = ifc.IfcStore.get_file() - logger = logging.getLogger("ImportIFC") - ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger) - element = self.file.by_id(self.obj.BIMObjectProperties.ifc_definition_id) - settings = ifcopenshell.geom.settings() - settings.set(settings.INCLUDE_CURVES, True) - shape = ifcopenshell.geom.create_shape(settings, self.file.by_id(self.ifc_definition_id)) - ifc_importer = import_ifc.IfcImporter(ifc_import_settings) - ifc_importer.file = self.file - mesh = ifc_importer.create_mesh(element, shape) - mesh.name = "{}/{}/{}/{}".format( - self.context, self.subcontext, self.target_view, self.obj.data.name.split("/")[3] - ) - self.obj.data = mesh - material_creator = import_ifc.MaterialCreator(ifc_import_settings, ifc_importer) - material_creator.create(element, self.obj, mesh) - return mesh - - -class RemoveRepresentation(bpy.types.Operator): - bl_idname = "bim.remove_representation" - bl_label = "Remove Representation" - index: bpy.props.IntProperty() - - def execute(self, context): - # TODO This should be refactored into a testable agnostic module. See #1222. - # Prep work - self.file = ifc.IfcStore.get_file() - obj = bpy.context.active_object - representation = obj.BIMObjectProperties.representations[self.index] - element = self.file.by_id(representation.ifc_definition_id) - c = element.ContextOfItems - - # Blender work - if "/" not in obj.data.name: - obj.data.name = "Model/Body/MODEL_VIEW/" + obj.data.name - mesh = bpy.data.meshes.get( - "{}/{}/{}/{}".format(c.ContextType, c.ContextIdentifier, c.TargetView, obj.data.name.split("/")[3]) - ) - if mesh: - if obj.data == mesh: - # TODO we can do better than this - void_name = "Void/Void/Void/" + obj.data.name.split("/")[3] - void_mesh = bpy.data.meshes.get(void_name) - if not void_mesh: - void_mesh = bpy.data.meshes.new(void_name) - obj.data = void_mesh - bpy.data.meshes.remove(mesh) - obj.BIMObjectProperties.representations.remove(self.index) - - # IFC work - # TODO: this works as a MVP but leaves a bunch of junk in the file. See #1222. - self.file.remove(element) - return {"FINISHED"} - - class OpenUpstream(bpy.types.Operator): bl_idname = "bim.open_upstream" bl_label = "Open Upstream Reference" @@ -4281,122 +4144,6 @@ class RefreshDrawingList(bpy.types.Operator): return {"FINISHED"} -class GetRepresentationIfcParameters(bpy.types.Operator): - bl_idname = "bim.get_representation_ifc_parameters" - bl_label = "Get Representation IFC Parameters" - - def execute(self, context): - self.file = ifc.IfcStore.get_file() - obj = bpy.context.active_object - props = obj.data.BIMMeshProperties - element_id = self.get_ifc_definition_id(obj) - if not element_id: - return {"FINISHED"} - elements = ifc.IfcStore.get_file().traverse(ifc.IfcStore.get_file().by_id(element_id)) - for element in elements: - if not element.is_a("IfcRepresentationItem"): - continue - for i in range(0, len(element)): - if element.attribute_type(i) == "DOUBLE": - new = props.ifc_parameters.add() - new.name = "{}/{}".format(element.is_a(), element.attribute_name(i)) - new.step_id = element.id() - new.type = element.attribute_type(i) - new.index = i - if element[i]: - new.value = element[i] - return {"FINISHED"} - - def get_ifc_definition_id(self, obj): - if "/" not in obj.data.name: - context, subcontext, target_view = ("Model", "Body", "MODEL_VIEW") - else: - context, subcontext, target_view = obj.data.name.split("/")[0:3] - for representation in obj.BIMObjectProperties.representations: - element = self.file.by_id(representation.ifc_definition_id) - if ifcopenshell.util.element.is_representation_of_context(element, context, subcontext, target_view): - return representation.ifc_definition_id or None - - -class BakeParametricGeometry(bpy.types.Operator): - bl_idname = "bim.bake_parametric_geometry" - bl_label = "Bake Parametric Geometry" - - def execute(self, context): - obj = bpy.context.active_object - self.file = ifc.IfcStore.get_file() - element = self.file.by_id(obj.data.BIMMeshProperties.ifc_definition_id) - - import blenderbim.bim.module.geometry.add_object_placement as add_object_placement - usecase = add_object_placement.Usecase(self.file, { - "product": self.file.by_id(obj.BIMObjectProperties.ifc_definition_id), - "matrix": np.array(obj.matrix_world) - }) - result = usecase.execute() - - import blenderbim.bim.module.geometry.add_shape_representation as add_shape_representation - usecase = add_shape_representation.Usecase(self.file, { - "context": element.ContextOfItems, - "geometry": obj.data, - "total_items": max(1, len(obj.material_slots)), - }) - result = usecase.execute() - if not result: - print("Failed to write shape representation") - return {"FINISHED"} - - import blenderbim.bim.module.geometry.assign_styles as assign_styles - usecase = assign_styles.Usecase(self.file, { - "shape_representation": result, - "styles": [self.file.by_id(s.material.BIMMaterialProperties.ifc_style_id) for s in obj.material_slots if s.material] - }) - usecase.execute() - - for inverse in self.file.get_inverse(element): - ifcopenshell.util.element.replace_attribute(inverse, element, result) - obj.data.BIMMeshProperties.ifc_definition_id = int(result.id()) - return {"FINISHED"} - - -class UpdateIfcRepresentation(bpy.types.Operator): - bl_idname = "bim.update_ifc_representation" - bl_label = "Update IFC Representation" - index: bpy.props.IntProperty() - - def execute(self, context): - self.file = ifc.IfcStore.get_file() - props = bpy.context.active_object.data.BIMMeshProperties - parameter = props.ifc_parameters[self.index] - element = ifc.IfcStore.get_file().by_id(parameter.step_id)[parameter.index] = parameter.value - self.recreate_ifc_representation() - return {"FINISHED"} - - def recreate_ifc_representation(self): - props = bpy.context.active_object.data.BIMMeshProperties - logger = logging.getLogger("ImportIFC") - self.ifc_import_settings = import_ifc.IfcImportSettings.factory(bpy.context, ifc.IfcStore.path, logger) - element = ifc.IfcStore.get_file().by_id(self.get_ifc_definition_id(bpy.context.active_object)) - settings = ifcopenshell.geom.settings() - shape = ifcopenshell.geom.create_shape(settings, element) - ifc_importer = import_ifc.IfcImporter(self.ifc_import_settings) - ifc_importer.file = ifc.IfcStore.get_file() - mesh = ifc_importer.create_mesh(element, shape) - bpy.context.active_object.data.user_remap(mesh) - ifc_importer.material_creator.mesh = mesh - if ifc_importer.material_creator.parse_representation(element): - ifc_importer.material_creator.assign_material_slots_to_faces(bpy.context.active_object) - - def get_ifc_definition_id(self, obj): - if "/" not in obj.data.name: - context, subcontext, target_view = ("Model", "Body", "MODEL_VIEW") - else: - context, subcontext, target_view = obj.data.name.split("/")[0:3] - for representation in obj.BIMObjectProperties.representations: - c = self.file.by_id(self.ifc_definition_id).ContextOfItems - if c.ContextType == context and c.ContextIdentifier == subcontext and c.TargetView == target_view: - return representation.ifc_definition_id or None - - class BlenderClasher: def process_clash_set(self): import collision diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index bcbb102b6c..9fa99cf4a5 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -54,20 +54,6 @@ vector_styles_enum = [] @persistent def setDefaultProperties(scene): - if ( - bpy.context.scene.BIMProperties.has_model_context - and len(bpy.context.scene.BIMProperties.model_subcontexts) == 0 - ): - subcontext = bpy.context.scene.BIMProperties.model_subcontexts.add() - subcontext.name = "Body" - subcontext.target_view = "MODEL_VIEW" - subcontext = bpy.context.scene.BIMProperties.model_subcontexts.add() - subcontext.name = "Box" - subcontext.target_view = "MODEL_VIEW" - if bpy.context.scene.BIMProperties.has_plan_context and len(bpy.context.scene.BIMProperties.plan_subcontexts) == 0: - subcontext = bpy.context.scene.BIMProperties.plan_subcontexts.add() - subcontext.name = "Annotation" - subcontext.target_view = "PLAN_VIEW" if len(bpy.context.scene.DocProperties.drawing_styles) == 0: drawing_style = bpy.context.scene.DocProperties.drawing_styles.add() drawing_style.name = "Technical" @@ -501,6 +487,19 @@ def getMaterialTypes(self, context): return materialtypes_enum +def getContexts(self, context): + from blenderbim.bim.module.context.data import Data + if not Data.is_loaded: + Data.load() + results = [] + for ifc_id, context in Data.contexts.items(): + results.append((str(ifc_id), context["ContextType"], "")) + for ifc_id2, subcontext in context["HasSubContexts"].items(): + results.append((str(ifc_id2), "{}/{}/{}".format( + subcontext["ContextType"], subcontext["ContextIdentifier"], subcontext["TargetView"]), "")) + return results + + def getSubcontexts(self, context): global subcontexts_enum subcontexts_enum.clear() @@ -551,19 +550,6 @@ class Attribute(PropertyGroup): float_value: FloatProperty(name="Value") -class Subcontext(PropertyGroup): - name: StringProperty(name="Name") - context: StringProperty(name="Context") - target_view: StringProperty(name="Target View") - ifc_definition_id: IntProperty(name="IFC Definition ID") - - -class Representation(PropertyGroup): - name: StringProperty(name="Name") - type: StringProperty(name="Type") - ifc_definition_id: IntProperty(name="IFC Definition ID") - - class MaterialLayer(PropertyGroup): name: StringProperty(name="Name") material: PointerProperty(name="Material", type=bpy.types.Material) @@ -1321,10 +1307,7 @@ class BIMProperties(PropertyGroup): classification: EnumProperty(items=getClassifications, name="Classification", update=refreshReferences) active_classification_name: StringProperty(name="Active Classification Name") classifications: CollectionProperty(name="Classifications", type=Classification) - has_model_context: BoolProperty(name="Has Model Context", default=True) - has_plan_context: BoolProperty(name="Has Plan Context", default=True) - model_subcontexts: CollectionProperty(name="Model Subcontexts", type=Subcontext) - plan_subcontexts: CollectionProperty(name="Plan Subcontexts", type=Subcontext) + contexts: EnumProperty(items=getContexts, name="Contexts") available_contexts: EnumProperty(items=[("Model", "Model", ""), ("Plan", "Plan", "")], name="Available Contexts") available_subcontexts: EnumProperty(items=getSubcontexts, name="Available Subcontexts") available_target_views: EnumProperty(items=getTargetViews, name="Available Target Views") @@ -1502,7 +1485,6 @@ class BIMObjectProperties(PropertyGroup): has_boundary_condition: BoolProperty(name="Has Boundary Condition") boundary_condition: PointerProperty(name="Boundary Condition", type=BoundaryCondition) structural_member_connection: PointerProperty(name="Structural Member Connection", type=bpy.types.Object) - representations: CollectionProperty(name="Representations", type=Representation) # Address applies to IfcSite's SiteAddress and IfcBuilding's BuildingAddress address: PointerProperty(name="Address", type=Address) @@ -1553,7 +1535,6 @@ class BIMMeshProperties(PropertyGroup): is_swept_solid: BoolProperty(name="Is Swept Solid") swept_solids: CollectionProperty(name="Swept Solids", type=SweptSolid) is_parametric: BoolProperty(name="Is Parametric", default=False) - geometry_type: StringProperty(name="Geometry Type") ifc_definition: StringProperty(name="IFC Definition") ifc_parameters: CollectionProperty(name="IFC Parameters", type=IfcParameter) active_representation_item_index: IntProperty(name="Active Representation Item Index") diff --git a/src/ifcblenderexport/blenderbim/bim/ui.py b/src/ifcblenderexport/blenderbim/bim/ui.py index ef73a3c7d4..13ebadb6cb 100644 --- a/src/ifcblenderexport/blenderbim/bim/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/ui.py @@ -626,42 +626,6 @@ class BIM_PT_constraint_relations(Panel): layout.label(text="Constraint is invalid") -class BIM_PT_representations(Panel): - bl_label = "IFC Representations" - bl_idname = "BIM_PT_representations" - bl_options = {"DEFAULT_CLOSED"} - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "object" - - def draw(self, context): - layout = self.layout - props = context.active_object.BIMObjectProperties - - if not props.representations: - layout.label(text="No representations found") - - row = layout.row(align=True) - row.prop(bpy.context.scene.BIMProperties, "available_contexts", text="") - row.prop(bpy.context.scene.BIMProperties, "available_subcontexts", text="") - row.prop(bpy.context.scene.BIMProperties, "available_target_views", text="") - row.operator("bim.add_representation", icon="ADD", text="") - - self.file = ifc.IfcStore.get_file() - for index, representation in enumerate(props.representations): - row = layout.row(align=True) - row.prop(representation, "name", text="") - row.prop(representation, "type", text="") - if not representation.ifc_definition_id: - continue - context_of_items = self.file.by_id(representation.ifc_definition_id).ContextOfItems - if context_of_items.is_a() == "IfcGeometricRepresentationContext": - continue - op = row.operator("bim.switch_representation", icon="OUTLINER_DATA_MESH", text="") - op.ifc_definition_id = representation.ifc_definition_id - row.operator("bim.remove_representation", icon="X", text="").index = index - - class BIM_PT_classification_references(Panel): bl_label = "IFC Classification References" bl_idname = "BIM_PT_classification_references" @@ -809,8 +773,6 @@ class BIM_PT_mesh(Panel): layout = self.layout props = context.active_object.data.BIMMeshProperties - row = layout.row() - row.prop(props, "geometry_type") layout.label(text="IFC Parameters:") row = layout.row() row.operator("bim.get_representation_ifc_parameters")