90 lines
3.7 KiB
Python
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)
|