From 99274c2d7a49e032da8be64daff553cce6ce3038 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 6 Feb 2023 20:28:50 +1100 Subject: [PATCH] Fix #1806. Changing filling types now also updates the void representation. --- .../blenderbim/bim/module/model/handler.py | 6 + .../blenderbim/bim/module/model/opening.py | 170 +++++++++++------- 2 files changed, 109 insertions(+), 67 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/handler.py b/src/blenderbim/blenderbim/bim/module/model/handler.py index 90fc56adea..800b9d9f00 100644 --- a/src/blenderbim/blenderbim/bim/module/model/handler.py +++ b/src/blenderbim/blenderbim/bim/module/model/handler.py @@ -93,3 +93,9 @@ def load_post(*args): "BlenderBIM.DumbProfile.RegenerateFromType", profile.DumbProfileRegenerator().regenerate_from_type, ) + + ifcopenshell.api.add_post_listener( + "type.assign_type", + "BlenderBIM.Opening.RegenerateFromType", + opening.FilledOpeningGenerator().regenerate_from_type, + ) diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py index 46241f5f6f..0749708a24 100644 --- a/src/blenderbim/blenderbim/bim/module/model/opening.py +++ b/src/blenderbim/blenderbim/bim/module/model/opening.py @@ -46,24 +46,28 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator): filling_obj: bpy.props.StringProperty() def _execute(self, context): - props = context.scene.BIMModelProperties + FilledOpeningGenerator().generate(bpy.data.objects.get(self.filling_obj), bpy.data.objects.get(self.voided_obj)) + return {"FINISHED"} + + +class FilledOpeningGenerator: + def generate(self, filling_obj, voided_obj): + props = bpy.context.scene.BIMModelProperties unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - voided_obj = bpy.data.objects.get(self.voided_obj) - filling_obj = bpy.data.objects.get(self.filling_obj) filling = tool.Ifc.get_entity(filling_obj) if not voided_obj or not filling_obj: - return {"FINISHED"} + return element = tool.Ifc.get_entity(voided_obj) - target = context.scene.cursor.location + target = bpy.context.scene.cursor.location raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.01) if not raycast[0]: target = filling_obj.matrix_world.col[3].to_3d().copy() raycast = voided_obj.closest_point_on_mesh(voided_obj.matrix_world.inverted() @ target, distance=0.5) if not raycast[0]: - return {"FINISHED"} + return # In this prototype, we assume openings are only added to axis-based elements layers = tool.Model.get_material_layer_parameters(element) @@ -80,41 +84,23 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator): filling_obj.matrix_world = new_matrix bpy.context.view_layer.update() - existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling) - - if existing_opening_occurrence: - existing_opening_occurrence_obj = tool.Ifc.get_object(existing_opening_occurrence) - if existing_opening_occurrence_obj: - opening_obj = bpy.data.objects.new("Opening", existing_opening_occurrence_obj.data) - opening_obj.data = existing_opening_occurrence_obj.data - else: - opening_obj = self.create_object_using_element_representation(existing_opening_occurrence) - opening_obj.matrix_world = filling_obj.matrix_world - else: - opening_obj = self.generate_opening_from_filling(filling, filling_obj, voided_obj) - - # Still prototyping, for now duplicating code from bpy.ops.bim.add_opening if tool.Ifc.is_moved(voided_obj): blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=voided_obj) - # Ensure we check this prior to adding the class - has_visible_openings = self.has_visible_openings(element) - - body_context = ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body") - opening = blenderbim.core.root.assign_class( - tool.Ifc, - tool.Collector, - tool.Root, - obj=opening_obj, - ifc_class="IfcOpeningElement", - should_add_representation=not bool(existing_opening_occurrence), - context=body_context, - ) + existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling) if existing_opening_occurrence: + opening = ifcopenshell.api.run( + "root.create_entity", + tool.Ifc.get(), + ifc_class="IfcOpeningElement", + predefined_type="OPENING", + name="Opening", + ) ifcopenshell.api.run( "geometry.edit_object_placement", tool.Ifc.get(), product=opening, matrix=filling_obj.matrix_world ) + representation = ifcopenshell.util.representation.get_representation( existing_opening_occurrence, "Model", "Body", "MODEL_VIEW" ) @@ -126,6 +112,17 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator): "geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation ) else: + has_visible_openings = self.has_visible_openings(element) + opening_obj = self.generate_opening_from_filling(filling, filling_obj, voided_obj) + opening = blenderbim.core.root.assign_class( + tool.Ifc, + tool.Collector, + tool.Root, + obj=opening_obj, + ifc_class="IfcOpeningElement", + should_add_representation=True, + context=ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body"), + ) representation = ifcopenshell.util.representation.get_representation(opening, "Model", "Body", "MODEL_VIEW") mapped_representation = ifcopenshell.api.run( "geometry.map_representation", tool.Ifc.get(), representation=representation @@ -136,8 +133,12 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator): ifcopenshell.api.run( "geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation ) + if not has_visible_openings: + tool.Ifc.unlink(obj=opening_obj) + bpy.data.objects.remove(opening_obj) ifcopenshell.api.run("void.add_opening", tool.Ifc.get(), opening=opening, element=element) + ifcopenshell.api.run("void.add_filling", tool.Ifc.get(), opening=opening, element=filling) representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id) blenderbim.core.geometry.switch_representation( @@ -150,14 +151,78 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator): should_sync_changes_first=False, ) - bpy.ops.bim.add_filling(opening=opening_obj.name, obj=filling_obj.name) + return {"FINISHED"} - if not has_visible_openings: - # It's a little strange how we create an object just to delete it. - # In the future this could be streamlined. + def regenerate_from_type(self, usecase_path, ifc_file, settings): + filling = settings["related_object"] + if not filling.FillsVoids: + return + + opening = filling.FillsVoids[0].RelatingOpeningElement + voided_obj = tool.Ifc.get_object(opening.VoidsElements[0].RelatingBuildingElement) + + opening_rep = ifcopenshell.util.representation.get_representation(opening, "Model", "Body", "MODEL_VIEW") + ifcopenshell.api.run( + "geometry.unassign_representation", tool.Ifc.get(), product=opening, representation=opening_rep + ) + + existing_opening_occurrence = self.get_existing_opening_occurrence_if_any(filling) + + if existing_opening_occurrence: + representation = ifcopenshell.util.representation.get_representation( + existing_opening_occurrence, "Model", "Body", "MODEL_VIEW" + ) + representation = ifcopenshell.util.representation.resolve_representation(representation) + mapped_representation = ifcopenshell.api.run( + "geometry.map_representation", tool.Ifc.get(), representation=representation + ) + ifcopenshell.api.run( + "geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation + ) + else: + opening_obj = tool.Ifc.get_object(opening) + if opening_obj: + tool.Ifc.unlink(obj=opening_obj) + bpy.data.objects.remove(opening_obj) + + filling_obj = tool.Ifc.get_object(filling) + opening_obj = self.generate_opening_from_filling(filling, filling_obj, voided_obj) + tool.Ifc.link(opening, opening_obj) + + representation = ifcopenshell.api.run( + "geometry.add_representation", + tool.Ifc.get(), + context=ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body"), + blender_object=opening_obj, + geometry=opening_obj.data, + coordinate_offset=tool.Geometry.get_cartesian_point_coordinate_offset(opening_obj), + total_items=tool.Geometry.get_total_representation_items(opening_obj), + should_force_faceted_brep=tool.Geometry.should_force_faceted_brep(), + should_force_triangulation=tool.Geometry.should_force_triangulation(), + should_generate_uvs=tool.Geometry.should_generate_uvs(opening_obj), + ifc_representation_class=None, + profile_set_usage=None, + ) + + mapped_representation = ifcopenshell.api.run( + "geometry.map_representation", tool.Ifc.get(), representation=representation + ) + ifcopenshell.api.run( + "geometry.assign_representation", tool.Ifc.get(), product=opening, representation=mapped_representation + ) tool.Ifc.unlink(obj=opening_obj) bpy.data.objects.remove(opening_obj) - return {"FINISHED"} + + representation = tool.Ifc.get().by_id(voided_obj.data.BIMMeshProperties.ifc_definition_id) + blenderbim.core.geometry.switch_representation( + tool.Ifc, + tool.Geometry, + obj=voided_obj, + representation=representation, + should_reload=True, + is_global=True, + should_sync_changes_first=False, + ) def generate_opening_from_filling(self, filling, filling_obj, voided_obj): profile = ifcopenshell.util.representation.get_representation(filling, "Model", "Profile", "ELEVATION_VIEW") @@ -168,9 +233,7 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator): def has_visible_openings(self, element): for opening in [r.RelatedOpeningElement for r in element.HasOpenings]: if tool.Ifc.get_object(opening): - print("OPENINGS ARE VISIBLE") return True - print("OPENINGS ARE NOTOOOTT VISIBLE") return False def get_existing_opening_occurrence_if_any(self, filling): @@ -181,31 +244,6 @@ class AddFilledOpening(bpy.types.Operator, tool.Ifc.Operator): if filling_occurrence != filling and filling_occurrence.FillsVoids: return filling_occurrence.FillsVoids[0].RelatingOpeningElement - def create_object_using_element_representation(self, opening): - settings = ifcopenshell.geom.settings() - - shape = ifcopenshell.geom.create_shape(settings, opening) - verts = shape.geometry.verts - faces = shape.geometry.faces - grouped_verts = [[verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)] - grouped_faces = [[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)] - - bm = bmesh.new() - bm.verts.index_update() - bm.faces.index_update() - new_verts = [bm.verts.new(v) for v in grouped_verts] - new_faces = [bm.faces.new((new_verts[f[0]], new_verts[f[1]], new_verts[f[2]])) for f in grouped_faces] - - bm.verts.index_update() - bm.faces.index_update() - - mesh = bpy.data.meshes.new(name="Opening") - bm.to_mesh(mesh) - bm.free() - - obj = bpy.data.objects.new("Opening", mesh) - return obj - def generate_opening_from_filling_profile(self, filling_obj, voided_obj, profile): settings = ifcopenshell.geom.settings() settings.set(settings.INCLUDE_CURVES, True) @@ -712,9 +750,7 @@ class EditOpenings(Operator, tool.Ifc.Operator): bpy.data.objects.remove(opening_obj) for obj in building_objs: - print('updating building obj', obj) element = tool.Ifc.get_entity(obj) - print('element is', element) body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") blenderbim.core.geometry.switch_representation( tool.Ifc,