diff --git a/CHANGELOG.md b/CHANGELOG.md index 47706dc..b99fd1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +1.7.7 + +- feat: add copy path to `check link` ops with multiple path representation choices + 1.7.6 - ui: expose (almost) all keymap added by the addon to allow user for customize/disable as needed diff --git a/OP_file_checker.py b/OP_file_checker.py index 76e84f9..14441a3 100755 --- a/OP_file_checker.py +++ b/OP_file_checker.py @@ -240,6 +240,80 @@ class GPTB_OT_file_checker(bpy.types.Operator): return {"FINISHED"} +class GPTB_OT_copy_string_to_clipboard(bpy.types.Operator): + bl_idname = "gp.copy_string_to_clipboard" + bl_label = "Copy String" + bl_description = "Copy passed string to clipboard" + bl_options = {"REGISTER"} + + string : bpy.props.StringProperty(options={'SKIP_SAVE'}) + + def execute(self, context): + if not self.string: + # self.report({'ERROR'}, 'Nothing to copy') + return {"CANCELLED"} + bpy.context.window_manager.clipboard = self.string + self.report({'INFO'}, f'Copied: {self.string}') + return {"FINISHED"} + +class GPTB_OT_copy_multipath_clipboard(bpy.types.Operator): + bl_idname = "gp.copy_multipath_clipboard" + bl_label = "Choose Path to Copy" + bl_description = "Copy Chosen Path" + bl_options = {"REGISTER"} + + string : bpy.props.StringProperty(options={'SKIP_SAVE'}) + + def invoke(self, context, event): + if not self.string: + return {"CANCELLED"} + self.pathes = [] + + try: + absolute = os.path.abspath(bpy.path.abspath(self.string)) + abs_parent = os.path.dirname(os.path.abspath(bpy.path.abspath(self.string))) + path_abs = str(Path(bpy.path.abspath(self.string)).resolve()) + + except: + # case of invalid / non-accessable path + bpy.context.window_manager.clipboard = self.string + return context.window_manager.invoke_props_dialog(self, width=800) + + self.pathes.append(('Raw Path', self.string)) + self.pathes.append(('Parent', os.path.dirname(self.string))) + + if absolute != self.string: + self.pathes.append(('Absolute', absolute)) + + if absolute != self.string: + self.pathes.append(('Absolute Parent', abs_parent)) + + if absolute != path_abs: + self.pathes.append(('Resolved',path_abs)) + + self.pathes.append(('File name', os.path.basename(self.string))) + + maxlen = max(len(l[1]) for l in self.pathes) + popup_width = 800 + if maxlen < 50: + popup_width = 500 + elif maxlen > 100: + popup_width = 1000 + return context.window_manager.invoke_props_dialog(self, width=popup_width) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.separator() + col = layout.column() + for l in self.pathes: + split=col.split(factor=0.2, align=True) + split.operator('gp.copy_string_to_clipboard', text=l[0], icon='COPYDOWN').string = l[1] + split.label(text=l[1]) + + def execute(self, context): + return {"FINISHED"} + class GPTB_OT_links_checker(bpy.types.Operator): bl_idname = "gp.links_checker" @@ -267,14 +341,28 @@ class GPTB_OT_links_checker(bpy.types.Operator): layout.separator() + # layout = layout.column() # thinner linespace for l in self.all_lnks: - if l[1] == 'LIBRARY_DATA_BROKEN': + if l[1] == 'CANCEL': layout.label(text=l[0], icon=l[1]) - else: - split=layout.split(factor=0.75) + continue + + if l[1] == 'LIBRARY_DATA_BROKEN': + split=layout.split(factor=0.85) split.label(text=l[0], icon=l[1]) - split.operator('wm.path_open', text='Open folder', icon='FILE_FOLDER').filepath = Path(bpy.path.abspath(l[0])).resolve().parent.as_posix() - split.operator('wm.path_open', text='Open file', icon='FILE_TICK').filepath = Path(bpy.path.abspath(l[0])).resolve().as_posix()#os.path.abspath(bpy.path.abspath(dirname(l[0]))) + # layout.label(text=l[0], icon=l[1]) + else: + split=layout.split(factor=0.75, align=True) + split.label(text=l[0], icon=l[1]) + ## resolve() return somethin different than os.path.abspath. + # split.operator('wm.path_open', text='Open folder', icon='FILE_FOLDER').filepath = Path(bpy.path.abspath(l[0])).resolve().parent.as_posix() + # split.operator('wm.path_open', text='Open file', icon='FILE_TICK').filepath = Path(bpy.path.abspath(l[0])).resolve().as_posix() + + split.operator('wm.path_open', text='Open Folder', icon='FILE_FOLDER').filepath = Path(os.path.abspath(bpy.path.abspath(l[0]))).parent.as_posix() + split.operator('wm.path_open', text='Open File', icon='FILE_TICK').filepath = Path(os.path.abspath(bpy.path.abspath(l[0]))).as_posix() + + split.operator('gp.copy_multipath_clipboard', text='Copy Path', icon='COPYDOWN').string = l[0] + # split.operator('gp.copy_string_to_clipboard', text='Copy Path', icon='COPYDOWN').string = l[0] # copy blend path directly def invoke(self, context, event): self.all_lnks = [] @@ -283,19 +371,32 @@ class GPTB_OT_links_checker(bpy.types.Operator): abs_ct = 0 rel_ct = 0 ## check for broken links + viewed = [] 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(): - self.broke_ct += 1 - self.all_lnks.append( (f"{realib.as_posix()}", 'LIBRARY_DATA_BROKEN') )#lfp.as_posix() - else: - if realib.as_posix().startswith('//'): - rel_ct += 1 - self.all_lnks.append( (f"{realib.as_posix()}", 'LINKED') )#lfp.as_posix() + # avoid relisting same path mutliple times + if current in viewed: + continue + # TODO find a proper way to show the number of user of this path... + viewed.append(current) + + realib = Path(current) # path as-is + lfp = Path(lib) # absolute path + + try: # Try because some path may fail parsing + if not lfp.exists(): + self.broke_ct += 1 + self.all_lnks.append( (f"{realib.as_posix()}", 'LIBRARY_DATA_BROKEN') ) else: - abs_ct += 1 - self.all_lnks.append( (f"{realib.as_posix()}", 'LIBRARY_DATA_INDIRECT') )#lfp.as_posix() + if realib.as_posix().startswith('//'): + rel_ct += 1 + self.all_lnks.append( (f"{realib.as_posix()}", 'LINKED') ) + else: + abs_ct += 1 + self.all_lnks.append( (f"{realib.as_posix()}", 'LIBRARY_DATA_INDIRECT') ) + except: + self.broke_ct += 1 + self.all_lnks.append( (f"{current}" , 'CANCEL') ) # error accessing file + if not self.all_lnks: self.report({'INFO'}, 'No external links in files') @@ -316,15 +417,21 @@ class GPTB_OT_links_checker(bpy.types.Operator): else: print(p[0]) # Show in viewport - + + maxlen = max(len(x) for x in viewed) + # if broke_ct == 0: # show_message_box(self.all_lnks, _title = self.title, _icon = 'INFO')# Links # return {"FINISHED"} - try: - self.proj = context.preferences.addons['pipe_sync'].preferences['local_folder'] - except: - self.proj = None - return context.window_manager.invoke_props_dialog(self, width=800) + popup_width = 800 + if maxlen < 50: + popup_width = 500 + elif maxlen > 100: + popup_width = 1000 + + self.proj = os.environ.get('PROJECT_ROOT') + 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): @@ -501,6 +608,8 @@ classes = ( # GPTB_OT_check_scene, GPTB_OT_list_object_visibility, GPTB_OT_list_modifier_visibility, +GPTB_OT_copy_string_to_clipboard, +GPTB_OT_copy_multipath_clipboard, GPTB_OT_file_checker, GPTB_OT_links_checker, ) diff --git a/__init__.py b/__init__.py index dba71e7..a0483ea 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, 6), +"version": (1, 7, 7), "blender": (2, 91, 0), "location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "warning": "",