Files
blender-portable-repo/scripts/addons/easy_weight/weight_cleaner.py
T
2026-03-17 14:58:51 -06:00

90 lines
3.7 KiB
Python

# SPDX-FileCopyrightText: 2025 Blender Studio Tools Authors
#
# SPDX-License-Identifier: GPL-3.0-or-later
import bpy
from bpy.app.handlers import persistent
from .utils import get_addon_prefs
@persistent
def start_cleaner(scene=None, depsgraph=None):
if WeightCleaner.clean_weights not in bpy.app.handlers.depsgraph_update_pre:
bpy.app.handlers.depsgraph_update_pre.append(WeightCleaner.clean_weights)
if WeightCleaner.reset_flag not in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.append(WeightCleaner.reset_flag)
@persistent
def stop_cleaner(scene=None, depsgraph=None):
if WeightCleaner.clean_weights in bpy.app.handlers.depsgraph_update_pre:
bpy.app.handlers.depsgraph_update_pre.remove(WeightCleaner.clean_weights)
if WeightCleaner.reset_flag in bpy.app.handlers.depsgraph_update_post:
bpy.app.handlers.depsgraph_update_post.remove(WeightCleaner.reset_flag)
class WeightCleaner:
"""Run bpy.ops.object.vertex_group_clean on every depsgraph update while in weight paint mode (ie. every brush stroke)."""
# Flag set in post_depsgraph_update, to indicate to pre_depsgraph_update that the depsgraph update has indeed completed.
can_clean = True
# Flag set by pre_depsgraph_update to indicate to post_depsgraph_update that the cleanup operator is still running (in a different thread).
cleaning_in_progress = False
@classmethod
def clean_weights(cls, scene, depsgraph):
context = bpy.context
prefs = get_addon_prefs(context)
if context.mode != 'PAINT_WEIGHT':
return
if not context or not hasattr(context, 'active_object') or not context.active_object:
return
if not prefs.auto_clean_weights:
return
tool = context.workspace.tools.from_space_view3d_mode("PAINT_WEIGHT", create=False).idname
if tool == "builtin.gradient":
# Trying to clean while using gradient causes a crash:
# https://projects.blender.org/studio/blender-studio-tools/issues/332
return
if cls.can_clean:
cls.can_clean = False
cls.cleaning_in_progress = True
# This will trigger a depsgraph update, and therefore clean_weights, again.
try:
ensure_mirror_groups(context.active_object)
bpy.ops.object.vertex_group_clean(group_select_mode='ALL', limit=0.001)
except Exception:
# This happens for example if the object has no vertex groups.
pass
cls.cleaning_in_progress = False
@classmethod
def reset_flag(cls, scene, depsgraph):
context = bpy.context
if context.mode != 'PAINT_WEIGHT':
return
if not context or not hasattr(context, 'active_object') or not context.active_object:
return
if cls.cleaning_in_progress:
return
cls.can_clean = True
def ensure_mirror_groups(mesh_obj):
mod_types = [mod.type for mod in mesh_obj.modifiers]
rigs = [m.object for m in mesh_obj.modifiers if m.type == 'ARMATURE' and m.object]
if rigs and 'MIRROR' in mod_types:
for rig in rigs:
for pb in rig.pose.bones:
if pb.name in mesh_obj.vertex_groups:
flipped_name = bpy.utils.flip_name(pb.name)
if flipped_name != pb.name and flipped_name not in mesh_obj.vertex_groups:
mesh_obj.vertex_groups.new(name=flipped_name)
def register():
start_cleaner()
bpy.app.handlers.load_post.append(start_cleaner)
def unregister():
stop_cleaner()
bpy.app.handlers.load_post.remove(start_cleaner)