Improve file checker
4.0.2 changed: File checker doest not fix directly when clicked: - List potential change and display an Apply fix changed: Enhanced visibility conflict list: - Also include viewlayer hide value - Allow to set all hide value from the state of one of the threemaster
parent
58e6816e39
commit
0cee6163aa
|
@ -1,5 +1,14 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
4.0.2
|
||||||
|
|
||||||
|
changed: File checker doest not fix directly when clicked:
|
||||||
|
- List potential change and display an Apply fix
|
||||||
|
changed: Enhanced visibility conflict list:
|
||||||
|
- Also include viewlayer hide value
|
||||||
|
- Allow to set all hide value from the state of one of the three
|
||||||
|
- fixed: material move operator
|
||||||
|
|
||||||
4.0.1
|
4.0.1
|
||||||
|
|
||||||
- fixed: layer nav operator on page up/down
|
- fixed: layer nav operator on page up/down
|
||||||
|
|
|
@ -4,6 +4,11 @@ from pathlib import Path
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from . import utils
|
from . import utils
|
||||||
|
|
||||||
|
from bpy.props import (BoolProperty,
|
||||||
|
PointerProperty,
|
||||||
|
CollectionProperty,
|
||||||
|
StringProperty)
|
||||||
|
|
||||||
def remove_stroke_exact_duplications(apply=True):
|
def remove_stroke_exact_duplications(apply=True):
|
||||||
'''Remove accidental stroke duplication (points exactly in the same place)
|
'''Remove accidental stroke duplication (points exactly in the same place)
|
||||||
:apply: Remove the duplication instead of just listing dupes
|
:apply: Remove the duplication instead of just listing dupes
|
||||||
|
@ -53,6 +58,10 @@ class GPTB_OT_file_checker(bpy.types.Operator):
|
||||||
# Disable use light on all object
|
# Disable use light on all object
|
||||||
# Remove redundant strokes in frames
|
# Remove redundant strokes in frames
|
||||||
|
|
||||||
|
apply_fixes : bpy.props.BoolProperty(name="Apply Fixes", default=False,
|
||||||
|
description="Apply possible fixes instead of just listing (pop the list again in fix mode)",
|
||||||
|
options={'SKIP_SAVE'})
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
# need some self-control (I had to...)
|
# need some self-control (I had to...)
|
||||||
self.ctrl = event.ctrl
|
self.ctrl = event.ctrl
|
||||||
|
@ -63,10 +72,14 @@ class GPTB_OT_file_checker(bpy.types.Operator):
|
||||||
fix = prefs.fixprops
|
fix = prefs.fixprops
|
||||||
problems = []
|
problems = []
|
||||||
|
|
||||||
apply = not fix.check_only
|
## Old method : Apply fixes based on pref (inverted by ctrl key)
|
||||||
|
# apply = not fix.check_only
|
||||||
|
# # If Ctrl is pressed, invert behavior (invert boolean)
|
||||||
|
# apply ^= self.ctrl
|
||||||
|
|
||||||
# If Ctrl is pressed, invert behavior (invert boolean)
|
apply = self.apply_fixes
|
||||||
apply ^= self.ctrl
|
if self.ctrl:
|
||||||
|
apply = True
|
||||||
|
|
||||||
## Lock main cam:
|
## Lock main cam:
|
||||||
if fix.lock_main_cam:
|
if fix.lock_main_cam:
|
||||||
|
@ -169,13 +182,14 @@ class GPTB_OT_file_checker(bpy.types.Operator):
|
||||||
if fix.list_obj_vis_conflict:
|
if fix.list_obj_vis_conflict:
|
||||||
viz_ct = 0
|
viz_ct = 0
|
||||||
for o in context.scene.objects:
|
for o in context.scene.objects:
|
||||||
if o.hide_viewport != o.hide_render:
|
if not (o.hide_get() == o.hide_viewport == o.hide_render):
|
||||||
|
hv = 'No' if o.hide_get() else 'Yes'
|
||||||
vp = 'No' if o.hide_viewport else 'Yes'
|
vp = 'No' if o.hide_viewport else 'Yes'
|
||||||
rd = 'No' if o.hide_render else 'Yes'
|
rd = 'No' if o.hide_render else 'Yes'
|
||||||
viz_ct += 1
|
viz_ct += 1
|
||||||
print(f'{o.name} : viewport {vp} != render {rd}')
|
print(f'{o.name} : viewlayer {hv} - viewport {vp} - render {rd}')
|
||||||
if viz_ct:
|
if viz_ct:
|
||||||
problems.append(['gp.list_object_visibility', f'{viz_ct} objects visibility conflicts (details in console)', 'OBJECT_DATAMODE'])
|
problems.append(['gp.list_object_visibility_conflicts', f'{viz_ct} objects visibility conflicts (details in console)', 'OBJECT_DATAMODE'])
|
||||||
|
|
||||||
## GP modifiers visibility conflict
|
## GP modifiers visibility conflict
|
||||||
if fix.list_gp_mod_vis_conflict:
|
if fix.list_gp_mod_vis_conflict:
|
||||||
|
@ -281,8 +295,12 @@ class GPTB_OT_file_checker(bpy.types.Operator):
|
||||||
else:
|
else:
|
||||||
print(p[0])
|
print(p[0])
|
||||||
|
|
||||||
|
if not self.apply_fixes:
|
||||||
|
## button to call the operator again with apply_fixes set to True
|
||||||
|
problems.append(['OPERATOR', 'gp.file_checker', 'Apply Fixes', 'FORWARD', {'apply_fixes': True}])
|
||||||
|
|
||||||
# Show in viewport
|
# Show in viewport
|
||||||
title = "Changed Settings" if apply else "Checked Settings (dry run, nothing changed)"
|
title = "Changed Settings" if apply else "Checked Settings (nothing changed)"
|
||||||
utils.show_message_box(problems, _title = title, _icon = 'INFO')
|
utils.show_message_box(problems, _title = title, _icon = 'INFO')
|
||||||
else:
|
else:
|
||||||
self.report({'INFO'}, 'All good')
|
self.report({'INFO'}, 'All good')
|
||||||
|
@ -482,45 +500,10 @@ class GPTB_OT_links_checker(bpy.types.Operator):
|
||||||
return context.window_manager.invoke_props_dialog(self, width=popup_width)
|
return context.window_manager.invoke_props_dialog(self, width=popup_width)
|
||||||
|
|
||||||
|
|
||||||
""" 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):
|
class GPTB_OT_list_viewport_render_visibility(bpy.types.Operator):
|
||||||
all_lnks = []
|
bl_idname = "gp.list_viewport_render_visibility"
|
||||||
has_broken_link = False
|
bl_label = "List Viewport And Render Visibility Conflicts"
|
||||||
## 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_description = "List objects visibility conflicts, when viewport and render have different values"
|
||||||
bl_options = {"REGISTER"}
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
|
@ -539,20 +522,142 @@ class GPTB_OT_list_object_visibility(bpy.types.Operator):
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
## basic listing as message box # all in invoke now
|
|
||||||
# li = []
|
### -- Sync visibility ops (Could be fused in one ops, but having 3 different operators allow to call from search menu)
|
||||||
# viz_ct = 0
|
class GPTB_OT_sync_visibility_from_viewlayer(bpy.types.Operator):
|
||||||
# for o in context.scene.objects:
|
bl_idname = "gp.sync_visibility_from_viewlayer"
|
||||||
# if o.hide_viewport != o.hide_render:
|
bl_label = "Sync Visibility From Viewlayer"
|
||||||
# vp = 'No' if o.hide_viewport else 'Yes'
|
bl_description = "Set viewport and render visibility to match viewlayer visibility"
|
||||||
# rd = 'No' if o.hide_render else 'Yes'
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
# viz_ct += 1
|
|
||||||
# li.append(f'{o.name} : viewport {vp} != render {rd}')
|
def execute(self, context):
|
||||||
# if li:
|
for obj in context.scene.objects:
|
||||||
# utils.show_message_box(_message=li, _title=f'{viz_ct} visibility conflicts found')
|
is_hidden = obj.hide_get() # Get viewlayer visibility
|
||||||
# else:
|
obj.hide_viewport = is_hidden
|
||||||
# self.report({'INFO'}, f"No Object visibility conflict on current scene")
|
obj.hide_render = is_hidden
|
||||||
# return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class GPTB_OT_sync_visibility_from_viewport(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.sync_visibility_from_viewport"
|
||||||
|
bl_label = "Sync Visibility From Viewport"
|
||||||
|
bl_description = "Set viewlayer and render visibility to match viewport visibility"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
for obj in context.scene.objects:
|
||||||
|
is_hidden = obj.hide_viewport
|
||||||
|
obj.hide_set(is_hidden)
|
||||||
|
obj.hide_render = is_hidden
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class GPTB_OT_sync_visibility_from_render(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.sync_visibility_from_render"
|
||||||
|
bl_label = "Sync Visibility From Render"
|
||||||
|
bl_description = "Set viewlayer and viewport visibility to match render visibility"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
for obj in context.scene.objects:
|
||||||
|
is_hidden = obj.hide_render
|
||||||
|
obj.hide_set(is_hidden)
|
||||||
|
obj.hide_viewport = is_hidden
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class GPTB_OT_sync_visibible_to_render(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.sync_visibible_to_render"
|
||||||
|
bl_label = "Sync Overall Viewport Visibility To Render"
|
||||||
|
bl_description = "Set render visibility from"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
for obj in context.scene.objects:
|
||||||
|
## visible_get is the current visibility status combination of hide_viewport and viewlayer hide (eye)
|
||||||
|
obj.hide_render = not obj.visible_get()
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
class GPTB_PG_object_visibility(bpy.types.PropertyGroup):
|
||||||
|
"""Property group to handle object visibility"""
|
||||||
|
is_hidden: BoolProperty(
|
||||||
|
name="Hide in Viewport",
|
||||||
|
description="Toggle object visibility in viewport",
|
||||||
|
get=lambda self: self.get("is_hidden", False),
|
||||||
|
set=lambda self, value: self.set_visibility(value)
|
||||||
|
)
|
||||||
|
|
||||||
|
object_name: StringProperty(name="Object Name")
|
||||||
|
|
||||||
|
def set_visibility(self, value):
|
||||||
|
"""Set the visibility using hide_set()"""
|
||||||
|
obj = bpy.context.view_layer.objects.get(self.object_name)
|
||||||
|
if obj:
|
||||||
|
obj.hide_set(value)
|
||||||
|
self["is_hidden"] = value
|
||||||
|
|
||||||
|
class GPTB_OT_list_object_visibility_conflicts(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.list_object_visibility_conflicts"
|
||||||
|
bl_label = "List Object Visibility Conflicts"
|
||||||
|
bl_description = "List objects visibility conflicts, when viewport and render have different values"
|
||||||
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
|
visibility_items: CollectionProperty(type=GPTB_PG_object_visibility) # type: ignore[valid-type]
|
||||||
|
|
||||||
|
## options:
|
||||||
|
# check_viewlayer : BoolProperty(name="Check Viewlayer", default=False, description="Compare viewlayer (eye) visibility")
|
||||||
|
# check_viewport : BoolProperty(name="Check Viewport", default=False, description="Compare Viewport (screen icon) visibility")
|
||||||
|
# check_render : BoolProperty(name="Check Viewport", default=False, description="Compare Render visibility")
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
# Clear and rebuild both collections
|
||||||
|
self.visibility_items.clear()
|
||||||
|
|
||||||
|
# Store objects with conflicts
|
||||||
|
# objects_with_conflicts = [o for o in context.scene.objects if not (o.hide_get() == o.hide_viewport == o.hide_render)]
|
||||||
|
|
||||||
|
|
||||||
|
# Create visibility items in same order
|
||||||
|
for obj in context.scene.objects: # objects_with_conflicts
|
||||||
|
item = self.visibility_items.add()
|
||||||
|
item.object_name = obj.name
|
||||||
|
item["is_hidden"] = obj.hide_get()
|
||||||
|
|
||||||
|
return context.window_manager.invoke_props_dialog(self, width=250)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
# row.prop(self, "check_viewlayer")
|
||||||
|
# row.prop(self, "check_viewport")
|
||||||
|
# row.prop(self, "check_render")
|
||||||
|
## If filtered by prop, displayed list will resize while applying changes ! (not good)
|
||||||
|
|
||||||
|
# Add sync buttons at the top
|
||||||
|
row = layout.row(align=False)
|
||||||
|
row.label(text="Sync All Visibility From:")
|
||||||
|
row.operator("gp.sync_visibility_from_viewlayer", text="", icon='HIDE_OFF')
|
||||||
|
row.operator("gp.sync_visibility_from_viewport", text="", icon='RESTRICT_VIEW_OFF')
|
||||||
|
row.operator("gp.sync_visibility_from_render", text="", icon='RESTRICT_RENDER_OFF')
|
||||||
|
layout.separator()
|
||||||
|
|
||||||
|
col = layout.column()
|
||||||
|
# We can safely iterate over visibility_items since objects are stored in same order
|
||||||
|
for vis_item in self.visibility_items:
|
||||||
|
obj = context.view_layer.objects.get(vis_item.object_name)
|
||||||
|
if not obj:
|
||||||
|
continue
|
||||||
|
|
||||||
|
row = col.row(align=False)
|
||||||
|
row.label(text=obj.name)
|
||||||
|
|
||||||
|
## Viewlayer visibility "as prop" to allow slide toggle
|
||||||
|
# hide_icon='HIDE_ON' if vis_item.is_hidden else 'HIDE_OFF'
|
||||||
|
hide_icon='HIDE_ON' if obj.hide_get() else 'HIDE_OFF'
|
||||||
|
row.prop(vis_item, "is_hidden", text="", icon=hide_icon, emboss=False)
|
||||||
|
|
||||||
|
# Direct object properties
|
||||||
|
row.prop(obj, 'hide_viewport', text='', emboss=False)
|
||||||
|
row.prop(obj, 'hide_render', text='', emboss=False)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
## not exposed in UI, Check is performed in Check file (can be called in popped menu)
|
## 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):
|
class GPTB_OT_list_modifier_visibility(bpy.types.Operator):
|
||||||
|
@ -594,7 +699,13 @@ class GPTB_OT_list_modifier_visibility(bpy.types.Operator):
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
GPTB_OT_list_object_visibility,
|
GPTB_OT_list_viewport_render_visibility, # Only viewport and render
|
||||||
|
GPTB_OT_sync_visibility_from_viewlayer,
|
||||||
|
GPTB_OT_sync_visibility_from_viewport,
|
||||||
|
GPTB_OT_sync_visibility_from_render,
|
||||||
|
GPTB_OT_sync_visibible_to_render,
|
||||||
|
GPTB_PG_object_visibility,
|
||||||
|
GPTB_OT_list_object_visibility_conflicts,
|
||||||
GPTB_OT_list_modifier_visibility,
|
GPTB_OT_list_modifier_visibility,
|
||||||
GPTB_OT_copy_string_to_clipboard,
|
GPTB_OT_copy_string_to_clipboard,
|
||||||
GPTB_OT_copy_multipath_clipboard,
|
GPTB_OT_copy_multipath_clipboard,
|
||||||
|
|
|
@ -625,9 +625,9 @@ class GPTB_prefs(bpy.types.AddonPreferences):
|
||||||
layout.label(text='Following checks will be made when clicking "Check File" button:')
|
layout.label(text='Following checks will be made when clicking "Check File" button:')
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
col.use_property_split = True
|
col.use_property_split = True
|
||||||
col.prop(self.fixprops, 'check_only')
|
# col.prop(self.fixprops, 'check_only')
|
||||||
col.label(text='If dry run is checked, no modification is done', icon='INFO')
|
col.label(text='The Popup list possible fixes, you can then use the "Apply Fixes"', icon='INFO')
|
||||||
col.label(text='Use Ctrl + Click on "Check File" button to invert the behavior', icon='BLANK1')
|
# col.label(text='Use Ctrl + Click on "Check File" to abply directly', icon='BLANK1')
|
||||||
col.separator()
|
col.separator()
|
||||||
col.prop(self.fixprops, 'lock_main_cam')
|
col.prop(self.fixprops, 'lock_main_cam')
|
||||||
col.prop(self.fixprops, 'set_scene_res', text=f'Reset Scene Resolution (to {self.render_res_x}x{self.render_res_y})')
|
col.prop(self.fixprops, 'set_scene_res', text=f'Reset Scene Resolution (to {self.render_res_x}x{self.render_res_y})')
|
||||||
|
|
|
@ -31,10 +31,6 @@ def update_layer_name(self, context):
|
||||||
|
|
||||||
class GP_PG_FixSettings(PropertyGroup):
|
class GP_PG_FixSettings(PropertyGroup):
|
||||||
|
|
||||||
check_only : BoolProperty(
|
|
||||||
name="Dry run mode (Check only)",
|
|
||||||
description="Do not change anything, just print the messages",
|
|
||||||
default=False, options={'HIDDEN'})
|
|
||||||
|
|
||||||
lock_main_cam : BoolProperty(
|
lock_main_cam : BoolProperty(
|
||||||
name="Lock Main Cam",
|
name="Lock Main Cam",
|
||||||
|
|
34
utils.py
34
utils.py
|
@ -1011,27 +1011,37 @@ def convert_attr(Attr):
|
||||||
def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'):
|
def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'):
|
||||||
'''Show message box with element passed as string or list
|
'''Show message box with element passed as string or list
|
||||||
if _message if a list of lists:
|
if _message if a list of lists:
|
||||||
|
if first element is "OPERATOR":
|
||||||
|
List format: ["OPERATOR", operator_id, text, icon, {prop_name: value, ...}]
|
||||||
if sublist have 2 element:
|
if sublist have 2 element:
|
||||||
considered a label [text,icon]
|
considered a label [text, icon]
|
||||||
if sublist have 3 element:
|
if sublist have 3 element:
|
||||||
considered as an operator [ops_id_name, text, icon]
|
considered as an operator [ops_id_name, text, icon]
|
||||||
|
if sublist have 4 element:
|
||||||
|
considered as a property [object, propname, text, icon]
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
for l in _message:
|
for l in _message:
|
||||||
if isinstance(l, str):
|
if isinstance(l, str):
|
||||||
self.layout.label(text=l)
|
layout.label(text=l)
|
||||||
else:
|
elif l[0] == "OPERATOR": # Special operator case with properties
|
||||||
if len(l) == 2: # label with icon
|
layout.operator_context = "INVOKE_DEFAULT"
|
||||||
self.layout.label(text=l[0], icon=l[1])
|
op = layout.operator(l[1], text=l[2], icon=l[3], emboss=False)
|
||||||
elif len(l) == 3: # ops
|
if len(l) > 4 and isinstance(l[4], dict):
|
||||||
self.layout.operator_context = "INVOKE_DEFAULT"
|
for prop_name, value in l[4].items():
|
||||||
self.layout.operator(l[0], text=l[1], icon=l[2], emboss=False) # <- highligh the entry
|
setattr(op, prop_name, value)
|
||||||
|
|
||||||
## offset pnale when using row...
|
elif len(l) == 2: # label with icon
|
||||||
# row = self.layout.row()
|
layout.label(text=l[0], icon=l[1])
|
||||||
# row.label(text=l[1])
|
elif len(l) == 3: # ops
|
||||||
# row.operator(l[0], icon=l[2])
|
layout.operator_context = "INVOKE_DEFAULT"
|
||||||
|
layout.operator(l[0], text=l[1], icon=l[2], emboss=False) # <- highligh the entry
|
||||||
|
elif len(l) == 4: # prop
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text=l[2], icon=l[3])
|
||||||
|
row.prop(l[0], l[1], text='')
|
||||||
|
|
||||||
if isinstance(_message, str):
|
if isinstance(_message, str):
|
||||||
_message = [_message]
|
_message = [_message]
|
||||||
|
|
Loading…
Reference in New Issue