diff --git a/CHANGELOG.md b/CHANGELOG.md index 0248e12..76f79c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +1.7.2 + +- added: `Object visibility conflict` in file check + - print in console when object have render activated but not viewport (& vice-versa) + - Standalone ops "List Object Visibility Conflicts" (`gp.list_object_visibility`) +- added: `GP Modifier visibility conflict` in file check. + - print in console when gp modifiers have render activated but not viewport (& vice-versa) + - Standalone ops "List GP Modifiers Visibility Conflicts" (`gp.list_modifier_visibility`) +- code: show_message_box utils can now receive operator in sublist (if 3 element) + 1.7.1 - feat: Improved `Create Empty Frames` operator with mutliple filters to choose source layers diff --git a/OP_file_checker.py b/OP_file_checker.py index 495c743..76e84f9 100755 --- a/OP_file_checker.py +++ b/OP_file_checker.py @@ -1,17 +1,13 @@ import bpy import os from pathlib import Path -from .utils import show_message_box, get_addon_prefs +from . import utils class GPTB_OT_file_checker(bpy.types.Operator): bl_idname = "gp.file_checker" - bl_label = "File check" + bl_label = "Check File" bl_description = "Check / correct some aspect of the file, properties and such and report" bl_options = {"REGISTER"} - - # @classmethod - # def poll(cls, context): - # return context.region_data.view_perspective == 'CAMERA' ## list of action : # Lock main cam: @@ -23,16 +19,19 @@ class GPTB_OT_file_checker(bpy.types.Operator): # GP use additive drawing (else creating a frame in dopesheet makes it blank...) # GP stroke placement/projection check # Disabled animation + # Object visibility conflict + # GP modifiers visibility conflict # Set onion skin filter to 'All type' # Set filepath type # Set Lock object mode state def invoke(self, context, event): + # need some self-control (I had to...) self.ctrl = event.ctrl return self.execute(context) def execute(self, context): - prefs = get_addon_prefs() + prefs = utils.get_addon_prefs() fix = prefs.fixprops problems = [] @@ -128,7 +127,37 @@ class GPTB_OT_file_checker(bpy.types.Operator): fcu_ct += 1 print(f"muted: {act.name} > {fcu.data_path}") if fcu_ct: - problems.append(f'{fcu_ct} anim channel disabled (details -> console)') + problems.append(f'{fcu_ct} anim channel disabled (details in console)') + + ## Object visibility conflict + if fix.list_obj_vis_conflict: + viz_ct = 0 + for o in context.scene.objects: + if o.hide_viewport != o.hide_render: + vp = 'No' if o.hide_viewport else 'Yes' + rd = 'No' if o.hide_render else 'Yes' + viz_ct += 1 + print(f'{o.name} : viewport {vp} != render {rd}') + if viz_ct: + ## warn only : problems.append(f'{viz_ct} objects visibility conflicts (details in console)') + problems.append(['gp.list_object_visibility', f'{viz_ct} objects visibility conflicts (details in console)', 'OBJECT_DATAMODE']) + + ## GP modifiers visibility conflict + if fix.list_gp_mod_vis_conflict: + mod_viz_ct = 0 + for o in context.scene.objects: + if o.type != 'GPENCIL': + continue + for m in o.grease_pencil_modifiers: + if m.show_viewport != m.show_render: + vp = 'Yes' if m.show_viewport else 'No' + rd = 'Yes' if m.show_render else 'No' + mod_viz_ct += 1 + print(f'{o.name} - modifier {m.name}: viewport {vp} != render {rd}') + if mod_viz_ct: + ## warn only : problems.append(f'{mod_viz_ct} visibility conflicts in Gp object modifiers (details in console)') + problems.append(['gp.list_modifier_visibility', f'{mod_viz_ct} visibility conflicts in Gp object modifiers (details in console)', 'MODIFIER_DATA']) + ## Use median point if fix.set_pivot_median_point: @@ -205,47 +234,12 @@ class GPTB_OT_file_checker(bpy.types.Operator): # Show in viewport title = "Changed Settings" if apply else "Checked Settings (dry run, nothing changed)" - show_message_box(problems, _title = title, _icon = 'INFO') + utils.show_message_box(problems, _title = title, _icon = 'INFO') else: self.report({'INFO'}, 'All good') return {"FINISHED"} -""" OLD links checker with show_message_box -class GPTB_OT_links_checker(bpy.types.Operator): - bl_idname = "gp.links_checker" - bl_label = "Links check" - bl_description = "Check states of file direct links" - bl_options = {"REGISTER"} - - def execute(self, context): - all_lnks = [] - has_broken_link = False - ## check for broken links - for current, lib in zip(bpy.utils.blend_paths(local=True), bpy.utils.blend_paths(absolute=True, local=True)): - lfp = Path(lib) - realib = Path(current) - if not lfp.exists(): - has_broken_link = True - all_lnks.append( (f"Broken link: {realib.as_posix()}", 'LIBRARY_DATA_BROKEN') )#lfp.as_posix() - else: - if realib.as_posix().startswith('//'): - all_lnks.append( (f"Link: {realib.as_posix()}", 'LINKED') )#lfp.as_posix() - else: - all_lnks.append( (f"Link: {realib.as_posix()}", 'LIBRARY_DATA_INDIRECT') )#lfp.as_posix() - - all_lnks.sort(key=lambda x: x[1], reverse=True) - if all_lnks: - print('===File check===') - for p in all_lnks: - if isinstance(p, str): - print(p) - else: - print(p[0]) - # Show in viewport - show_message_box(all_lnks, _title = "Links", _icon = 'INFO') - return {"FINISHED"} """ - class GPTB_OT_links_checker(bpy.types.Operator): bl_idname = "gp.links_checker" @@ -332,6 +326,144 @@ class GPTB_OT_links_checker(bpy.types.Operator): self.proj = None return context.window_manager.invoke_props_dialog(self, width=800) +""" OLD links checker with show_message_box +class GPTB_OT_links_checker(bpy.types.Operator): + bl_idname = "gp.links_checker" + bl_label = "Links check" + bl_description = "Check states of file direct links" + bl_options = {"REGISTER"} + + def execute(self, context): + all_lnks = [] + has_broken_link = False + ## check for broken links + for current, lib in zip(bpy.utils.blend_paths(local=True), bpy.utils.blend_paths(absolute=True, local=True)): + lfp = Path(lib) + realib = Path(current) + if not lfp.exists(): + has_broken_link = True + all_lnks.append( (f"Broken link: {realib.as_posix()}", 'LIBRARY_DATA_BROKEN') )#lfp.as_posix() + else: + if realib.as_posix().startswith('//'): + all_lnks.append( (f"Link: {realib.as_posix()}", 'LINKED') )#lfp.as_posix() + else: + all_lnks.append( (f"Link: {realib.as_posix()}", 'LIBRARY_DATA_INDIRECT') )#lfp.as_posix() + + all_lnks.sort(key=lambda x: x[1], reverse=True) + if all_lnks: + print('===File check===') + for p in all_lnks: + if isinstance(p, str): + print(p) + else: + print(p[0]) + # Show in viewport + utils.show_message_box(all_lnks, _title = "Links", _icon = 'INFO') + return {"FINISHED"} """ + + +class GPTB_OT_list_object_visibility(bpy.types.Operator): + bl_idname = "gp.list_object_visibility" + bl_label = "List Object Visibility Conflicts" + bl_description = "List objects visibility conflicts, when viewport and render have different values" + bl_options = {"REGISTER"} + + def invoke(self, context, event): + self.ob_list = [o for o in context.scene.objects if o.hide_viewport != o.hide_render] + return context.window_manager.invoke_props_dialog(self, width=250) + + def draw(self, context): + layout = self.layout + for o in self.ob_list: + row = layout.row() + row.label(text=o.name) + row.prop(o, 'hide_viewport', text='', emboss=False) # invert_checkbox=True + row.prop(o, 'hide_render', text='', emboss=False) # invert_checkbox=True + + def execute(self, context): + return {'FINISHED'} + ## basic listing as message box # all in invoke now + # li = [] + # viz_ct = 0 + # for o in context.scene.objects: + # if o.hide_viewport != o.hide_render: + # vp = 'No' if o.hide_viewport else 'Yes' + # rd = 'No' if o.hide_render else 'Yes' + # viz_ct += 1 + # li.append(f'{o.name} : viewport {vp} != render {rd}') + # if li: + # utils.show_message_box(_message=li, _title=f'{viz_ct} visibility conflicts found') + # else: + # self.report({'INFO'}, f"No Object visibility conflict on current scene") + # return {'FINISHED'} + + +## not exposed in UI, Check is performed in Check file (can be called in popped menu) +class GPTB_OT_list_modifier_visibility(bpy.types.Operator): + bl_idname = "gp.list_modifier_visibility" + bl_label = "List GP Modifiers Visibility Conflicts" + bl_description = "List Modifier visibility conflicts, when viewport and render have different values" + bl_options = {"REGISTER"} + + def invoke(self, context, event): + self.ob_list = [] + for o in context.scene.objects: + if o.type != 'GPENCIL': + continue + if not len(o.grease_pencil_modifiers): + continue + + mods = [] + for m in o.grease_pencil_modifiers: + if m.show_viewport != m.show_render: + if not mods: + self.ob_list.append([o, mods]) + mods.append(m) + return context.window_manager.invoke_props_dialog(self, width=250) + + def draw(self, context): + layout = self.layout + for o in self.ob_list: + layout.label(text=o[0].name, icon='OUTLINER_OB_GREASEPENCIL') + for m in o[1]: + row = layout.row() + row.label(text='') + row.label(text=m.name, icon='MODIFIER_ON') + row.prop(m, 'show_viewport', text='', emboss=False) # invert_checkbox=True + row.prop(m, 'show_render', text='', emboss=False) # invert_checkbox=True + + def execute(self, context): + return {'FINISHED'} + ## basic listing as message box # all in invoke now + # li = [] + # oblist = [] + # if self.selection: + # pool = context.selected_objects + # else: + # pool = context.scene.objects + + # for o in pool: + # if o.type != 'GPENCIL': + # continue + # for m in o.grease_pencil_modifiers: + # if m.show_viewport != m.show_render: + # if o not in oblist: + # oblist.append(o) + # li.append(o.name) # just name first time to list mods after + + # vp = 1 if m.show_viewport else 0 + # rd = 1 if m.show_render else 0 + + # mess = f' - modifier {m.name}: viewport {vp} != render {rd}' + # # mess = f'{o.name} - modifier {m.name}: viewport {vp} != render {rd}' + # li.append(mess) + # if li: + # utils.show_message_box(li) + # else: + # self.report({'INFO'}, f"No Gp modifier visibility conflict on {'selection' if self.selection else 'scene'}") + # return {'FINISHED'} + + '''### OLD class GPTB_OT_check_scene(bpy.types.Operator): bl_idname = "gp.scene_check" @@ -367,6 +499,8 @@ class GPTB_OT_check_scene(bpy.types.Operator): classes = ( # GPTB_OT_check_scene, +GPTB_OT_list_object_visibility, +GPTB_OT_list_modifier_visibility, GPTB_OT_file_checker, GPTB_OT_links_checker, ) diff --git a/OP_helpers.py b/OP_helpers.py index a21f9cf..fdddec9 100644 --- a/OP_helpers.py +++ b/OP_helpers.py @@ -351,7 +351,7 @@ class GPTB_OT_toggle_mute_animation(bpy.types.Operator): class GPTB_OT_list_disabled_anims(bpy.types.Operator): bl_idname = "gp.list_disabled_anims" - bl_label = "List disabled anims" + bl_label = "List Disabled Anims" bl_description = "List disabled animations channels in scene. (shit+clic to list only on seleciton)" bl_options = {"REGISTER"} diff --git a/__init__.py b/__init__.py index cb9553c..e66b141 100755 --- a/__init__.py +++ b/__init__.py @@ -15,7 +15,7 @@ bl_info = { "name": "GP toolbox", "description": "Set of tools for Grease Pencil in animation production", "author": "Samuel Bernou, Christophe Seux", -"version": (1, 7, 1), +"version": (1, 7, 2), "blender": (2, 91, 0), "location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "warning": "", @@ -465,6 +465,8 @@ class GPTB_prefs(bpy.types.AddonPreferences): col.prop(self.fixprops, 'set_pivot_median_point') col.prop(self.fixprops, 'disable_guide') col.prop(self.fixprops, 'list_disabled_anim') + col.prop(self.fixprops, 'list_obj_vis_conflict') + col.prop(self.fixprops, 'list_gp_mod_vis_conflict') col.prop(self.fixprops, 'autokey_add_n_replace') #-# col.prop(self.fixprops, 'set_cursor_type') col.prop(self.fixprops, "select_active_tool", icon='RESTRICT_SELECT_OFF') diff --git a/properties.py b/properties.py index d254ef1..6846a88 100755 --- a/properties.py +++ b/properties.py @@ -84,6 +84,16 @@ class GP_PG_FixSettings(bpy.types.PropertyGroup): name="List Disabled Animation", description="Alert if there are disabled animations", default=True, options={'HIDDEN'}) + + list_obj_vis_conflict : BoolProperty( + name="List Object Visibility Conflicts", + description="Alert if some objects have different hide viewport and hide render values", + default=True, options={'HIDDEN'}) + + list_gp_mod_vis_conflict : BoolProperty( + name="List GP Object Modifiers Visibility Conflicts", + description="Alert if some GP modifier have different show viewport and show render values", + default=True, options={'HIDDEN'}) autokey_add_n_replace : BoolProperty( name="Autokey Mode Add and Replace", diff --git a/utils.py b/utils.py index 6afed23..d0142d0 100644 --- a/utils.py +++ b/utils.py @@ -715,12 +715,29 @@ def convert_attr(Attr): ## confirm pop-up message: def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'): + '''Show message box with element passed as string or list + if _message if a list of lists: + if sublist have 2 element: + considered a label [text,icon] + if sublist have 3 element: + considered as an operator [ops_id_name, text, icon] + ''' + def draw(self, context): for l in _message: if isinstance(l, str): self.layout.label(text=l) else: - self.layout.label(text=l[0], icon=l[1]) + if len(l) == 2: # label with icon + self.layout.label(text=l[0], icon=l[1]) + elif len(l) == 3: # ops + self.layout.operator_context = "INVOKE_DEFAULT" + self.layout.operator(l[0], text=l[1], icon=l[2], emboss=False) # <- highligh the entry + + ## offset pnale when using row... + # row = self.layout.row() + # row.label(text=l[1]) + # row.operator(l[0], icon=l[2]) if isinstance(_message, str): _message = [_message]