# SPDX-FileCopyrightText: 2011-2023 Blender Authors # # SPDX-License-Identifier: GPL-2.0-or-later import bpy from bpy.types import ( Operator, FileHandler, ) from bpy.props import ( BoolProperty, EnumProperty, ) bl_file_extensions_image_and_movie = ";".join(( *bpy.path.extensions_image, *bpy.path.extensions_movie, )) class VIEW3D_OT_edit_mesh_extrude_individual_move(Operator): """Extrude each individual face separately along local normals""" bl_label = "Extrude Individual and Move" bl_idname = "view3d.edit_mesh_extrude_individual_move" @classmethod def poll(cls, context): return context.mode == 'EDIT_MESH' def execute(self, context): from bpy_extras.object_utils import object_report_if_active_shape_key_is_locked ob = context.object if object_report_if_active_shape_key_is_locked(ob, self): return {'CANCELLED'} mesh = ob.data select_mode = context.tool_settings.mesh_select_mode totface = mesh.total_face_sel totedge = mesh.total_edge_sel # totvert = mesh.total_vert_sel if select_mode[2] and totface == 1: bpy.ops.mesh.extrude_region_move( 'INVOKE_REGION_WIN', TRANSFORM_OT_translate={ "orient_type": 'NORMAL', "constraint_axis": (False, False, True), "release_confirm": False, }, ) elif select_mode[2] and totface > 1: bpy.ops.mesh.extrude_faces_move( 'INVOKE_REGION_WIN', TRANSFORM_OT_shrink_fatten={ "release_confirm": False, }, ) elif select_mode[1] and totedge >= 1: bpy.ops.mesh.extrude_edges_move( 'INVOKE_REGION_WIN', TRANSFORM_OT_translate={ "release_confirm": False, }, ) else: bpy.ops.mesh.extrude_vertices_move( 'INVOKE_REGION_WIN', TRANSFORM_OT_translate={ "release_confirm": False, }, ) # ignore return from operators above because they are 'RUNNING_MODAL', # and cause this one not to be freed. #24671. return {'FINISHED'} def invoke(self, context, _event): return self.execute(context) class VIEW3D_OT_edit_mesh_extrude_move(Operator): """Extrude region together along the average normal""" bl_label = "Extrude and Move on Normals" bl_idname = "view3d.edit_mesh_extrude_move_normal" dissolve_and_intersect: BoolProperty( name="dissolve_and_intersect", default=False, description="Dissolves adjacent faces and intersects new geometry", ) @classmethod def poll(cls, context): return context.mode == 'EDIT_MESH' @staticmethod def extrude_region(operator, context, use_vert_normals, dissolve_and_intersect): from bpy_extras.object_utils import object_report_if_active_shape_key_is_locked ob = context.object if object_report_if_active_shape_key_is_locked(ob, operator): return {'CANCELLED'} mesh = ob.data totface = mesh.total_face_sel totedge = mesh.total_edge_sel # totvert = mesh.total_vert_sel if totface >= 1: if use_vert_normals: bpy.ops.mesh.extrude_region_shrink_fatten( 'INVOKE_REGION_WIN', TRANSFORM_OT_shrink_fatten={ "release_confirm": False, }, ) elif dissolve_and_intersect: bpy.ops.mesh.extrude_manifold( 'INVOKE_REGION_WIN', MESH_OT_extrude_region={ "use_dissolve_ortho_edges": True, }, TRANSFORM_OT_translate={ "orient_type": 'NORMAL', "constraint_axis": (False, False, True), "release_confirm": False, }, ) else: bpy.ops.mesh.extrude_region_move( 'INVOKE_REGION_WIN', TRANSFORM_OT_translate={ "orient_type": 'NORMAL', "constraint_axis": (False, False, True), "release_confirm": False, }, ) elif totedge == 1: bpy.ops.mesh.extrude_region_move( 'INVOKE_REGION_WIN', TRANSFORM_OT_translate={ # Don't set the constraint axis since users will expect MMB # to use the user setting, see: #61637 # "orient_type": 'NORMAL', # Not a popular choice, too restrictive for retopology. # "constraint_axis": (True, True, False), "constraint_axis": (False, False, False), "release_confirm": False, }) else: bpy.ops.mesh.extrude_region_move( 'INVOKE_REGION_WIN', TRANSFORM_OT_translate={ "release_confirm": False, }, ) # ignore return from operators above because they are 'RUNNING_MODAL', # and cause this one not to be freed. #24671. return {'FINISHED'} def execute(self, context): return VIEW3D_OT_edit_mesh_extrude_move.extrude_region(self, context, False, self.dissolve_and_intersect) def invoke(self, context, _event): return self.execute(context) class VIEW3D_OT_edit_mesh_extrude_shrink_fatten(Operator): """Extrude region together along local normals""" bl_label = "Extrude and Move on Individual Normals" bl_idname = "view3d.edit_mesh_extrude_move_shrink_fatten" @classmethod def poll(cls, context): return context.mode == 'EDIT_MESH' def execute(self, context): return VIEW3D_OT_edit_mesh_extrude_move.extrude_region(self, context, True, False) def invoke(self, context, _event): return self.execute(context) class VIEW3D_OT_edit_mesh_extrude_manifold_normal(Operator): """Extrude manifold region along normals""" bl_label = "Extrude Manifold Along Normals" bl_idname = "view3d.edit_mesh_extrude_manifold_normal" @classmethod def poll(cls, context): return context.mode == 'EDIT_MESH' def execute(self, context): from bpy_extras.object_utils import object_report_if_active_shape_key_is_locked if object_report_if_active_shape_key_is_locked(context.object, self): return {'CANCELLED'} bpy.ops.mesh.extrude_manifold( 'INVOKE_REGION_WIN', MESH_OT_extrude_region={ "use_dissolve_ortho_edges": True, }, TRANSFORM_OT_translate={ "orient_type": 'NORMAL', "constraint_axis": (False, False, True), "release_confirm": False, }, ) return {'FINISHED'} def invoke(self, context, _event): return self.execute(context) class VIEW3D_OT_transform_gizmo_set(Operator): """Set the current transform gizmo""" bl_label = "Transform Gizmo Set" bl_options = {'REGISTER', 'UNDO'} bl_idname = "view3d.transform_gizmo_set" extend: BoolProperty( name="Extend", default=False, ) type: EnumProperty( name="Type", items=( ('TRANSLATE', "Move", ""), ('ROTATE', "Rotate", ""), ('SCALE', "Scale", ""), ), options={'ENUM_FLAG'}, ) @classmethod def poll(cls, context): area = context.area return area and (area.type == 'VIEW_3D') def execute(self, context): space_data = context.space_data space_data.show_gizmo = True attrs = ("show_gizmo_object_translate", "show_gizmo_object_rotate", "show_gizmo_object_scale") attr_active = tuple( attrs[('TRANSLATE', 'ROTATE', 'SCALE').index(t)] for t in self.type ) if self.extend: for attr in attrs: if attr in attr_active: setattr(space_data, attr, True) else: for attr in attrs: setattr(space_data, attr, attr in attr_active) return {'FINISHED'} def invoke(self, context, event): if not self.properties.is_property_set("extend"): self.extend = event.shift return self.execute(context) class VIEW3D_FH_empty_image(FileHandler): bl_idname = "VIEW3D_FH_empty_image" bl_label = "Add empty image" bl_import_operator = "OBJECT_OT_empty_image_add" bl_file_extensions = bl_file_extensions_image_and_movie @classmethod def poll_drop(cls, context): if not context.space_data or context.space_data.type != 'VIEW_3D': return False rv3d = context.space_data.region_3d return rv3d.view_perspective == 'PERSP' or rv3d.view_perspective == 'ORTHO' class VIEW3D_FH_camera_background_image(FileHandler): bl_idname = "VIEW3D_FH_camera_background_image" bl_label = "Add camera background image" bl_import_operator = "VIEW3D_OT_camera_background_image_add" bl_file_extensions = bl_file_extensions_image_and_movie @classmethod def poll_drop(cls, context): if not context.space_data or context.space_data.type != 'VIEW_3D': return False rv3d = context.space_data.region_3d return rv3d.view_perspective == 'CAMERA' class VIEW3D_FH_vdb_volume(FileHandler): bl_idname = "VIEW3D_FH_vdb_volume" bl_label = "OpenVDB volume" bl_import_operator = "OBJECT_OT_volume_import" bl_file_extensions = ".vdb" @classmethod def poll_drop(cls, context): return context.space_data and context.space_data.type == 'VIEW_3D' classes = ( VIEW3D_OT_edit_mesh_extrude_individual_move, VIEW3D_OT_edit_mesh_extrude_move, VIEW3D_OT_edit_mesh_extrude_shrink_fatten, VIEW3D_OT_edit_mesh_extrude_manifold_normal, VIEW3D_OT_transform_gizmo_set, VIEW3D_FH_camera_background_image, VIEW3D_FH_empty_image, VIEW3D_FH_vdb_volume, )