From 798afbe82a1c98d872919b9ae92ce3390ba9b45e Mon Sep 17 00:00:00 2001 From: pullusb Date: Thu, 9 Mar 2023 17:53:40 +0100 Subject: [PATCH] Animation manager improve animated hint 2.3.1 - changed: Animation manager show icons when animation is enabled: fully, partially or not at all. --- CHANGELOG.md | 4 ++ UI_tools.py | 61 ++++++++++++++++++--------- __init__.py | 2 +- utils.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 162 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6077a2d..09e25f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +2.3.1 + +- changed: Animation manager show hints when animation is enabled: fully, partially or not at all. + 2.3.0 - added: Animation manager buttons are colored red when objects have disabled animation diff --git a/UI_tools.py b/UI_tools.py index 119dd48..e4017fa 100644 --- a/UI_tools.py +++ b/UI_tools.py @@ -1,5 +1,8 @@ # from . import addon_updater_ops -from .utils import get_addon_prefs, all_anim_enabled, all_object_modifier_enabled +from .utils import (get_addon_prefs, + anim_status, + gp_modifier_status, + ) import bpy from pathlib import Path from bpy.types import Panel @@ -179,48 +182,69 @@ class GPTB_PT_anim_manager(Panel): # def draw_header(self,context): # self.layout.prop(context.scene.camera.data, "show_background_images", text="") + def get_object_by_types(self, context) -> dict: + # import time + # t0 = time.perf_counter() + + # objs = [o for o in context.scene.objects if o.type not in ('GPENCIL', 'CAMERA')] + # gps = [o for o in context.scene.objects if o.type == 'GPENCIL'] + # cams = [o for o in context.scene.objects if o.type == 'CAMERA'] + objs = [] + gps = [] + cams = [] + for o in context.scene.objects: + if o.type not in ('GPENCIL', 'CAMERA'): + objs.append(o) + elif o.type == 'GPENCIL': + gps.append(o) + elif o.type == 'CAMERA': + cams.append(o) + + # print(f'{time.perf_counter() - t0:.8f}s') + + return {'OBJECT': objs, 'GPENCIL': gps, 'CAMERA': cams} + + def draw(self, context): layout = self.layout layout.use_property_split = True col = layout.column() ## Animation enable disable anim (shift click to select) OP_helpers.GPTB_OT_toggle_mute_animation - - # import time - # t0 = time.time() - - scn = context.scene - objs = [o for o in scn.objects if o.type not in ('GPENCIL', 'CAMERA')] # show_alert - gps = [o for o in scn.objects if o.type == 'GPENCIL'] # show_alert - cams = [o for o in scn.objects if o.type == 'CAMERA'] # show_alert + + obj_types = self.get_object_by_types(context) col.operator('gp.list_disabled_anims') - ## Show Enable / Disable - for cat, cat_type, tgt in [('Obj anims:', 'OBJECT', objs), ('Cam anims:', 'CAMERA', cams), ('Gp anims:', 'GPENCIL', gps)]: + ## Show Enable / Disable anims + for cat, cat_type in [('Obj anims:', 'OBJECT'), ('Cam anims:', 'CAMERA'), ('Gp anims:', 'GPENCIL')]: + on_icon, off_icon = anim_status(obj_types[cat_type]) + subcol = col.column() - subcol.alert = not all_anim_enabled(tgt) # show_alert + # subcol.alert = off_icon == 'LAYER_ACTIVE' # Turn red row = subcol.row(align=True) row.label(text=cat) - ops = row.operator('gp.toggle_mute_animation', text = 'ON') + + ops = row.operator('gp.toggle_mute_animation', text='ON', icon=on_icon) ops.mode = cat_type ops.mute = False - ops = row.operator('gp.toggle_mute_animation', text = 'OFF') + ops = row.operator('gp.toggle_mute_animation', text='OFF', icon=off_icon) ops.mode = cat_type ops.mute = True ## GP modifiers subcol = col.column() - subcol.alert = not all_object_modifier_enabled(gps) # show_alert + row = subcol.row(align=True) row.label(text='Gp modifiers:') - row.operator('gp.toggle_hide_gp_modifier', text = 'ON').show = True - row.operator('gp.toggle_hide_gp_modifier', text = 'OFF').show = False + on_icon, off_icon = gp_modifier_status(obj_types['GPENCIL']) + # subcol.alert = off_icon == 'LAYER_ACTIVE' # Turn red + row.operator('gp.toggle_hide_gp_modifier', text='ON', icon=on_icon).show = True + row.operator('gp.toggle_hide_gp_modifier', text='OFF', icon=off_icon).show = False ## Follow curve path col = col.column() row = col.row(align=True) - # row.operator('object.create_follow_path_curve', text='Create Curve', icon='CURVE_BEZCURVE') if context.object: if context.object.type == 'CURVE' and context.mode in ('OBJECT', 'EDIT_CURVE'): @@ -248,7 +272,6 @@ class GPTB_PT_anim_manager(Panel): text, icon = ('Cursor Follow On', 'PIVOT_CURSOR') if context.scene.gptoolprops.cursor_follow else ('Cursor Follow Off', 'CURSOR') col.prop(context.scene.gptoolprops, 'cursor_follow', text=text, icon=icon) - # print(f'{time.time() - t0:.6f}s') class GPTB_PT_toolbox_playblast(Panel): bl_label = "Playblast" diff --git a/__init__.py b/__init__.py index 88fba66..779867e 100755 --- a/__init__.py +++ b/__init__.py @@ -4,7 +4,7 @@ bl_info = { "name": "GP toolbox", "description": "Tool set for Grease Pencil in animation production", "author": "Samuel Bernou, Christophe Seux", -"version": (2, 3, 0), +"version": (2, 3, 1), "blender": (3, 0, 0), "location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "warning": "", diff --git a/utils.py b/utils.py index 87414cf..3d5e213 100644 --- a/utils.py +++ b/utils.py @@ -1119,6 +1119,7 @@ def go_edit_mode(ob, context=None): bpy.ops.object.mode_set(mode='EDIT', toggle=False) +## Not used anymore def all_anim_enabled(objects) -> bool: '''return False if at least one fcurve/group has disabled animation in passed object anim''' for o in objects: @@ -1146,6 +1147,7 @@ def all_anim_enabled(objects) -> bool: return True +## Not used anymore def all_object_modifier_enabled(objects) -> bool: '''Return False if one modifier of one object has GP modifier disabled in viewport but enabled in render''' for o in objects: @@ -1158,5 +1160,117 @@ def all_object_modifier_enabled(objects) -> bool: ## return False when viewport but no render ? # if not m.show_render and m.show_viewport: # return False + return True + +## Only used in anim_status in 'per GP object' mode +def has_fully_enabled_anim(o): + if o.animation_data and o.animation_data.action: + ## Skip hided object ? (missmatch) + # if o.hide_get(): + # continue + + ## Check if groups are muted + for grp in o.animation_data.action.groups: + if grp.mute: + return False + + ## Check if fcurves are muted + for fcu in o.animation_data.action.fcurves: + if fcu.mute: + return False + + if o.type in ('GPENCIL', 'CAMERA'): + if o.data.animation_data and o.data.animation_data.action: + ## Check if object data attributes fcurves are muted + for fcu in o.animation_data.action.fcurves: + if fcu.mute: + return False + return True + +def anim_status(objects) -> tuple((str, str)): + '''Return a tutple of icon string status in ('ALL_ON', 'MIXED', 'ALL_OFF', 'NONE')''' - return True \ No newline at end of file + on_count = off_count = count = 0 + + for o in objects: + ## Skip object with no animation + if not (o.animation_data and o.animation_data.action) and not (o.data.animation_data and o.data.animation_data.action): + continue + + ## Also skip hidden objects + if o.hide_get() and o.hide_render: + continue + + ### Per Object + # count += 1 + # if has_fully_enabled_anim(o): + # on_count += 1 + # else: + # off_count += 1 + + ### Consider All channels individually + for grp in o.animation_data.action.groups: + ## Check if groups are muted + if grp.mute: + off_count += 1 + else: + on_count += 1 + count += 1 + + + for fcu in o.animation_data.action.fcurves: + ## Check if fcurves are muted + if fcu.mute: + off_count += 1 + else: + on_count += 1 + count += 1 + + if o.type in ('GPENCIL', 'CAMERA'): + if o.data.animation_data and o.data.animation_data.action: + ## Check if object data attributes fcurves are muted + for fcu in o.animation_data.action.fcurves: + if fcu.mute: + off_count += 1 + else: + on_count += 1 + count += 1 + + if not on_count and not off_count: + return ('BLANK1', 'BLANK1') # 'NONE' + elif on_count == count: + return ('LAYER_ACTIVE', 'BLANK1') # 'ALL_ON' + elif off_count == count: + return ('BLANK1', 'LAYER_ACTIVE') # 'ALL_OFF' + else: + return ('LAYER_USED', 'LAYER_USED') # 'MIXED' + + +def gp_modifier_status(objects) -> tuple((str, str)): + '''return icons on/off tuple''' + on_count = off_count = count = 0 + for o in objects: + if o.type != 'GPENCIL': + continue + ## Skip hided object + if o.hide_get() and o.hide_render: + continue + for m in o.grease_pencil_modifiers: + if m.show_render and not m.show_viewport: + off_count += 1 + else: + on_count += 1 + count += 1 + + ## return False when viewport but no render ? + # if not m.show_render and m.show_viewport: + # return False + + if not on_count and not off_count: + return ('BLANK1', 'BLANK1') # 'NONE' + elif on_count == count: + return ('LAYER_ACTIVE', 'BLANK1') # 'ALL_ON' + elif off_count == count: + return ('BLANK1', 'LAYER_ACTIVE') # 'ALL_OFF' + else: + return ('LAYER_USED', 'LAYER_USED') \ No newline at end of file