From 4e00d79c17c2e8e43a9f573359d162e59d3bdeef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20L=C3=B6schner?= Date: Fri, 17 Jan 2025 16:06:45 +0100 Subject: [PATCH 1/2] Fix problems with relative paths on Windows --- bseq/callback.py | 24 +++++++----- bseq/importer.py | 11 +++--- bseq/operators.py | 97 +++++++++++++++++++++++------------------------ bseq/utils.py | 28 +++++++------- 4 files changed, 83 insertions(+), 77 deletions(-) diff --git a/bseq/callback.py b/bseq/callback.py index 6aaff62..079b702 100644 --- a/bseq/callback.py +++ b/bseq/callback.py @@ -1,30 +1,36 @@ import bpy import fileseq +import traceback + +from .utils import show_message_box # Code here are mostly about the callback/update/items functions used in properties.py file_sequences = [] def update_path(self, context): + ''' + Detects all the file sequences in the directory + ''' + # When the path has been changed, reset the selected sequence to None context.scene.BSEQ['fileseq'] = 1 context.scene.BSEQ.use_pattern = False context.scene.BSEQ.pattern = "" - - ''' - Detects all the file sequences in the directory - ''' + file_sequences.clear() p = context.scene.BSEQ.path try: - f = fileseq.findSequencesOnDisk(p) - except: - return [("None", "No sequence detected", "", 1)] + f = fileseq.findSequencesOnDisk(bpy.path.abspath(p)) + except Exception as e: + show_message_box("Error when reading path\n" + traceback.format_exc(), + "fileseq Error" + str(e), + icon="ERROR") + return None if not f: - return [("None", "No sequence detected", "", 1)] + return None - file_sequences.clear() if len(f) >= 30: file_sequences.append(("None", "Too much sequence detected, could be false detection, please use pattern below", "", 1)) else: diff --git a/bseq/importer.py b/bseq/importer.py index 87d377a..3f0f240 100644 --- a/bseq/importer.py +++ b/bseq/importer.py @@ -262,13 +262,14 @@ def create_obj(fileseq, use_relative, root_path, transform_matrix=Matrix.Identit object = bpy.data.objects.new(name, mesh) # create the object + full_path = str(fileseq) + path = os.path.dirname(full_path) + pattern = os.path.basename(full_path) if use_relative: - full_path = get_relative_path(str(fileseq), root_path) - else: - full_path = str(fileseq) + path = get_relative_path(path, root_path) # path is only the directory in which the file is located - object.BSEQ.path = os.path.dirname(full_path) - object.BSEQ.pattern = os.path.basename(full_path) + object.BSEQ.path = path + object.BSEQ.pattern = pattern object.BSEQ.current_file = filepath object.BSEQ.init = True object.BSEQ.enabled = enabled diff --git a/bseq/operators.py b/bseq/operators.py index 5028048..7168868 100644 --- a/bseq/operators.py +++ b/bseq/operators.py @@ -50,7 +50,8 @@ def execute(self, context): fs = importer_prop.path + '/' + importer_prop.pattern try: - fs = fileseq.findSequenceOnDisk(fs) + # Call os.path.abspath in addition because findSequenceOnDisk does not support \..\ components on Windows apparently + fs = fileseq.findSequenceOnDisk(os.path.abspath(bpy.path.abspath(fs))) except Exception as e: show_message_box(traceback.format_exc(), "Can't find sequence: " + str(fs), "ERROR") return {"CANCELLED"} @@ -522,7 +523,7 @@ def execute(self, context): return {'FINISHED'} class BSEQ_OT_load_all(bpy.types.Operator): - """Load all sequences from selected folder""" + """Load all sequences from selected folder and its subfolders""" bl_idname = "bseq.load_all" bl_label = "Load All" bl_options = {'PRESET', 'UNDO'} @@ -533,8 +534,8 @@ def execute(self, context): if importer_prop.use_relative and not bpy.data.is_saved: return relative_path_error() - dir = importer_prop.path - seqs = fileseq.findSequencesOnDisk(str(dir)) + p = importer_prop.path + seqs = fileseq.findSequencesOnDisk(bpy.path.abspath(p)) for s in seqs: print(s) @@ -555,60 +556,56 @@ def execute(self, context): if importer_prop.use_relative and not bpy.data.is_saved: return relative_path_error() - root_dir = importer_prop.path + root_dir = bpy.path.abspath(importer_prop.path) root_coll = bpy.context.scene.collection root_layer_collection = bpy.context.view_layer.layer_collection unlinked_collections = [] - # Recurse through subdirectories - for root, dirs, files in os.walk(bpy.path.abspath(root_dir)): - for dir in sorted(dirs): - # Process subdirectory - subdirectory = os.path.join(root, dir) - - seqs = fileseq.findSequencesOnDisk(subdirectory) - if len(seqs) == 0: - continue - - # Get list of directories from the root_dir to the current subdirectory - coll_list = bpy.path.relpath(subdirectory, start=root_dir).strip("//").split("/") - - # Get or create a nested collection starting from the root - last_coll = root_coll - layer_collection = root_layer_collection - for coll in coll_list: - # If it already exists and is not in the children of the last collection, then the prefix has changed - cur_coll = bpy.data.collections.get(coll) - if cur_coll is not None and last_coll is not None: - if cur_coll.name not in last_coll.children: - # Get the old parent of the existing collection and move the children to the old parent - parent = [c for c in bpy.data.collections if bpy.context.scene.user_of_id(cur_coll) and cur_coll.name in c.children] - if len(parent) > 0: - for child in cur_coll.children: - parent[0].children.link(child) - for obj in cur_coll.objects: - parent[0].objects.link(obj) - parent[0].children.unlink(cur_coll) - unlinked_collections.append(cur_coll) - else: - layer_collection = layer_collection.children[cur_coll.name] - last_coll = cur_coll - - - # If it was newly created, link it to the last collection - if cur_coll is None and last_coll is not None: - cur_coll = bpy.data.collections.new(coll) - last_coll.children.link(cur_coll) + # Recurse through directory itself and subdirectories + for current_dir, subdirs, files in os.walk(root_dir): + seqs = fileseq.findSequencesOnDisk(current_dir) + if len(seqs) == 0: + continue + + # Get list of directories from the root_dir to the current directory + coll_list = bpy.path.relpath(current_dir, start=root_dir).strip("//").split("/") + + # Get or create a nested collection starting from the root + last_coll = root_coll + layer_collection = root_layer_collection + for coll in coll_list: + # If it already exists and is not in the children of the last collection, then the prefix has changed + cur_coll = bpy.data.collections.get(coll) + if cur_coll is not None and last_coll is not None: + if cur_coll.name not in last_coll.children: + # Get the old parent of the existing collection and move the children to the old parent + parent = [c for c in bpy.data.collections if bpy.context.scene.user_of_id(cur_coll) and cur_coll.name in c.children] + if len(parent) > 0: + for child in cur_coll.children: + parent[0].children.link(child) + for obj in cur_coll.objects: + parent[0].objects.link(obj) + parent[0].children.unlink(cur_coll) + unlinked_collections.append(cur_coll) + else: layer_collection = layer_collection.children[cur_coll.name] last_coll = cur_coll - # Set the last collection as the active collection by recursing through the collections - context.view_layer.active_layer_collection = layer_collection - # for s in seqs: - # print(s) + # If it was newly created, link it to the last collection + if cur_coll is None and last_coll is not None: + cur_coll = bpy.data.collections.new(coll) + last_coll.children.link(cur_coll) + layer_collection = layer_collection.children[cur_coll.name] + last_coll = cur_coll + + # Set the last collection as the active collection by recursing through the collections + context.view_layer.active_layer_collection = layer_collection + + # for s in seqs: + # print(s) - for s in seqs: - create_obj_wrapper(s, importer_prop) + for s in seqs: + create_obj_wrapper(s, importer_prop) # Make sure unused datablocks are freed for coll in unlinked_collections: diff --git a/bseq/utils.py b/bseq/utils.py index 22f5ad2..5383e8e 100644 --- a/bseq/utils.py +++ b/bseq/utils.py @@ -21,7 +21,6 @@ def draw(self, context): stop_animation() bpy.context.window_manager.popup_menu(draw, title=title, icon=icon) - def stop_animation(): if bpy.context.screen.is_animation_playing: # if playing animation, then stop it, otherwise it will keep showing message box @@ -29,17 +28,18 @@ def stop_animation(): def get_relative_path(path, root_path): if root_path != "": - path = bpy.path.relpath(path, start=root_path) + rel_path = bpy.path.relpath(path, start=bpy.path.abspath(root_path)) else: - path = bpy.path.relpath(path) - return path + rel_path = bpy.path.relpath(path) + return rel_path # convert relative path to absolute path def convert_to_absolute_path(path, root_path): + # Additional call to os.path.abspath removes any "/../"" in the path (can be a problem on Windows) if root_path != "": - path = bpy.path.abspath(path, start=root_path) + path = os.path.abspath(bpy.path.abspath(path, start=bpy.path.abspath(root_path))) else: - path = bpy.path.abspath(path) + path = os.path.abspath(bpy.path.abspath(path)) return path def get_absolute_path(obj, scene): @@ -47,19 +47,21 @@ def get_absolute_path(obj, scene): full_path = convert_to_absolute_path(full_path, scene.BSEQ.root_path) return full_path - def refresh_obj(obj, scene): is_relative = obj.BSEQ.path.startswith("//") - print("is_relative: ", is_relative) fs = get_absolute_path(obj, scene) fs = fileseq.findSequenceOnDisk(fs) fs = fileseq.findSequenceOnDisk(fs.dirname() + fs.basename() + "@" + fs.extension()) - obj.BSEQ.start_end_frame = (fs.start(), fs.end()) - fs = str(fs) + + full_path = str(fs) + path = os.path.dirname(full_path) + pattern = os.path.basename(full_path) if is_relative: - fs = get_relative_path(fs, scene.BSEQ.root_path) - obj.BSEQ.path = os.path.dirname(fs) - obj.BSEQ.pattern = os.path.basename(fs) + path = get_relative_path(path, scene.BSEQ.root_path) + + obj.BSEQ.path = path + obj.BSEQ.pattern = pattern + obj.BSEQ.start_end_frame = (fs.start(), fs.end()) def load_meshio_from_path(fileseq, filepath, obj = None): try: From 6b61da8f95253828539c41f10a74ca8946ed6171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20L=C3=B6schner?= Date: Tue, 21 Jan 2025 12:35:21 +0100 Subject: [PATCH 2/2] Fix updating of sequences without pattern in filename --- bseq/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bseq/utils.py b/bseq/utils.py index 5383e8e..161542c 100644 --- a/bseq/utils.py +++ b/bseq/utils.py @@ -51,7 +51,7 @@ def refresh_obj(obj, scene): is_relative = obj.BSEQ.path.startswith("//") fs = get_absolute_path(obj, scene) fs = fileseq.findSequenceOnDisk(fs) - fs = fileseq.findSequenceOnDisk(fs.dirname() + fs.basename() + "@" + fs.extension()) + #fs = fileseq.findSequenceOnDisk(fs.dirname() + fs.basename() + "@" + fs.extension()) full_path = str(fs) path = os.path.dirname(full_path)