Skip to content

Fix problems with relative paths on Windows #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions bseq/callback.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
11 changes: 6 additions & 5 deletions bseq/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
97 changes: 47 additions & 50 deletions bseq/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand Down Expand Up @@ -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'}
Expand All @@ -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)
Expand All @@ -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:
Expand Down
30 changes: 16 additions & 14 deletions bseq/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,45 +21,47 @@ 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
bpy.ops.screen.animation_cancel()

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):
full_path = os.path.join(bpy.path.native_pathsep(obj.BSEQ.path), obj.BSEQ.pattern)
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)
#fs = fileseq.findSequenceOnDisk(fs.dirname() + fs.basename() + "@" + fs.extension())

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:
Expand Down