diff --git a/__init__.py b/__init__.py index 24b0906..44bceb0 100644 --- a/__init__.py +++ b/__init__.py @@ -1,8 +1,8 @@ import bpy -from . ui.menus import load_menus_itools, unload_menus_itools, VIEW3D_MT_object_mode_itools, VIEW3D_MT_edit_mesh_itools, VIEW3D_MT_edit_lattice_itools, VIEW3D_MT_edit_uvs_itools +from . ui.menus import load_menus_itools, unload_menus_itools, VIEW3D_MT_object_mode_itools, VIEW3D_MT_edit_mesh_itools, VIEW3D_MT_edit_lattice_itools, VIEW3D_MT_edit_uvs_itools, VIEW3D_MT_edit_outliner_itools from . ui.pies import VIEW3D_MT_PIE_SSC_Duplicate,VIEW3D_MT_PIE_SM_uv ,VIEW3D_MT_PIE_SM_looptools, VIEW3D_MT_PIE_SM_lattice, VIEW3D_MT_PIE_SSC_New_Obj,VIEW3D_MT_PIE_TransformOptions, VIEW3D_MT_PIE_SM_object, VIEW3D_MT_PIE_SM_mesh, VIEW3D_MT_PIE_SM_curve from . ui.pannels import VIEW3D_PT_Itools -#from . utils.debug import MaxivzToolsDebug_PT_Panel, DebugOp +from . ui.pie_menus.make_new import VIEW3D_MT_PIE_Make_New from . op.super_smart_create import SuperSmartCreate from . op.radial_symmetry import QuickRadialSymmetry from . op.quick_align import QuickAlign @@ -17,16 +17,20 @@ from . op.quick_lattice import QuickLattice, LatticeResolution2x2x2, LatticeResolution3x3x3, LatticeResolution4x4x4 from . op.quick_pipe import QuickPipe from . op.rebase_cylinder import RebaseCylinder +from . op.collision_ops import QuickConvexHull, CollisionCollectionUpdate from . op.uv_functions import QuickRotateUv90Pos, QuickRotateUv90Neg, SeamsFromSharps, UvsFromSharps +from . op.collection_ops import RenameObjsByCollection, EditCollectionOffset, ColorObjsByCollection from . utils.user_prefs import AddonPreferences, OBJECT_OT_addon_prefs_example, MenuPlaceholder, unregister_keymaps, get_enable_legacy_tools - +from . utils.custom_data import ToggleItoolsProperty +from . op.new_objects import AddBezierSimple +from . op.handlers import load_handlers, unload_handlers bl_info = { "name": "Interactive Tools", "author": "Maxi Vazquez, Ajfurey", "description": "Collection of context sensitive tools", - "blender": (4, 2, 0), + "blender": (4, 5, 0), "location": "View3D", - "version": (1, 4, 1), + "version": (1, 5, 0), "tracker_url": "https://github.com/maxivz/interactivetoolsblender/issues", "wiki_url": "https://maxivz.github.io/interactivetoolsblenderdocs.github.io/", "warning": "", @@ -37,7 +41,7 @@ classes = (VIEW3D_PT_Itools, VIEW3D_MT_PIE_SSC_Duplicate, VIEW3D_MT_PIE_SSC_New_Obj, RebaseCylinder, VIEW3D_MT_object_mode_itools, VIEW3D_MT_edit_mesh_itools, VIEW3D_MT_edit_lattice_itools, VIEW3D_MT_PIE_SM_object, VIEW3D_MT_PIE_SM_mesh, TransformOptionsPie, - VIEW3D_MT_edit_uvs_itools, VIEW3D_MT_PIE_TransformOptions, SuperSmartCreate, TransformModeCycle, QuickAlign, + VIEW3D_MT_edit_uvs_itools, VIEW3D_MT_PIE_TransformOptions,VIEW3D_MT_edit_outliner_itools, SuperSmartCreate, TransformModeCycle, QuickAlign, QuickRadialSymmetry,QuickPivot, QuickEditPivot, SelectionModeCycle, QuickSelectionEdge, QuickSelectionVert, QuickSelectionFace, VIEW3D_MT_PIE_SM_lattice, FlexiBezierToolsCreate, ContextSensitiveSlide, TargetWeldToggle, QuickModifierToggle, @@ -49,7 +53,9 @@ QuickRotateUv90Pos, QuickRotateUv90Neg, UvsFromSharps,QuickPipe, MenuPlaceholder, SmartModify, LatticeResolution2x2x2, SnapPresetsOp, PropEditOp, TransformPivotPointOp, - LatticeResolution3x3x3, LatticeResolution4x4x4, QuickHpLpNamer, ChildrenVisibility) + LatticeResolution3x3x3, LatticeResolution4x4x4, QuickHpLpNamer, ChildrenVisibility, + RenameObjsByCollection, EditCollectionOffset, ColorObjsByCollection, QuickConvexHull, CollisionCollectionUpdate, + ToggleItoolsProperty, VIEW3D_MT_PIE_Make_New, AddBezierSimple) legacy_classes = (SmartExtrudeModal, SmartTranslate) @@ -67,6 +73,9 @@ def register(): # register_keymaps() + #Register Handlers + load_handlers() + def unregister(): from bpy.utils import unregister_class @@ -79,6 +88,10 @@ def unregister(): for cls in reversed(classes): unregister_class(cls) + #Unregister Handlers + unload_handlers() + + if __name__ == "__main__": register() diff --git a/op/collection_ops.py b/op/collection_ops.py new file mode 100644 index 0000000..89f69cd --- /dev/null +++ b/op/collection_ops.py @@ -0,0 +1,112 @@ +import bpy +import random + +from .. utils.itools import get_collection_top_level_parent, get_selected +from .. utils.custom_data import itools_data_get +from ..utils.constants import COLLECTION_COLOR, COLLECTION_COLORS_USE_PARENT_COLOR + +def get_collection_color(collection): + """Returns the color of a collection, if it has a tag it uses that one, if it doesnt it assigns one""" + if collection is None: + return (1, 1, 1, 1) + + if itools_data_get(COLLECTION_COLORS_USE_PARENT_COLOR): + parent_collection = get_collection_top_level_parent(collection) + if parent_collection: + return get_collection_color(parent_collection) + + + if collection.color_tag != 'NONE': + color_map = { + 'COLOR_01': (1.0, 0.0, 0.0, 1.0), + 'COLOR_02': (1.0, 0.7, 0.4, 1.0), + 'COLOR_03': (1.0, 0.95, 0.5, 1.0), + 'COLOR_04': (0.48, 0.8, 0.48, 1.0), + 'COLOR_05': (0.36, 0.71, 0.91, 1.0), + 'COLOR_06': (0.55, 0.35, 0.85, 1.0), + 'COLOR_07': (0.77, 0.45, 0.72, 1.0), + 'COLOR_08': (0.47, 0.33, 0.25, 1.0), + } + + return color_map.get(collection.color_tag, 1) + + + if not collection.get(COLLECTION_COLOR): + collection[COLLECTION_COLOR] = (random.random(), random.random(), random.random(), 1) + + return collection[COLLECTION_COLOR] + +def assign_object_collection_colors(): + """Assigns viewport display colors to objects based on their collections.""" + for obj in bpy.data.objects: + if obj.type not in {'MESH', 'CURVE'}: + continue + + collection = next((col for col in bpy.data.collections if obj.name in col.objects), None) + + if collection: + color = get_collection_color(collection) + obj.color = color + +class ColorObjsByCollection(bpy.types.Operator): + bl_idname = "collection.color_objs_by_collection" + bl_label = "Color Objects By Collection" + bl_description = """Sets the color of the objects to the color of its collection. + If the collection has a color tag it will use it, if it doesnt it will generate a random one + The color is visible in Solid shading mode, with color mode set to attribute""" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + assign_object_collection_colors() + return {'FINISHED'} + + +class RenameObjsByCollection(bpy.types.Operator): + bl_idname = "collection.rename_objs_by_collection" + bl_label = "Rename Objects By Collection" + bl_description = "Renames all objects in collection to reflect the collection name" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + active_col = bpy.context.collection + for obj_num, obj in enumerate(active_col.objects): + obj.name = f"{active_col.name}_{obj_num}" + return {'FINISHED'} + + +class EditCollectionOffset(bpy.types.Operator): + bl_idname = "collection.edit_collection_offset_toggle" + bl_label = "Edit Collection Offset" + bl_description = "Spawns a locator to edit the offset of the collection" + bl_options = {'REGISTER', 'UNDO'} + + def edit_collection_offset_toggle(self, collection, context): + locator_name = collection.name + "_origin" + locator = bpy.data.objects.get(locator_name) + + selection = get_selected() + + if selection: + bpy.ops.object.select_all(action='DESELECT') + + if not locator: + if collection.objects: + locator = bpy.data.objects.new(locator_name, None) + locator.empty_display_type = 'ARROWS' + + collection.objects.link(locator) + locator.select_set(True) + locator.location = collection.instance_offset + context.view_layer.objects.active = locator + + else: + # Set the collection offset to the locator location and delete it + collection.instance_offset = locator.location + bpy.data.objects.remove(locator) + + + def execute(self, context): + active_col = bpy.context.collection + self.edit_collection_offset_toggle(active_col, context) + return {'FINISHED'} + \ No newline at end of file diff --git a/op/collision_ops.py b/op/collision_ops.py new file mode 100644 index 0000000..2f32ae6 --- /dev/null +++ b/op/collision_ops.py @@ -0,0 +1,126 @@ +import bpy +import bmesh +from ..utils.materials import get_material +from ..utils.user_prefs import get_quickconvex_prefix, get_collision_prefixes +from ..utils.constants import CONVEXHULL_MAT_COLOR, COLLISION, DESCRIPTION_DIC, COLLISION_COLLECTION_UPDATE + +def get_collision_collection(): + """Returns collision collection, if it doesnt exist it creates it and returns it""" + if COLLISION in bpy.data.collections: + return bpy.data.collections[COLLISION] + + col = bpy.data.collections.new(COLLISION) + col.color_tag = "COLOR_04" + bpy.context.scene.collection.children.link(col) + return col + +def update_global_collision_collection(): + """Adds collision objs into global collision collection""" + collision_col = get_collision_collection() + collision_prefixes = tuple(get_collision_prefixes().split(",")) + + for obj in bpy.context.scene.objects: + if not obj.name.startswith(collision_prefixes): + continue + + + if obj.name in collision_col.objects: + continue + + collision_col.objects.link(obj) + + #Remove objs from collision colection that no longer posses a proper prefix + for obj in list(collision_col.objects): + if obj.name.startswith(collision_prefixes): + continue + + collision_col.objects.unlink(obj) + collection_count = sum(1 for col in bpy.data.collections if obj.name in col.objects) + + #If its in no other collection link it to the parent scene so obj is not lost + if collection_count > 0: + bpy.context.scene.objects.link(obj) + + + +class CollisionCollectionUpdate(bpy.types.Operator): + bl_idname = "itools.collision_collection_update" + bl_label = "Collision Collection Update" + bl_description = DESCRIPTION_DIC[COLLISION_COLLECTION_UPDATE] + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + update_global_collision_collection() + return {'FINISHED'} + +class QuickConvexHull(bpy.types.Operator): + bl_idname = "mesh.quick_convex_hull" + bl_label = "Quick Convex Hull" + bl_description = "Makes a quick convex hull from selection, parents it to selected object and adds the desired prefix" + bl_options = {'REGISTER', 'UNDO'} + + def assign_collision_mat(self, obj): + # Get collision material and assign green color, create it if its missing + get_material("Collision") + mat = bpy.data.materials.get("Collision") + mat.diffuse_color = CONVEXHULL_MAT_COLOR + obj.data.materials.append(mat) + + @classmethod + def poll(cls, context): + return context.object is not None + + def execute(self, context): + edit_mode = False + hull_prefix = get_quickconvex_prefix() + og_selection = context.selected_objects + og_active = context.view_layer.objects.active + + for obj in og_selection: + if og_active.mode == 'EDIT': + edit_mode = True + bpy.ops.object.mode_set(mode="OBJECT") + + + depsgraph = bpy.context.evaluated_depsgraph_get() + bm = bmesh.new() + bm.from_object(obj, depsgraph) + + selected_verts = [] + + if edit_mode: + selected_verts = [v for v in bm.verts if v.select] + + else: + selected_verts = [v for v in bm.verts] + + if not selected_verts: + print("ERROR: No elements selected, please make a selection") + return {'FINISHED'} + + + op_data = bmesh.ops.convex_hull(bm, input=selected_verts, use_existing_faces=True) + delete_verts = [v for v in bm.verts if v not in selected_verts] + delete_verts += op_data["geom_interior"] + op_data["geom_unused"] + + if delete_verts: + bmesh.ops.delete(bm, geom=delete_verts) + + # Finish up, write the bmesh into a new mesh + new_bmesh = bpy.data.meshes.new(f"{hull_prefix}_{og_selection[0].name}") + bm.to_mesh(new_bmesh) + bm.free() + + convex_hull = bpy.data.objects.new(f"{hull_prefix}_{og_selection[0].name}", new_bmesh) + convex_hull.parent = obj + + for col in og_active.users_collection: + if convex_hull.name not in col.objects: + col.objects.link(convex_hull) + + self.assign_collision_mat(convex_hull) + + if edit_mode: + bpy.ops.object.mode_set(mode="EDIT") + + return {'FINISHED'} \ No newline at end of file diff --git a/op/handlers.py b/op/handlers.py new file mode 100644 index 0000000..5e441c3 --- /dev/null +++ b/op/handlers.py @@ -0,0 +1,23 @@ +import bpy +from ..utils.constants import COLLISION_COLORS_UPDATE, COLLISION_COLLECTION_UPDATE +from ..utils.custom_data import itools_data_get +from .collection_ops import assign_object_collection_colors +from .collision_ops import update_global_collision_collection + +def update_collection_colors(scene): + if itools_data_get(COLLISION_COLORS_UPDATE): + assign_object_collection_colors() + +def update_collision_collection(scene): + if itools_data_get(COLLISION_COLLECTION_UPDATE): + update_global_collision_collection() + +def load_handlers(): + bpy.app.handlers.depsgraph_update_post.append(update_collection_colors) + bpy.app.handlers.depsgraph_update_post.append(update_collision_collection) + + +def unload_handlers(): + bpy.app.handlers.depsgraph_update_post.remove(update_collection_colors) + bpy.app.handlers.depsgraph_update_post.remove(update_collision_collection) + diff --git a/op/mesh_modes.py b/op/mesh_modes.py index 18dc7e5..820403b 100644 --- a/op/mesh_modes.py +++ b/op/mesh_modes.py @@ -20,7 +20,7 @@ def quick_selection(target_mode, safe_mode=False): if current_object != None: #Convert types from Mesh to Gpencil space - if current_object.type == 'GPENCIL': + if current_object.type == 'GREASEPENCIL': if target_mode == 'VERT': target_mode = 'POINT' elif target_mode == 'EDGE': @@ -28,6 +28,10 @@ def quick_selection(target_mode, safe_mode=False): elif target_mode == 'FACE': target_mode = 'SEGMENT' + #Convert types from Mesh to Curve space + if current_object.type == 'CURVE': + target_mode = "EDIT_CURVE" + other_modes = itools.list_difference(['VERT', 'EDGE', 'FACE', 'POINT', 'STROKE', 'SEGMENT', 'OBJECT'], [target_mode]) sticky = get_enable_sticky_selection() @@ -62,8 +66,8 @@ def quick_selection(target_mode, safe_mode=False): store_sel_data(current_mode) itools.set_mode('OBJECT') - if current_object.type == 'GPENCIL': - bpy.ops.object.mode_set(mode="EDIT_GPENCIL") + if current_object.type == 'GREASEPENCIL': + bpy.ops.object.mode_set(mode="EDIT") if current_mode in other_modes: if target_mode == 'POINT': @@ -75,6 +79,14 @@ def quick_selection(target_mode, safe_mode=False): elif current_mode == target_mode: bpy.ops.object.mode_set(mode="OBJECT") + + if current_object.type == 'CURVE': + if current_mode == "OBJECT": + bpy.ops.object.mode_set(mode="EDIT") + + else: + bpy.ops.object.mode_set(mode="OBJECT") + @@ -86,7 +98,6 @@ class SelectionModeCycle(bpy.types.Operator): def execute(self, context): mode = itools.get_mode() - print(mode) if mode == 'OBJECT': bpy.ops.object.editmode_toggle() diff --git a/op/misc.py b/op/misc.py index 98352c8..ea3a3e5 100644 --- a/op/misc.py +++ b/op/misc.py @@ -375,16 +375,15 @@ def set_preset(self, context): bpy.context.scene.tool_settings.use_snap_align_rotation = False elif self.mode == 4: - bpy.context.scene.tool_settings.snap_elements = {'FACE'} + bpy.context.scene.tool_settings.snap_elements_individual = {'FACE_PROJECT'} bpy.context.scene.tool_settings.snap_target = 'CENTER' bpy.context.scene.tool_settings.use_snap_align_rotation = True - bpy.context.scene.tool_settings.use_snap_project = True + elif self.mode == 5: bpy.context.scene.tool_settings.snap_elements = {'EDGE_MIDPOINT'} bpy.context.scene.tool_settings.snap_target = 'MEDIAN' bpy.context.scene.tool_settings.use_snap_align_rotation = False - bpy.context.scene.tool_settings.use_snap_project = False def execute(self, context): self.set_preset(context) diff --git a/op/new_objects.py b/op/new_objects.py new file mode 100644 index 0000000..08e3c09 --- /dev/null +++ b/op/new_objects.py @@ -0,0 +1,39 @@ +import bpy + +class AddBezierSimple(bpy.types.Operator): + bl_idname = "curve.add_bezier_simple" + bl_label = "Add Bezier Simple" + bl_description = "Adds Simple Bezier Curve and sets its as active" + bl_options = {'REGISTER', 'UNDO'} + + mode: bpy.props.StringProperty(default="Simple") + @classmethod + def poll(cls, context): + return context.mode == "OBJECT" + + def execute(self, context): + curve_data = bpy.data.curves.new('BezierCurve', 'CURVE') + curve_data.dimensions = '3D' + + if self.mode == "Simple": + spline = curve_data.splines.new('BEZIER') + spline.bezier_points.add(1) + spline.bezier_points[0].co = (0, 0, 0) + spline.bezier_points[1].co = (0, 0, 1) + + curve_object = bpy.data.objects.new('BezierCurve', curve_data) + curve_object.location = context.scene.cursor.location + + # Link the object to the scene and set active + context.collection.objects.link(curve_object) + context.view_layer.objects.active = curve_object + curve_object.select_set(True) + + + if self.mode == "Draw": + if context.mode == "OBJECT": + bpy.ops.object.mode_set(mode = 'EDIT') + bpy.ops.wm.tool_set_by_id(name="builtin.draw") + context.scene.tool_settings.curve_paint_settings.depth_mode = "SURFACE" + + return{'FINISHED'} diff --git a/op/pivot.py b/op/pivot.py index 19c01c2..867da99 100644 --- a/op/pivot.py +++ b/op/pivot.py @@ -38,7 +38,6 @@ def create_pivot(self, context, obj): pivot = bpy.context.active_object pivot.name = obj.name + ".PivotHelper" pivot.location = obj.location - print("Pivot") def get_pivot(self, context, obj): pivot = obj.name + ".PivotHelper" diff --git a/op/quick_lattice.py b/op/quick_lattice.py index 571c37f..7b6da7a 100644 --- a/op/quick_lattice.py +++ b/op/quick_lattice.py @@ -90,7 +90,6 @@ def setup_lattice(self, context, selection): # Make sure no axis is 0 as this caused the bug where you couldnt move the lattice. for axis in range(3): - print(axis) if dimensions[axis] == 0: dimensions[axis] = 0.001 diff --git a/op/quick_pipe.py b/op/quick_pipe.py index ad3674d..565249c 100644 --- a/op/quick_pipe.py +++ b/op/quick_pipe.py @@ -143,19 +143,6 @@ def restore_settings(self, context, selection): bpy.context.object.data.bevel_resolution = self.original_resolution bpy.context.object.data.bevel_depth = self.original_depth - def __init__(self): - print("Start") - - def __del__(self): - print("End") - - """ - @classmethod - def poll(cls, context): - return ((context.mode == 'OBJECT' and bpy.context.object.modifiers.find("Cylindrical Sides") > -1) or - bpy.context.mode == 'EDIT_MESH') - """ - def execute(self, context): #self.sync_ui_settings() self.calculate_depth(context, bpy.data.objects[self.selection]) diff --git a/op/radial_symmetry.py b/op/radial_symmetry.py index c29069c..8a3184d 100644 --- a/op/radial_symmetry.py +++ b/op/radial_symmetry.py @@ -222,12 +222,6 @@ def restore_settings(self, context, selection): elif self.original_sym_axis == 2: bpy.data.objects[self.offset_obj].rotation_euler = (0, 0, math.radians(360 / self.original_sym_count)) - def __init__(self): - print("Start") - - def __del__(self): - print("End") - @classmethod def poll(cls, context): return context.mode == 'OBJECT' and len(context.selected_objects) > 0 diff --git a/op/rebase_cylinder.py b/op/rebase_cylinder.py index fa59972..c481038 100644 --- a/op/rebase_cylinder.py +++ b/op/rebase_cylinder.py @@ -213,12 +213,6 @@ def restore_settings(self, context, selection): mod.merge_threshold = self.original_merge_distance - def __init__(self): - print("Start") - - def __del__(self): - print("End") - @classmethod def poll(cls, context): if bpy.context.object != None: diff --git a/op/smart_extrude.py b/op/smart_extrude.py index 1b0be39..1361306 100644 --- a/op/smart_extrude.py +++ b/op/smart_extrude.py @@ -72,14 +72,11 @@ def context_sensitive_extend(self, context): bpy.ops.curve.extrude_move(CURVE_OT_extrude={"mode": 'TRANSLATION'}, TRANSFORM_OT_translate={"value": (0, 0, 0)}) - def __init__(self): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.initial_mouse_pos = Vector((0, 0, 0)) self.translation_accumulator = Vector((0, 0, 0)) self.initial_pos = Vector((0, 0, 0)) - print("Start") - - def __del__(self): - print("End") def execute(self, context): return {'FINISHED'} diff --git a/op/smart_transform.py b/op/smart_transform.py index a4640e2..f138791 100644 --- a/op/smart_transform.py +++ b/op/smart_transform.py @@ -147,14 +147,11 @@ def calculate_translation(self, context, event): bpy.ops.transform.translate(value=translation, orient_type='GLOBAL') return True - def __init__(self): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) self.initial_mouse_pos = Vector((0, 0, 0)) self.translation_accumulator = Vector((0, 0, 0)) self.initial_pos = Vector((0, 0, 0)) - print("Start") - - def __del__(self): - print("End") def execute(self, context): return {'FINISHED'} diff --git a/op/super_smart_create.py b/op/super_smart_create.py index 50a952b..a44e786 100644 --- a/op/super_smart_create.py +++ b/op/super_smart_create.py @@ -1,7 +1,7 @@ import bpy from ..utils import itools as itools from ..utils import mesh as mesh -from ..utils.user_prefs import get_f2_active, get_ssc_switch_modes +from ..utils.user_prefs import get_f2_active, get_ssc_switch_modes, get_ssc_duplicate_pie_enable class SuperSmartCreate(bpy.types.Operator): bl_idname = "mesh.super_smart_create" @@ -64,11 +64,11 @@ def super_smart_create(self): mode = itools.get_mode() if mode == 'OBJECT': - if len(itools.get_selected()) > 0: + if len(itools.get_selected()) > 0 and get_ssc_duplicate_pie_enable(): bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_SSC_Duplicate") else: - bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_SSC_New_Obj") + bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_Make_New") # if Vertex is selected elif mode == 'VERT': diff --git a/op/uv_functions.py b/op/uv_functions.py index 1a924b1..eec1294 100644 --- a/op/uv_functions.py +++ b/op/uv_functions.py @@ -39,8 +39,6 @@ class QuickRotateUv90Pos(bpy.types.Operator): bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - original_pos = selected_uv_verts_pos() - print(original_pos) bpy.ops.transform.rotate(value=math.radians(90), orient_axis='Z') new_pos = selected_uv_verts_pos() return{'FINISHED'} diff --git a/ui/menus.py b/ui/menus.py index d4472e7..189500b 100644 --- a/ui/menus.py +++ b/ui/menus.py @@ -15,7 +15,6 @@ def draw(self, context): layout.separator() layout.operator("mesh.quick_pivot", text="Quick Origin") layout.operator("mesh.simple_edit_pivot", text="Edit Origin") - layout.operator("mesh.transform_orientation_pie_pie", text="Quick Transform Orientation") layout.operator('mesh.quick_align', text="Quick Align") layout.operator('mesh.quick_lattice', text="Quick Lattice") layout.operator('mesh.rebase_cylinder', text="Edit Rebased Cylinder") @@ -44,7 +43,6 @@ def draw(self, context): layout.separator() layout.operator("mesh.quick_pivot", text="Quick Origin") - layout.operator("mesh.transform_orientation_pie_pie", text="Quick Transform Orientation") layout.operator('mesh.quick_pipe', text="Quick Pipe") layout.operator('mesh.quick_lattice', text="Quick Lattice") layout.operator('mesh.rebase_cylinder', text="Rebase Cylinder") @@ -77,6 +75,19 @@ def draw(self, context): layout.operator("mesh.smart_modify", text="Smart Modify") +class VIEW3D_MT_edit_outliner_itools(bpy.types.Menu): + bl_label = "Interactive Tools" + + def draw(self, context): + layout = self.layout + layout.operator('collection.rename_objs_by_collection', text="Rename Objs by Collection Name") + layout.operator('collection.color_objs_by_collection', text="Color Objs by Collection Name") + layout.operator('collection.edit_collection_offset_toggle', text="Edit Collection Offset Toggle") + + + + + def menu_object_mode_itools(self, context): self.layout.menu("VIEW3D_MT_object_mode_itools") self.layout.separator() @@ -96,12 +107,19 @@ def menu_edit_uvs_itools(self, context): self.layout.menu("VIEW3D_MT_edit_uvs_itools") self.layout.separator() +def menu_edit_outliner_itools(self, context): + bl_label = "Interactive Tools" + + self.layout.menu("VIEW3D_MT_edit_outliner_itools") + self.layout.separator() + def load_menus_itools(): bpy.types.VIEW3D_MT_object_context_menu.prepend(menu_object_mode_itools) bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_edit_mesh_itools) bpy.types.VIEW3D_MT_edit_lattice_context_menu.prepend(menu_edit_lattice_itools) bpy.types.IMAGE_MT_uvs_context_menu.prepend(menu_edit_uvs_itools) + bpy.types.OUTLINER_MT_collection.prepend(menu_edit_outliner_itools) def unload_menus_itools(): @@ -109,3 +127,5 @@ def unload_menus_itools(): bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_edit_mesh_itools) bpy.types.VIEW3D_MT_edit_lattice_context_menu.remove(menu_edit_lattice_itools) bpy.types.IMAGE_MT_uvs_context_menu.remove(menu_edit_uvs_itools) + bpy.types.OUTLINER_MT_collection.remove(menu_edit_outliner_itools) + diff --git a/ui/pannels.py b/ui/pannels.py index 6326816..331a375 100644 --- a/ui/pannels.py +++ b/ui/pannels.py @@ -1,7 +1,19 @@ import bpy -from bpy.utils import register_class, unregister_class from ..utils.user_prefs import get_enable_legacy_tools +from ..utils.constants import COLLISION_COLORS_UPDATE, COLLECTION_COLORS_USE_PARENT_COLOR, DESCRIPTION_DIC, COLLISION_COLLECTION_UPDATE +from ..utils.custom_data import itools_data_get +def icon_toggle(row, operator_name, icon_name, constant): + depress = False + if itools_data_get(constant): + depress = True + op = row.operator(operator_name, text="", icon = icon_name, depress = depress) + op.prop_name = constant + + if constant in DESCRIPTION_DIC: + op.description = f"{DESCRIPTION_DIC[constant]} \nCLICK: ON/OFF" + + return op class VIEW3D_PT_Itools(bpy.types.Panel): bl_idname = "VIEW3D_PT_Itools" @@ -61,6 +73,32 @@ def draw(self, context): row = layout.row(align=True) row.operator('mesh.quick_hplp_namer', text="Quick Hp Lp Namer") + + + layout.label(text="Collections") + row = layout.row() + row.operator('collection.edit_collection_offset_toggle', text="Edit Collection Offset") + row = layout.row() + row.operator('collection.rename_objs_by_collection', text="Rename Objects By Collection") + + #Color Object By Collection row + row = layout.row(align = True) + row.operator('collection.color_objs_by_collection', text="Color Objects By Collection") + icon_toggle(row, "itools.toggle_property", "FILE_REFRESH", COLLISION_COLORS_UPDATE) + icon_toggle(row, "itools.toggle_property", "ORIENTATION_PARENT", COLLECTION_COLORS_USE_PARENT_COLOR) + + + + layout.label(text="Collisions") + row = layout.row(align=True) + row.operator('mesh.quick_convex_hull', text="Quick Convex Hull") + row = layout.row(align=True) + row.operator('itools.collision_collection_update', text="Collision Collection Update") + icon_toggle(row, "itools.toggle_property", "FILE_REFRESH", COLLISION_COLLECTION_UPDATE) + """BRUSH_DATA""" + + + layout.label(text="Pie Menus") row = layout.row() row.operator('mesh.smart_modify', text="Smart Modify Pie") diff --git a/ui/pie_menus/make_new.py b/ui/pie_menus/make_new.py new file mode 100644 index 0000000..8da6ded --- /dev/null +++ b/ui/pie_menus/make_new.py @@ -0,0 +1,100 @@ +import bpy +from bpy.types import Menu + +NEW_OBJ_ALIGNMENT = "itools_new_obj_alignment" + +bpy.types.Scene.itools_new_obj_alignment = bpy.props.EnumProperty( + items=[ + ('WORLD', "World", "First option"), + ('CURSOR', "Cursor", "Second option"), + ('VIEW', "View", "Third option") + ], + name="Dropdown Setting", + description="Choose an option" +) + +class VIEW3D_MT_PIE_Make_New(Menu): + bl_label = "New" + + def draw(self, context): + layout = self.layout + pie = layout.menu_pie() + + #Get orientation Setting + + + # 1 - LEFT + submenu = pie.column() + container = submenu.box() + column = container.column() + row = column.row(align=True) + + row.operator("object.empty_add", text="Empty", icon="OUTLINER_OB_EMPTY").type = 'ARROWS' + row = column.row(align=True) + row.operator("object.grease_pencil_add", text="Gpencil", icon="OUTLINER_OB_GREASEPENCIL").type = 'EMPTY' + + row = column.row(align=True) + row.operator("object.camera_add", text="Camera", icon="OUTLINER_OB_CAMERA") + + row = column.row(align=True) + row.operator("object.light_add", text="Light", icon="OUTLINER_OB_LIGHT").type = 'POINT' + + # 2 - RIGHT + submenu = pie.column() + container = submenu.box() + column = container.column() + + obj_align = context.scene.itools_new_obj_alignment + + row = column.row(align=True) + row.operator("mesh.primitive_plane_add", text="Plane", icon="MESH_PLANE").align = obj_align + + + row = column.row(align=True) + row.operator("mesh.primitive_cube_add", text="Cube", icon="MESH_CUBE").align = obj_align + row.operator("wm.tool_set_by_id", text="", icon="GREASEPENCIL").name = "builtin.primitive_cube_add" + + + row = column.row(align=True) + row.operator("mesh.primitive_cylinder_add", text="Cylinder", icon="MESH_CYLINDER").align = obj_align + row.operator("wm.tool_set_by_id", text="", icon="GREASEPENCIL").name = "builtin.primitive_cylinder_add" + + + row = column.row(align=True) + row.operator("mesh.primitive_uv_sphere_add", text="Sphere", icon="MESH_UVSPHERE").align = obj_align + row.operator("wm.tool_set_by_id", text="", icon="GREASEPENCIL").name = "builtin.primitive_uv_sphere_add" + + + row = column.row(align=True) + row.operator("curve.add_bezier_simple", text="Curve", icon="IPO_EASE_IN").mode = "Simple" + row.emboss = "PULLDOWN_MENU" + row.operator("curve.add_bezier_simple", text="", icon="GREASEPENCIL").mode = "Draw" + + row = column.row(align=True) + + row.prop(context.scene, NEW_OBJ_ALIGNMENT, text="Align") + + + + # 3 - BOTTOM + + submenu = pie.column() + column = submenu.column() + + has_collections = bool(bpy.data.collections) + column.enabled = has_collections + + + column.operator_context = 'INVOKE_REGION_WIN' + column.operator( + "object.collection_instance_add", + text="Collection Instance..." if has_collections else "No Collections to Instance", + icon='OUTLINER_OB_GROUP_INSTANCE', + ) + + # 4 - TOP + + + + + diff --git a/ui/pies.py b/ui/pies.py index 0ce168f..e1de364 100644 --- a/ui/pies.py +++ b/ui/pies.py @@ -135,6 +135,8 @@ def draw(self, context): row.operator("object.move_to_collection", text="Move To Collection", icon = "DECORATE_DRIVER") row = column.row(align=False) row.operator("object.link_to_collection", text="Link To Collection", icon = "DECORATE_LINKED") + row = column.row(align=False) + row.operator("collection.edit_collection_offset_toggle", text="Edit Collection Offset", icon = "EMPTY_DATA") # 6 - RIGHT submenu = pie.column() @@ -463,9 +465,8 @@ def draw(self, context): pie.operator("mesh.snap_presets_op", text="Vert Center", icon="SNAP_VERTEX").mode = 2 # 2 - BOTTOM - if(bpy.context.scene.tool_settings.snap_elements == {'FACE'} and - bpy.context.scene.tool_settings.use_snap_align_rotation == True and - bpy.context.scene.tool_settings.use_snap_project == True): + if(bpy.context.scene.tool_settings.snap_elements == {'FACE_PROJECT'} and + bpy.context.scene.tool_settings.use_snap_align_rotation == True): pie.operator("mesh.snap_presets_op", text="Face Normal", icon="SNAP_FACE",depress=True).mode = 4 else: pie.operator("mesh.snap_presets_op", text="Face Normal", icon="SNAP_FACE").mode = 4 diff --git a/utils/constants.py b/utils/constants.py new file mode 100644 index 0000000..629a6d9 --- /dev/null +++ b/utils/constants.py @@ -0,0 +1,13 @@ +COLLISION = "Collision" +COLLISION_COLORS_UPDATE = "collision_colors_update" +COLLECTION_COLORS_USE_PARENT_COLOR = "collection_colors_use_parent_color" +COLLISION_COLLECTION_UPDATE = "collision_collection_update" +COLLECTION_COLOR = "Collection Color" #TODO: Make this into an option for the tool +CONVEXHULL_MAT_COLOR = (0, 1, 0, 1) + + +DESCRIPTION_DIC ={ + COLLISION_COLORS_UPDATE: "Automatically update collision colors", + COLLECTION_COLORS_USE_PARENT_COLOR: "Use top level parent to set the color of the collections", + COLLISION_COLLECTION_UPDATE: "Automatically add collision objects to a global collision collection" +} \ No newline at end of file diff --git a/utils/custom_data.py b/utils/custom_data.py new file mode 100644 index 0000000..8631aba --- /dev/null +++ b/utils/custom_data.py @@ -0,0 +1,45 @@ +import bpy + +def get_itools_data_obj(): + obj_name = "Itools_data_obj" + # Check if the object exists, make it if it doesnt + if obj_name not in bpy.data.objects: + obj = bpy.data.objects.new(obj_name, None) + obj.use_fake_user = True + + return bpy.data.objects[obj_name] + +def itools_data_get(data_key): + """Looks for itools data on data obj, if it doesnt find it returns nothing""" + itools_data_obj = get_itools_data_obj() + + return itools_data_obj.get(data_key, None) + +def itools_data_set(data_key, value): + """Sets data in itools data obj""" + itools_data_obj = get_itools_data_obj() + itools_data_obj[data_key] = value + itools_data_obj.update_tag() + +class ToggleItoolsProperty(bpy.types.Operator): + """Toggles target property""" + bl_idname = "itools.toggle_property" + bl_label = "" + + prop_name: bpy.props.StringProperty() + description: bpy.props.StringProperty(default="Toggles Property") + + @classmethod + def description(cls, context, properties): + return properties.description + + def execute(self, context): + prop_value = itools_data_get(self.prop_name) + print(prop_value) + if prop_value: + itools_data_set(self.prop_name, not prop_value) + + else: + itools_data_set(self.prop_name, True) + + return {'FINISHED'} \ No newline at end of file diff --git a/utils/debug.py b/utils/debug.py index 58204c2..81e9a66 100644 --- a/utils/debug.py +++ b/utils/debug.py @@ -40,12 +40,6 @@ class DebugOpModal(bpy.types.Operator): mode = 0 - def __init__(self): - print("Start") - - def __del__(self): - print("End") - def execute(self, context): context.object.location.x = self.value / 100.0 return {'FINISHED'} diff --git a/utils/itools.py b/utils/itools.py index 3003f48..0bb00fe 100644 --- a/utils/itools.py +++ b/utils/itools.py @@ -41,7 +41,7 @@ def get_mode(): elif selection_mode[2]: return 'FACE' - if mode == 'EDIT_GPENCIL': + if mode == 'EDIT_GREASE_PENCIL': return bpy.context.scene.tool_settings.gpencil_selectmode_edit return mode @@ -155,7 +155,6 @@ def active_get(item=True): # Sets active object based on name def active_set(obj, item=True): if item: - print(obj) bpy.context.view_layer.objects.active = obj else: bpy.context.view_layer.objects.active = bpy.data.objects[obj] @@ -304,9 +303,7 @@ def convert_selection(selection, to): def update_indexes(mode=''): bm = get_bmesh() if not mode: - print("Try to get mode") mode = get_mode() - print(mode) if 'VERT' or 'ALL' in mode: bm.verts.index_update() @@ -334,3 +331,27 @@ def get_children(obj_name): if ob.parent.name == obj_name: children.append(ob) return children + +def get_collection_top_level_parent(collection): + """Finds the top-level parent of a collection by checking all collections in the scene.""" + parent_level = [] + for parent_collection in bpy.data.collections: + children_recursive = parent_collection.children_recursive + if collection in children_recursive: + parent_level.append((parent_collection, len(children_recursive))) + + if parent_level: + return max(parent_level, key=lambda p: p[1])[0] + + return None + +def duplicate_object(target_obj,linked_data = False): + # Duplicate the object and mesh + new_obj = target_obj.copy() + if not linked_data: + new_obj.data = target_obj.data.copy() + bpy.context.collection.objects.link(new_obj) + + new_obj.matrix_world = target_obj.matrix_world.copy() + + return new_obj \ No newline at end of file diff --git a/utils/materials.py b/utils/materials.py new file mode 100644 index 0000000..97a18c6 --- /dev/null +++ b/utils/materials.py @@ -0,0 +1,8 @@ +import bpy + +def get_material(material_name): + """Get material by material name, if it doesnt exist create it""" + mat = bpy.data.materials.get(material_name) + if not mat: + mat = bpy.data.materials.new(name=material_name) + return mat \ No newline at end of file diff --git a/utils/user_prefs.py b/utils/user_prefs.py index a6927d5..4793936 100644 --- a/utils/user_prefs.py +++ b/utils/user_prefs.py @@ -149,6 +149,10 @@ def get_ssc_switch_modes(): prefs = get_addon_preferences() return prefs.ssc_switch_modes +def get_ssc_duplicate_pie_enable(): + prefs = get_addon_preferences() + return prefs.scc_duplicate_pie_enable + def get_ssc_qblocker_integration(): prefs = get_addon_preferences() @@ -194,6 +198,13 @@ def get_quickhplp_hp_suffix(): prefs = get_addon_preferences() return prefs.quickhplp_hp_suffix +def get_quickconvex_prefix(): + prefs = get_addon_preferences() + return prefs.quickconvex_prefix + +def get_collision_prefixes(): + prefs = get_addon_preferences() + return prefs.collision_prefixes def get_enable_wireshaded_cs(): prefs = get_addon_preferences() @@ -278,6 +289,10 @@ class AddonPreferences(AddonPreferences): ssc_bezierutilities_integration: BoolProperty(name="Super Smart Create Bezier Utilities Integration", description="Use Flexi Bezier Tool for spline creation, needs Beier Utilities to be used", default=False) + + scc_duplicate_pie_enable: BoolProperty(name="Super Smart Duplicate Pie Enable", + description="Enables the duplicate pie when in object mode when at least an object is selected", + default=False) enable_sticky_selection: BoolProperty(name="Selection Sticky Mode", description="Enables Sticky Selection when using Quick Select Modes and Selection Cycle", @@ -306,6 +321,14 @@ class AddonPreferences(AddonPreferences): quickhplp_hp_suffix: StringProperty(name="High Poly suffix", description="Suffix to use for High Poly Meshes", default="_high") + + quickconvex_prefix: StringProperty(name="Quick Convex Hull Prefix", + description="Prefix to use for new Quick Convex Hull mesh naming", + default="UCX") + + collision_prefixes: StringProperty(name="Collision Prefixes", + description="Prefixes to use when adding objects to the collision collection. Separate the prefixes with a comma ',' and leave no space between them", + default="UCX,UBX,USP") enable_wireshaded_cs: BoolProperty(name="Wireframe / Shaded Context Sensitive Mode", description="Enables context sensitive mode for the Wireframe / Shaded Tool", @@ -359,7 +382,9 @@ def draw_general(self, context): row = box.row(align=True) row.prop(self, "ssc_switch_modes", toggle=False) - + row = box.row(align=True) + row.prop(self, "scc_duplicate_pie_enable", toggle=False) + if qblocker_active: row = box.row(align=True) row.prop(self, "ssc_qblocker_integration", toggle=True) @@ -410,6 +435,16 @@ def draw_general(self, context): row = box.row(align=True) row.prop(self, "quickhplp_hp_suffix", toggle=False) + #Quick Convex Hull + box = layout.box() + row = box.row(align=True) + row.label(text="Collision:") + row = box.row(align=True) + row.prop(self, "quickconvex_prefix", toggle=False) + + row = box.row(align=True) + row.prop(self, "collision_prefixes", toggle=False) + #Other box = layout.box() row = box.row(align=True) @@ -425,6 +460,7 @@ def draw_general(self, context): row = box.row(align=True) row.prop(self, "transform_mode_cycle_cyclic", toggle=False) + row = box.row(align=True) row.prop(self, "enable_legacy_tools", toggle=False)