EasyWeight: Major update #317

Merged
Demeter Dzadik merged 9 commits from easyweight-updates into main 2024-06-25 21:09:03 +02:00
4 changed files with 85 additions and 83 deletions
Showing only changes of commit 312b33f5da - Show all commits

View File

@ -1,24 +1,11 @@
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from . import ( from . import (
rogue_weights,
vertex_group_menu,
vertex_group_operators,
weight_paint_context_menu,
toggle_weight_paint,
force_apply_mirror, force_apply_mirror,
toggle_weight_paint,
weight_paint_context_menu,
vertex_group_operators,
weight_cleaner,
vertex_group_menu,
rogue_weights,
prefs, prefs,
) )
import bpy import bpy
@ -42,6 +29,7 @@ modules = [
toggle_weight_paint, toggle_weight_paint,
weight_paint_context_menu, weight_paint_context_menu,
vertex_group_operators, vertex_group_operators,
weight_cleaner,
vertex_group_menu, vertex_group_menu,
rogue_weights, rogue_weights,
prefs, prefs,

View File

@ -1,15 +1,31 @@
import bpy, json import bpy, json
from bpy.props import BoolProperty from bpy.props import BoolProperty
from . import __package__ as base_package
def get_addon_prefs(context=None):
if not context:
context = bpy.context
if base_package.startswith('bl_ext'):
# 4.2
return context.preferences.addons[base_package].preferences
else:
return context.preferences.addons[base_package.split(".")[0]].preferences
class EASYWEIGHT_addon_preferences(bpy.types.AddonPreferences): class EASYWEIGHT_addon_preferences(bpy.types.AddonPreferences):
bl_idname = __package__ bl_idname = __package__
easyweight_keymap_items = {} easyweight_keymap_items = {}
auto_clean_weights: BoolProperty(
name="Auto Clean",
description="While this is enabled, zero-weights will be removed automatically after every brush stroke",
default=True,
)
show_hotkeys: BoolProperty( show_hotkeys: BoolProperty(
name="Show Hotkeys", name="Show Hotkeys",
default=False,
description="Reveal the hotkey list. You may customize or disable these hotkeys", description="Reveal the hotkey list. You may customize or disable these hotkeys",
default=False,
) )
def draw(self, context): def draw(self, context):
@ -205,12 +221,6 @@ def register():
) )
register_hotkey( register_hotkey(
bl_idname='object.weight_paint_toggle', bl_idname='object.weight_paint_toggle',
hotkey_kwargs={ hotkey_kwargs={'type': 'TAB', 'value': 'PRESS', 'ctrl': True},
'type': 'TAB',
'value': 'PRESS',
'ctrl': True
},
key_cat='3D View', key_cat='3D View',
) )

View File

@ -0,0 +1,55 @@
import bpy
from bpy.app.handlers import persistent
from .prefs import get_addon_prefs
@persistent
def start_cleaner(scene, depsgraph):
bpy.app.handlers.depsgraph_update_pre.append(WeightCleaner.clean_weights)
bpy.app.handlers.depsgraph_update_post.append(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, 'object') or not context.object:
return
if not prefs.auto_clean_weights:
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.
bpy.ops.object.vertex_group_clean(group_select_mode='ALL', limit=0.001)
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, 'object') or not context.object:
return
if cls.cleaning_in_progress:
return
cls.can_clean = True
def register():
start_cleaner(None, None)
bpy.app.handlers.load_post.append(start_cleaner)
def unregister():
bpy.app.handlers.load_post.remove(start_cleaner)

View File

@ -1,7 +1,7 @@
import bpy import bpy
from bpy.props import BoolProperty, EnumProperty from bpy.props import BoolProperty, EnumProperty
from bpy.app.handlers import persistent
from .vertex_group_operators import EASYWEIGHTS_OT_delete_empty_deform_groups, EASYWEIGHTS_OT_delete_unused_vertex_groups from .vertex_group_operators import EASYWEIGHTS_OT_delete_empty_deform_groups, EASYWEIGHTS_OT_delete_unused_vertex_groups
from .prefs import get_addon_prefs
class EASYWEIGHT_OT_wp_context_menu(bpy.types.Operator): class EASYWEIGHT_OT_wp_context_menu(bpy.types.Operator):
""" Custom Weight Paint context menu """ """ Custom Weight Paint context menu """
@ -9,10 +9,6 @@ class EASYWEIGHT_OT_wp_context_menu(bpy.types.Operator):
bl_label = "Custom Weight Paint Context Menu" bl_label = "Custom Weight Paint Context Menu"
bl_options = {'REGISTER'} bl_options = {'REGISTER'}
def update_clean_weights(self, context):
context.scene['clean_weights'] = self.clean_weights
WeightCleaner.cleaner_active = context.scene['clean_weights']
def update_front_faces(self, context): def update_front_faces(self, context):
for b in bpy.data.brushes: for b in bpy.data.brushes:
if not b.use_paint_weight: if not b.use_paint_weight:
@ -35,8 +31,6 @@ class EASYWEIGHT_OT_wp_context_menu(bpy.types.Operator):
b.cursor_color_add[i] = ( b.cursor_color_add[i] = (
0.5 if self.falloff_shape == 'SPHERE' else 2.0) 0.5 if self.falloff_shape == 'SPHERE' else 2.0)
clean_weights: BoolProperty(
name="Clean Weights", description="Run the Clean Vertex Groups operator after every weight brush stroke", update=update_clean_weights)
front_faces: BoolProperty( front_faces: BoolProperty(
name="Front Faces Only", description="Toggle the Front Faces Only setting for all weight brushes", update=update_front_faces) name="Front Faces Only", description="Toggle the Front Faces Only setting for all weight brushes", update=update_front_faces)
accumulate: BoolProperty( accumulate: BoolProperty(
@ -121,10 +115,12 @@ class EASYWEIGHT_OT_wp_context_menu(bpy.types.Operator):
tool_settings = context.tool_settings tool_settings = context.tool_settings
layout.label(text="Weight Paint settings") layout.label(text="Weight Paint settings")
prefs = get_addon_prefs(context)
row = layout.row() row = layout.row()
row.prop(tool_settings, "use_auto_normalize", row.prop(tool_settings, "use_auto_normalize",
text="Auto Normalize", toggle=True) text="Auto Normalize", toggle=True)
row.prop(self, "clean_weights", toggle=True) row.prop(prefs, "auto_clean_weights", toggle=True)
row.prop(tool_settings, "use_multipaint", row.prop(tool_settings, "use_multipaint",
text="Multi-Paint", toggle=True) text="Multi-Paint", toggle=True)
row = layout.row() row = layout.row()
@ -171,6 +167,7 @@ class EASYWEIGHT_OT_wp_context_menu(bpy.types.Operator):
active_brush = context.tool_settings.weight_paint.brush active_brush = context.tool_settings.weight_paint.brush
self.front_faces = active_brush.use_frontface self.front_faces = active_brush.use_frontface
self.falloff_shape = active_brush.falloff_shape self.falloff_shape = active_brush.falloff_shape
self.accumulate = active_brush.use_accumulate
if 'clean_weights' not in context.scene: if 'clean_weights' not in context.scene:
context.scene['clean_weights'] = False context.scene['clean_weights'] = False
self.clean_weights = context.scene['clean_weights'] self.clean_weights = context.scene['clean_weights']
@ -183,55 +180,10 @@ class EASYWEIGHT_OT_wp_context_menu(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class WeightCleaner:
"""Run bpy.ops.object.vertex_group_clean on every depsgraph update while in weight paint mode (ie. every brush stroke)."""
# Most of the code is simply responsible for avoiding infinite looping depsgraph updates.
# Flag set by the user via the custom WP context menu.
cleaner_active = False
# 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):
if bpy.context.mode != 'PAINT_WEIGHT':
return
if not bpy.context or not hasattr(bpy.context, 'object') or not bpy.context.object:
return
if not cls.cleaner_active:
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.
bpy.ops.object.vertex_group_clean(
group_select_mode='ALL', limit=0.001)
cls.cleaning_in_progress = False
@classmethod
def reset_flag(cls, scene, depsgraph):
if bpy.context.mode != 'PAINT_WEIGHT':
return
if not bpy.context or not hasattr(bpy.context, 'object') or not bpy.context.object:
return
if cls.cleaning_in_progress:
return
if not cls.cleaner_active:
return
cls.can_clean = True
def draw_menu_entry(self, context): def draw_menu_entry(self, context):
self.layout.operator(EASYWEIGHT_OT_wp_context_menu.bl_idname) self.layout.operator(EASYWEIGHT_OT_wp_context_menu.bl_idname)
@persistent
def start_cleaner(scene, depsgraph):
bpy.app.handlers.depsgraph_update_pre.append(WeightCleaner.clean_weights)
bpy.app.handlers.depsgraph_update_post.append(WeightCleaner.reset_flag)
registry = [ registry = [
EASYWEIGHT_OT_wp_context_menu EASYWEIGHT_OT_wp_context_menu
] ]
@ -243,13 +195,10 @@ def register():
description="Hide options that are less frequently used", description="Hide options that are less frequently used",
default=False default=False
) )
start_cleaner(None, None)
bpy.app.handlers.load_post.append(start_cleaner)
bpy.types.VIEW3D_MT_paint_weight.append(draw_menu_entry) bpy.types.VIEW3D_MT_paint_weight.append(draw_menu_entry)
def unregister(): def unregister():
del bpy.types.Scene.easyweight_minimal del bpy.types.Scene.easyweight_minimal
bpy.app.handlers.load_post.remove(start_cleaner)
bpy.types.VIEW3D_MT_paint_weight.remove(draw_menu_entry) bpy.types.VIEW3D_MT_paint_weight.remove(draw_menu_entry)