From e38f3beb91e725b16ffa5ca458dd2affca18704b Mon Sep 17 00:00:00 2001 From: Gorgious Date: Mon, 8 Apr 2024 21:31:23 +0200 Subject: [PATCH] New UI List in Sandbox panel to access, flip, remove, add, edit clipping planes and some tweaks : - Disable possibility to edit clipping plane quad geometry - Add select and delete operators - Remove polls from CreateClippingPlane that prevented from using it in the Properties editor. The clipping plane is added near the origin when using the Add button in the panel --- src/blenderbim/blenderbim/bim/__init__.py | 3 ++ .../bim/module/geometry/operator.py | 12 ++++-- .../blenderbim/bim/module/project/operator.py | 42 +++++++++---------- .../blenderbim/bim/module/project/prop.py | 5 +++ src/blenderbim/blenderbim/bim/operator.py | 25 +++++++++++ src/blenderbim/blenderbim/bim/ui.py | 34 +++++++++++++++ src/blenderbim/blenderbim/tool/blender.py | 15 +++++-- 7 files changed, 109 insertions(+), 27 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/__init__.py b/src/blenderbim/blenderbim/bim/__init__.py index ec0fb4b38f..1476a30bff 100644 --- a/src/blenderbim/blenderbim/bim/__init__.py +++ b/src/blenderbim/blenderbim/bim/__init__.py @@ -97,8 +97,10 @@ for name in modules.keys(): classes = [ operator.AddIfcFile, operator.BIM_OT_add_section_plane, + operator.BIM_OT_delete_object, operator.BIM_OT_open_webbrowser, operator.BIM_OT_remove_section_plane, + operator.BIM_OT_select_object, operator.BIM_OT_show_description, operator.ClippingPlaneCutWithCappings, operator.EditBlenderCollection, @@ -131,6 +133,7 @@ classes = [ prop.BIMMeshProperties, prop.BIMFacet, prop.BIMFilterGroup, + ui.BIM_UL_clipping_plane, ui.BIM_UL_generic, ui.BIM_UL_topics, ui.BIM_ADDON_preferences, diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index e667c39097..e9c723068d 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -1015,7 +1015,9 @@ class DuplicateMoveLinkedAggregate(bpy.types.Operator): return linked_aggregate_group = ifcopenshell.api.run("group.add_group", tool.Ifc.get(), Name=self.group_name) - ifcopenshell.api.run("group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group) + ifcopenshell.api.run( + "group.assign_group", tool.Ifc.get(), products=[element], group=linked_aggregate_group + ) def custom_incremental_naming_for_element_assembly(old_to_new): for new in old_to_new.values(): @@ -1267,7 +1269,9 @@ class RefreshLinkedAggregate(bpy.types.Operator): tool.Ifc.get_object(base_instance).select_set(True) - old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator(self, context) + old_to_new = DuplicateMoveLinkedAggregate.execute_ifc_duplicate_linked_aggregate_operator( + self, context + ) for old, new in old_to_new.items(): new_obj = tool.Ifc.get_object(new[0]) new_base_matrix = Matrix.LocRotScale(*duplicate_matrix) @@ -1439,7 +1443,9 @@ class OverrideModeSetEdit(bpy.types.Operator): for obj in selected_objs: if not obj: continue - if not obj.data: + obj_supports_edit_mode, message = tool.Blender.object_supports_edit_mode(obj) + if not obj_supports_edit_mode: + self.report({"INFO"}, message) obj.select_set(False) continue element = tool.Ifc.get_entity(obj) diff --git a/src/blenderbim/blenderbim/bim/module/project/operator.py b/src/blenderbim/blenderbim/bim/module/project/operator.py index 450c1397e8..8e96df2dcc 100644 --- a/src/blenderbim/blenderbim/bim/module/project/operator.py +++ b/src/blenderbim/blenderbim/bim/module/project/operator.py @@ -1920,32 +1920,32 @@ class CreateClippingPlane(bpy.types.Operator): bl_label = "Create Clipping Plane" bl_options = {"REGISTER", "UNDO"} - @classmethod - def poll(cls, context): - return context.area.type == "VIEW_3D" - def execute(self, context): from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d # Clean up deleted planes - if len(bpy.context.scene.BIMProjectProperties.clipping_planes) > 5: + if len(context.scene.BIMProjectProperties.clipping_planes) > 5: self.report({"INFO"}, "Maximum of six clipping planes allowed.") return {"FINISHED"} - for area in bpy.context.screen.areas: + for area in context.screen.areas: if area.type == "VIEW_3D": area.tag_redraw() region = context.region rv3d = context.region_data - coord = (self.mouse_x, self.mouse_y) - origin = region_2d_to_origin_3d(region, rv3d, coord) - direction = region_2d_to_vector_3d(region, rv3d, coord) - hit, location, normal, face_index, obj, matrix = self.ray_cast(context, origin, direction) - if not hit: - self.report({"INFO"}, "No object found.") - return {"FINISHED"} + if rv3d: # Called from a 3D viewport + coord = (self.mouse_x, self.mouse_y) + origin = region_2d_to_origin_3d(region, rv3d, coord) + direction = region_2d_to_vector_3d(region, rv3d, coord) + hit, location, normal, face_index, obj, matrix = self.ray_cast(context, origin, direction) + if not hit: + self.report({"INFO"}, "No object found.") + return {"FINISHED"} + else: # Not Called from a 3D viewport + location = (0, 0, 1) + normal = (0, 0, 1) vertices = [(-0.5, -0.5, 0), (0.5, -0.5, 0), (0.5, 0.5, 0), (-0.5, 0.5, 0)] @@ -1956,20 +1956,20 @@ class CreateClippingPlane(bpy.types.Operator): mesh.update() plane_obj = bpy.data.objects.new("ClippingPlane", mesh) - bpy.context.collection.objects.link(plane_obj) + context.collection.objects.link(plane_obj) z_axis = Vector((0, 0, 1)) rotation_matrix = z_axis.rotation_difference(normal).to_matrix().to_4x4() plane_obj.matrix_world = rotation_matrix plane_obj.matrix_world.translation = location - bpy.context.scene.cursor.location = location + context.scene.cursor.location = location - new = bpy.context.scene.BIMProjectProperties.clipping_planes.add() + new = context.scene.BIMProjectProperties.clipping_planes.add() new.obj = plane_obj tool.Blender.set_active_object(plane_obj) - ClippingPlaneDecorator.install(bpy.context) + ClippingPlaneDecorator.install(context) bpy.ops.bim.refresh_clipping_planes("INVOKE_DEFAULT") return {"FINISHED"} @@ -1991,11 +1991,11 @@ class FlipClippingPlane(bpy.types.Operator): @classmethod def poll(cls, context): - return context.area.type == "VIEW_3D" + return context.active_object def execute(self, context): - obj = bpy.context.active_object - if obj in [cp.obj for cp in bpy.context.scene.BIMProjectProperties.clipping_planes]: + obj = context.active_object + if obj in context.scene.BIMProjectProperties.clipping_planes_objs: obj.rotation_euler[0] += radians(180) - bpy.context.view_layer.update() + context.view_layer.update() return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/project/prop.py b/src/blenderbim/blenderbim/bim/module/project/prop.py index e988a89f5b..6e34461be4 100644 --- a/src/blenderbim/blenderbim/bim/module/project/prop.py +++ b/src/blenderbim/blenderbim/bim/module/project/prop.py @@ -187,6 +187,11 @@ class BIMProjectProperties(PropertyGroup): use_relative_project_path: BoolProperty(name="Use Relative Project Path", default=False) queried_obj: bpy.props.PointerProperty(type=bpy.types.Object) clipping_planes: bpy.props.CollectionProperty(type=ObjProperty) + clipping_planes_active: bpy.props.IntProperty(min=0, default=0, max=5) + + @property + def clipping_planes_objs(self): + return list({cp.obj for cp in self.clipping_planes if cp.obj}) def get_library_element_index(self, lib_element): return next((i for i in range(len(self.library_elements)) if self.library_elements[i] == lib_element)) diff --git a/src/blenderbim/blenderbim/bim/operator.py b/src/blenderbim/blenderbim/bim/operator.py index ca3b1d4aa3..381fb98e2e 100644 --- a/src/blenderbim/blenderbim/bim/operator.py +++ b/src/blenderbim/blenderbim/bim/operator.py @@ -936,6 +936,31 @@ class BIM_OT_enum_property_search(bpy.types.Operator): ) +class BIM_OT_select_object(bpy.types.Operator): + bl_idname = "bim.select_object" + bl_label = "Select Object" + bl_options = {"REGISTER", "UNDO"} + obj_name: bpy.props.StringProperty(description="Object Name To Select") + + def execute(self, context): + obj = bpy.data.objects[self.obj_name] + tool.Blender.set_objects_selection(context, obj, [obj], clear_previous_selection=True) + return {"FINISHED"} + + +class BIM_OT_delete_object(bpy.types.Operator): + bl_idname = "bim.delete_object" + bl_label = "Delete Object" + bl_options = {"REGISTER", "UNDO"} + obj_name: bpy.props.StringProperty(description="Object Name To Delete") + + def execute(self, context): + obj = bpy.data.objects[self.obj_name] + with context.temp_override(selected_objects=[obj], active_object=obj): + bpy.ops.bim.override_object_delete(is_batch=False) + return {"FINISHED"} + + class EditBlenderCollection(bpy.types.Operator): bl_idname = "bim.edit_blender_collection" bl_label = "Add or Remove Blender Collection Item" diff --git a/src/blenderbim/blenderbim/bim/ui.py b/src/blenderbim/blenderbim/bim/ui.py index bdc74c516a..05df26cad3 100644 --- a/src/blenderbim/blenderbim/bim/ui.py +++ b/src/blenderbim/blenderbim/bim/ui.py @@ -127,6 +127,40 @@ class BIM_PT_section_with_cappings(Panel): row.operator("bim.clipping_plane_cut_with_cappings", icon="XRAY", text="Cut") row.operator("bim.revert_clipping_plane_cut", icon="FILE_REFRESH", text="Revert Cut") + props = context.scene.BIMProjectProperties + box = layout.box() + header = box.row(align=True) + header.label(text="Clipping Planes") + header.operator("bim.create_clipping_plane", text="", icon="ADD") + + box.template_list( + "BIM_UL_clipping_plane", + "", + props, + "clipping_planes", + props, + "clipping_planes_active", + ) + + if props.clipping_planes_active < len(props.clipping_planes): + active_clipping_plane_obj = props.clipping_planes[props.clipping_planes_active].obj + box.prop(active_clipping_plane_obj, "location") + box.prop(active_clipping_plane_obj, "rotation_euler") + + +class BIM_UL_clipping_plane(bpy.types.UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + obj = item.obj + row = layout.row(align=True) + row.prop(obj, "name", text="", emboss=False) + row.operator("bim.select_object", text="", icon="RESTRICT_SELECT_OFF").obj_name = obj.name + row.context_pointer_set("active_object", obj) + row.operator("bim.flip_clipping_plane", text="", icon="PASTEFLIPDOWN") + row.operator("bim.delete_object", text="", icon="TRASH").obj_name = obj.name + else: + layout.label(text="", translate=False) + class BIM_UL_generic(bpy.types.UIList): def draw_item(self, context, layout, data, item, icon, active_data, active_propname): diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py index 0d2b7d8b9e..63bec8569f 100644 --- a/src/blenderbim/blenderbim/tool/blender.py +++ b/src/blenderbim/blenderbim/tool/blender.py @@ -689,6 +689,14 @@ class Blender(blenderbim.core.tool.Blender): return collections_mapping + @classmethod + def object_supports_edit_mode(cls, obj): + if not obj.data: + return False, "Can't Edit Empty Object" + if obj in bpy.context.scene.BIMProjectProperties.clipping_planes_objs: + return False, "Can't Edit Clipping Plane Geometry" + return True, "" + class Modifier: @classmethod def is_eligible_for_railing_modifier(cls, obj): @@ -790,8 +798,7 @@ class Blender(blenderbim.core.tool.Blender): @classmethod def get_modifiers_data(cls, parent_element): array_pset = ifcopenshell.util.element.get_pset(parent_element, "BBIM_Array") - for modifier_data in json.loads(array_pset["Data"]): - yield modifier_data + yield from json.loads(array_pset["Data"]) @classmethod def get_children_objects(cls, modifier_data): @@ -838,7 +845,9 @@ class Blender(blenderbim.core.tool.Blender): bpy.utils.register_tool(ws_model.PipeTool, after={"bim.duct_tool"}, separator=False, group=False) bpy.utils.register_tool(ws_model.BimTool, after={"bim.pipe_tool"}, separator=False, group=False) bpy.utils.register_tool(ws_drawing.AnnotationTool, after={"bim.bim_tool"}, separator=True, group=False) - bpy.utils.register_tool(ws_spatial.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False) + bpy.utils.register_tool( + ws_spatial.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=False + ) bpy.utils.register_tool( ws_structural.StructuralTool, after={"bim.spatial_tool"}, separator=False, group=False )