Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f27222f
Added tool to rename objs in collection by collection name. Added rig…
maxivz Feb 14, 2025
36afecf
Added edit collection offset toggle operator
maxivz Feb 17, 2025
bef0ba3
First version of color objs by collection
maxivz Feb 17, 2025
6646682
Added collection tools to itools panel
maxivz Feb 18, 2025
6534878
First iteration of quick convex
maxivz Feb 18, 2025
3c173ad
Added quick convex prefix setting
maxivz Feb 18, 2025
1f97751
Added new toggles to panel
maxivz Feb 19, 2025
a5462cf
Add new pie menu for making objects
maxivz Feb 19, 2025
ce441ef
Renamed bezier empty to simple.
maxivz Feb 19, 2025
1dc352c
Style consistency pass
maxivz Feb 19, 2025
4ddb9bc
-Added collision collection function and handler
maxivz Feb 20, 2025
9d8c2f3
Fixed bug with transform options align to face normal in Blender 4.2
maxivz Mar 8, 2025
37bbf89
Added new obj orientation settings to pie menu, added plane prim
maxivz Mar 9, 2025
8376600
FIxed grease_pencil_add so it works with blender 4.5
maxivz Jul 26, 2025
21515f3
Fixed rebase cylinder for blender 4.5, removed unnecesary code
maxivz Jul 26, 2025
e1153eb
Removed unnecesary code, fix for blender 4.5
maxivz Jul 26, 2025
60a1fee
Fixed for blender 4.5
maxivz Jul 26, 2025
1bcd4b6
Updated to work witn blender 4.5, 4.4. Cleanup
maxivz Jul 26, 2025
523ff43
Updated collection ops
maxivz Jul 26, 2025
4a86137
Cleanup
maxivz Jul 26, 2025
29a7c6e
Remove operator that didnt exist anymore from menus, so it doesnt gen…
maxivz Dec 8, 2025
bb60646
Merge branch '1.5-tests' of https://github.com/maxivz/interactivetool…
maxivz Dec 8, 2025
11a8c55
Cleanup
maxivz Dec 8, 2025
316eca1
Added option to change collision prefixes in the user pannel
maxivz Dec 8, 2025
a2fbadd
Reorganized preferences menu
maxivz Dec 8, 2025
b4b86a1
Fixed quick selection to work with curves and grease pencil
maxivz Dec 8, 2025
922501a
Added option to enable duplicate mode in super smart create in object…
maxivz Dec 8, 2025
d3824af
Version bump, increased minumum version
maxivz Dec 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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": "",
Expand All @@ -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,
Expand All @@ -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)

Expand All @@ -67,6 +73,9 @@ def register():

# register_keymaps()

#Register Handlers
load_handlers()


def unregister():
from bpy.utils import unregister_class
Expand All @@ -79,6 +88,10 @@ def unregister():
for cls in reversed(classes):
unregister_class(cls)

#Unregister Handlers
unload_handlers()



if __name__ == "__main__":
register()
112 changes: 112 additions & 0 deletions op/collection_ops.py
Original file line number Diff line number Diff line change
@@ -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'}

126 changes: 126 additions & 0 deletions op/collision_ops.py
Original file line number Diff line number Diff line change
@@ -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'}
23 changes: 23 additions & 0 deletions op/handlers.py
Original file line number Diff line number Diff line change
@@ -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)

19 changes: 15 additions & 4 deletions op/mesh_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ 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':
target_mode = 'STROKE'
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()

Expand Down Expand Up @@ -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':
Expand All @@ -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")




Expand All @@ -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()

Expand Down
Loading