update addons (for 5.2)

This commit is contained in:
Nathan
2026-07-14 14:44:58 -06:00
parent a232ea1c9c
commit e3310263f3
246 changed files with 21097 additions and 9607 deletions
@@ -0,0 +1,34 @@
if "bpy" in locals():
import importlib
for mod in [icons,
lists,
menus,
panels,
]:
importlib.reload(mod)
else:
import bpy
from . import (
icons,
lists,
menus,
panels,
)
#### ------------------------------ REGISTRATION ------------------------------ ####
modules = (
icons,
lists,
menus,
panels,
)
def register():
for module in modules:
module.register()
def unregister():
for module in reversed(modules):
module.unregister()
@@ -0,0 +1,96 @@
import bpy
from contextlib import contextmanager
from ..functions.canvas import (
is_canvas,
)
from ..functions.modifier import (
enumerate_boolean_modifiers,
is_boolean_modifier,
)
#### ------------------------------ FUNCTIONS ------------------------------ ####
def get_modifier_from_list_index(obj, index: int):
"""
Returns the modifier of an object based on Cutters list index.
Filters out non-Boolean modifiers to leave a list that matches Cutters one in length.
"""
# Create a list of only Boolean modifiers.
boolean_modifiers = []
for mod in obj.modifiers:
if is_boolean_modifier(mod):
boolean_modifiers.append(mod)
if 0 <= index < len(boolean_modifiers):
return boolean_modifiers[index]
return None
@contextmanager
def preserve_list_index(prop_owner, index_prop: str):
"""Preserves index of active item in UI list."""
index = getattr(prop_owner, index_prop, 0)
stored_index = index
try:
yield
finally:
new_index = stored_index - 1
if new_index < 0:
new_index = 0
setattr(prop_owner, index_prop, new_index)
#### ------------------------------ /ui/ ------------------------------ ####
def boolean_operators_menu(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_DEFAULT'
col = layout.column(align=True)
col.label(text="Auto Boolean")
col.operator("object.boolean_auto_difference", text="Difference", icon='SELECT_SUBTRACT')
col.operator("object.boolean_auto_union", text="Union", icon='SELECT_EXTEND')
col.operator("object.boolean_auto_intersect", text="Intersect", icon='SELECT_INTERSECT')
col.operator("object.boolean_auto_slice", text="Slice", icon='SELECT_DIFFERENCE')
col.separator()
col.label(text="Brush Boolean")
col.operator("object.boolean_brush_difference", text="Difference", icon='SELECT_SUBTRACT')
col.operator("object.boolean_brush_union", text="Union", icon='SELECT_EXTEND')
col.operator("object.boolean_brush_intersect", text="Intersect", icon='SELECT_INTERSECT')
col.operator("object.boolean_brush_slice", text="Slice", icon='SELECT_DIFFERENCE')
def boolean_extras_menu(self, context, cutter_only=False):
layout = self.layout
layout.operator_context = 'INVOKE_DEFAULT'
col = layout.column(align=True)
obj = context.active_object
if not obj:
return
# Canvas operators
if is_canvas(obj) and not cutter_only:
col.separator()
col.operator("object.boolean_toggle_all", text="Toggle All Cuters")
col.operator("object.boolean_apply_all", text="Apply All Cutters")
col.operator("object.boolean_remove_all", text="Remove All Cutters")
# Cutter operators
if obj.booleans.cutter:
if not cutter_only:
col.separator()
col.operator("object.boolean_toggle_cutter", text="Toggle Cutter").method='ALL'
col.operator("object.boolean_apply_cutter", text="Apply Cutter").method='ALL'
col.operator("object.boolean_remove_cutter", text="Remove Cutter").method='ALL'
@@ -0,0 +1,46 @@
import bpy
import os
import bpy.utils.previews
#### ------------------------------ FUNCTIONS ------------------------------ ####
def get_custom_icon(icon_name: str):
"""Returns the ID of a custom icon registered by add-on."""
svg_icons = preview_collections["main"]
if not svg_icons:
return 0
icon = svg_icons[icon_name].icon_id
if not icon:
return 0
return icon
#### ------------------------------ REGISTRATION ------------------------------ ####
preview_collections = {}
svg_icons_dir = os.path.join(os.path.dirname(__file__), "svg")
icons = {
"MEASURE": "measure.svg",
"CPU": "cpu.svg",
}
def register():
svg_icons = bpy.utils.previews.new()
for key, file in icons.items():
svg_icons.load(key, os.path.join(svg_icons_dir, file), 'IMAGE')
preview_collections["main"] = svg_icons
def unregister():
for pcoll in preview_collections.values():
bpy.utils.previews.remove(pcoll)
preview_collections.clear()
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" fill="#fff" viewBox="0 0 24 24"><g fill-rule="evenodd"><path d="M8.75 8a.75.75 0 0 0-.75.75v6.5c0 .414.336.75.75.75h6.5a.75.75 0 0 0 .75-.75v-6.5a.75.75 0 0 0-.75-.75h-6.5zm.75 6.5v-5h5v5h-5z"/><path d="M15.25 1a.75.75 0 0 1 .75.75V4h2.25c.966 0 1.75.784 1.75 1.75V8h2.25a.75.75 0 0 1 0 1.5H20v5h2.25a.75.75 0 0 1 0 1.5H20v2.25A1.75 1.75 0 0 1 18.25 20H16v2.25a.75.75 0 0 1-1.5 0V20h-5v2.25a.75.75 0 0 1-1.5 0V20H5.75A1.75 1.75 0 0 1 4 18.25V16H1.75a.75.75 0 0 1 0-1.5H4v-5H1.75a.75.75 0 0 1 0-1.5H4V5.75C4 4.784 4.784 4 5.75 4H8V1.75a.75.75 0 0 1 1.5 0V4h5V1.75a.75.75 0 0 1 .75-.75zm3 17.5a.25.25 0 0 0 .25-.25V5.75a.25.25 0 0 0-.25-.25H5.75a.25.25 0 0 0-.25.25v12.5c0 .138.112.25.25.25h12.5z"/></g></svg>

After

Width:  |  Height:  |  Size: 772 B

@@ -0,0 +1,7 @@
<?xml version="1.0"?>
<svg width="23.999999999999996" height="23.999999999999996" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g class="layer">
<title>Layer 1</title>
<path clip-rule="evenodd" d="m13.18,4.48l3.62,3.65a0.75,0.75 0 0 1 -1.16,1.09l-3.52,-3.59l-1.54,1.51l2.91,2.89a0.75,0.75 0 1 1 -1.06,1.06l-2.92,-2.88l-1.44,1.37l3.62,3.59a0.75,0.75 0 1 1 -1.06,1.06l-3.62,-3.59l-1.51,1.54l2.85,2.84a0.75,0.75 0 1 1 -1.06,1.06c-0.29,-0.3 -2.53,-2.51 -2.82,-2.81c-0.46,0.47 -1.18,1.23 -1.41,1.54c-0.29,0.38 -0.36,0.6 -0.36,0.8c0,0.19 0.07,0.41 0.36,0.79c0.3,0.4 0.77,0.87 1.47,1.57l1.44,1.44c0.7,0.7 1.17,1.17 1.57,1.47c0.38,0.29 0.6,0.36 0.79,0.36c0.2,0 0.42,-0.07 0.8,-0.36c0.4,-0.3 0.87,-0.77 1.57,-1.47l8.67,-8.67c0.7,-0.7 1.17,-1.17 1.47,-1.57c0.29,-0.38 0.36,-0.6 0.36,-0.8c0,-0.19 -0.07,-0.41 -0.36,-0.79c-0.3,-0.4 -0.77,-0.87 -1.47,-1.57l-1.44,-1.44c-0.7,-0.7 -1.17,-1.17 -1.57,-1.47c-0.38,-0.29 -0.6,-0.36 -0.79,-0.36c-0.2,0 -0.42,0.07 -0.8,0.36l-1.61,1.37zm0.7,-2.56c0.51,-0.4 1.05,-0.67 1.71,-0.67c0.65,0 1.19,0.27 1.7,0.67c0.49,0.37 1.03,0.91 1.68,1.56l1.52,1.52c0.65,0.65 1.19,1.19 1.56,1.68c0.4,0.51 0.67,1.05 0.67,1.7c0,0.66 -0.27,1.2 -0.67,1.71c-0.37,0.49 -0.91,1.03 -1.56,1.68l-8.75,8.75c-0.65,0.65 -1.19,1.19 -1.68,1.56c-0.51,0.4 -1.05,0.67 -1.71,0.67c-0.65,0 -1.19,-0.27 -1.7,-0.67c-0.49,-0.37 -1.03,-0.91 -1.68,-1.56l-1.52,-1.52c-0.65,-0.65 -1.19,-1.19 -1.57,-1.68c-0.39,-0.51 -0.66,-1.05 -0.66,-1.7c0,-0.66 0.27,-1.2 0.66,-1.71c0.38,-0.49 0.92,-1.03 1.57,-1.68l8.75,-8.75c0.65,-0.65 1.19,-1.19 1.68,-1.57l0,0.01z" fill="#ffffff" fill-rule="evenodd" id="svg_1"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,92 @@
import bpy
from ..functions.modifier import (
is_boolean_modifier,
)
#### ------------------------------ /cutters_list/ ------------------------------ ####
class VIEW3D_UL_boolean_cutters(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
canvas = context.active_object
mod = item
# Pick Icon
if mod.operation == 'DIFFERENCE':
icon = 'SELECT_SUBTRACT'
elif mod.operation == 'UNION':
icon = 'SELECT_EXTEND'
elif mod.operation == 'INTERSECT':
icon = 'SELECT_INTERSECT'
row = layout.row(align=True)
row.prop(mod.object, "name", text="", icon=icon, emboss=False)
# Select Cutter
op_select = row.operator("object.boolean_select_cutter", text="", icon='RESTRICT_SELECT_OFF', emboss=False)
op_select.cutter = mod.object.name
# Toggle Cutter
icon = 'HIDE_OFF' if mod.show_viewport else 'HIDE_ON'
op_toggle = row.operator("object.boolean_toggle_cutter", text="", icon=icon, emboss=False)
op_toggle.method = 'SPECIFIED'
op_toggle.specified_cutter = mod.object.name
op_toggle.specified_canvas = canvas.name
op_toggle.specified_modifier = mod.name
def filter_items(self, context, data, propname):
flags = []
indices = []
modifiers = getattr(data, propname)
for mod in modifiers:
if is_boolean_modifier(mod):
flags.append(self.bitflag_filter_item)
else:
flags.append(0)
# Search Filter
if self.filter_name:
filter_name = self.filter_name.lower()
for i, mod in enumerate(modifiers):
if flags[i] != self.bitflag_filter_item:
continue
if filter_name not in mod.object.name.lower():
flags[i] = 0
# Invert
if self.use_filter_invert:
for i, mod in enumerate(modifiers):
if not is_boolean_modifier(mod):
continue
flags[i] ^= self.bitflag_filter_item
# Sort by Name
indices = list(range(len(modifiers)))
if self.use_filter_sort_alpha:
sorted_indices = sorted(range(len(modifiers)),
key=lambda i: modifiers[i].object.name if modifiers[i].object else "")
indices = [0] * len(modifiers)
for rank, original_i in enumerate(sorted_indices):
indices[original_i] = rank
return flags, indices
#### ------------------------------ REGISTRATION ------------------------------ ####
classes = (
VIEW3D_UL_boolean_cutters,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
@@ -0,0 +1,129 @@
import bpy
from ..functions.canvas import (
is_canvas,
)
from .common import (
boolean_operators_menu,
boolean_extras_menu,
)
#### ------------------------------ /boolean_menu/ ------------------------------ ####
# 3D Viewport (Object Mode) -> Object
class VIEW3D_MT_boolean(bpy.types.Menu):
bl_label = "Boolean"
bl_idname = "VIEW3D_MT_boolean"
def draw(self, context):
layout = self.layout
layout.menu("VIEW3D_MT_carve")
layout.separator()
boolean_operators_menu(self, context)
boolean_extras_menu(self, context)
def object_mode_menu(self, context):
layout = self.layout
layout.separator()
layout.menu("VIEW3D_MT_boolean")
#### ------------------------------ /popup_menu/ ------------------------------ ####
# Shift-Ctrl-B Menu
class VIEW3D_MT_boolean_popup(bpy.types.Menu):
bl_label = "Boolean"
bl_idname = "VIEW3D_MT_boolean_popup"
def draw(self, context):
boolean_operators_menu(self, context)
boolean_extras_menu(self, context)
#### ------------------------------ /carve_menu/ ------------------------------ ####
class VIEW3D_MT_carve(bpy.types.Menu):
bl_label = "Carve"
bl_idname = "VIEW3D_MT_carve"
def draw(self, context):
layout = self.layout
layout.operator("object.carve_box", text="Box Carve")
layout.operator("object.carve_circle", text="Circle Carve")
layout.operator("object.carve_polyline", text="Polyline Carve")
# Separate "Carve" menu in Edit Mode (where we don't have "Boolean" menu).
def edit_mode_menu(self, context):
layout = self.layout
layout.separator()
layout.menu("VIEW3D_MT_carve")
#### ------------------------------ /select_menu/ ------------------------------ ####
def select_menu(self, context):
layout = self.layout
obj = context.active_object
if not obj:
return
if is_canvas(obj) or obj.booleans.cutter:
layout.separator()
if is_canvas(obj):
layout.operator("object.boolean_select_all", text="Boolean Cutters")
if obj.booleans.cutter:
layout.operator("object.select_cutter_canvas", text="Boolean Canvases")
#### ------------------------------ REGISTRATION ------------------------------ ####
addon_keymaps = []
classes = (
VIEW3D_MT_boolean,
VIEW3D_MT_boolean_popup,
VIEW3D_MT_carve,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
# MENU
bpy.types.VIEW3D_MT_object.append(object_mode_menu)
bpy.types.VIEW3D_MT_edit_mesh.append(edit_mode_menu)
bpy.types.VIEW3D_MT_select_object.append(select_menu)
# KEYMAP
addon = bpy.context.window_manager.keyconfigs.addon
km = addon.keymaps.new(name="Object Mode")
kmi = km.keymap_items.new("wm.call_menu", 'B', 'PRESS', ctrl=True, shift=True)
kmi.properties.name = "VIEW3D_MT_boolean_popup"
kmi.active = True
addon_keymaps.append((km, kmi))
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
# MENU
bpy.types.VIEW3D_MT_object.remove(object_mode_menu)
bpy.types.VIEW3D_MT_edit_mesh.remove(edit_mode_menu)
bpy.types.VIEW3D_MT_select_object.remove(select_menu)
# KEYMAP
for km, kmi in addon_keymaps:
km.keymap_items.remove(kmi)
addon_keymaps.clear()
@@ -0,0 +1,182 @@
import bpy
from .. import __package__ as base_package
from ..functions.canvas import (
is_canvas,
)
from .common import (
get_modifier_from_list_index,
boolean_extras_menu,
)
#### ------------------------------ PANELS ------------------------------ ####
# Boolean Operators Panel
class VIEW3D_PT_boolean(bpy.types.Panel):
bl_label = "Boolean"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Edit"
bl_context = "objectmode"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(cls, context):
prefs = context.preferences.addons[base_package].preferences
return prefs.show_in_sidebar
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_DEFAULT'
col = layout.column(align=True)
col.label(text="Auto Boolean")
row = col.row(align=False)
row.operator("object.boolean_auto_difference", text="Difference", icon='SELECT_SUBTRACT')
row.operator("object.boolean_auto_difference", text="", icon='UV_SYNC_SELECT').flip=True
row = col.row(align=False)
row.operator("object.boolean_auto_union", text="Union", icon='SELECT_EXTEND')
row.operator("object.boolean_auto_union", text="", icon='UV_SYNC_SELECT').flip=True
row = col.row(align=False)
row.operator("object.boolean_auto_intersect", text="Intersect", icon='SELECT_INTERSECT')
row.operator("object.boolean_auto_intersect", text="", icon='UV_SYNC_SELECT').flip=True
row = col.row(align=False)
row.operator("object.boolean_auto_slice", text="Slice", icon='SELECT_DIFFERENCE')
row.operator("object.boolean_auto_slice", text="", icon='UV_SYNC_SELECT').flip=True
col.separator()
col.label(text="Brush Boolean")
row = col.row(align=False)
row.operator("object.boolean_brush_difference", text="Difference", icon='SELECT_SUBTRACT')
row.operator("object.boolean_brush_difference", text="", icon='UV_SYNC_SELECT').flip=True
row = col.row(align=False)
row.operator("object.boolean_brush_union", text="Union", icon='SELECT_EXTEND')
row.operator("object.boolean_brush_union", text="", icon='UV_SYNC_SELECT').flip=True
row = col.row(align=False)
row.operator("object.boolean_brush_intersect", text="Intersect", icon='SELECT_INTERSECT')
row.operator("object.boolean_brush_intersect", text="", icon='UV_SYNC_SELECT').flip=True
row = col.row(align=False)
row.operator("object.boolean_brush_slice", text="Slice", icon='SELECT_DIFFERENCE')
row.operator("object.boolean_brush_slice", text="", icon='UV_SYNC_SELECT').flip=True
# Cutters Panel
class VIEW3D_PT_boolean_cutters(bpy.types.Panel):
bl_label = "Cutters"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Edit"
bl_context = "objectmode"
bl_parent_id = "VIEW3D_PT_boolean"
@classmethod
def poll(cls, context):
prefs = context.preferences.addons[base_package].preferences
if prefs.show_in_sidebar:
if context.active_object:
if is_canvas(context.active_object):
return True
else:
return False
else:
return False
else:
return False
def draw(self, context):
layout = self.layout
canvas = context.active_object
# Cutters List
row = layout.row()
col = row.column()
col.template_list(
"VIEW3D_UL_boolean_cutters",
"",
canvas, "modifiers",
canvas.booleans, "modifiers_list_index",
rows=5,
)
# Filter & Operators
col = row.column(align=True)
cutters_list_index = canvas.booleans.modifiers_list_index
mod = get_modifier_from_list_index(canvas, cutters_list_index)
sub = col.column(align=True)
# Apply Cutter
op_apply = sub.operator("object.boolean_apply_cutter", text="", icon='CHECKMARK')
op_apply.method = 'SPECIFIED'
op_apply.specified_cutter = mod.object.name if mod else ""
op_apply.specified_canvas = canvas.name
# Remove Cutter
op_remove = sub.operator("object.boolean_remove_cutter", text="", icon='X')
op_remove.method = 'SPECIFIED'
op_remove.specified_cutter = mod.object.name if mod else ""
op_remove.specified_canvas = canvas.name
op_remove.specified_modifier = mod.name if mod else ""
if cutters_list_index < 0 or not mod:
sub.enabled = False
col.separator()
col.menu("VIEW3D_MT_boolean_specials", icon='DOWNARROW_HLT', text="")
# Helpers Panel
class VIEW3D_PT_boolean_helpers(bpy.types.Panel):
bl_label = "Helpers"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Edit"
bl_context = "objectmode"
bl_parent_id = "VIEW3D_PT_boolean"
@classmethod
def poll(cls, context):
prefs = context.preferences.addons[base_package].preferences
if not prefs.show_in_sidebar:
return False
if not context.active_object:
return False
if context.active_object.booleans.cutter:
return True
return False
def draw(self, context):
boolean_extras_menu(self, context, cutter_only=True)
#### ------------------------------ MENUS ------------------------------ ####
# Specials
class VIEW3D_MT_boolean_specials(bpy.types.Menu):
bl_label = "Boolean Operators"
bl_idname = "VIEW3D_MT_boolean_specials"
def draw(self, context):
boolean_extras_menu(self, context)
#### ------------------------------ REGISTRATION ------------------------------ ####
classes = (
VIEW3D_PT_boolean,
VIEW3D_PT_boolean_cutters,
VIEW3D_PT_boolean_helpers,
VIEW3D_MT_boolean_specials,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)