Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: InteractiveComputerGraphics/blender-sequence-loader
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.3.3
Choose a base ref
...
head repository: InteractiveComputerGraphics/blender-sequence-loader
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
  • 4 commits
  • 6 files changed
  • 2 contributors

Commits on Jan 15, 2025

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    8ad97b5 View commit details

Commits on Mar 11, 2025

  1. Fix problems with relative paths on Windows (#40)

    * Fix problems with relative paths on Windows
    
    * Fix updating of sequences without pattern in filename
    w1th0utnam3 authored Mar 11, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    994b2bb View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    4f8bf38 View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    e0eed7d View commit details
Showing with 92 additions and 85 deletions.
  1. +1 −1 blender_manifest.toml
  2. +15 −9 bseq/callback.py
  3. +11 −9 bseq/importer.py
  4. +47 −50 bseq/operators.py
  5. +16 −14 bseq/utils.py
  6. +2 −2 docs/conf.py
2 changes: 1 addition & 1 deletion blender_manifest.toml
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ schema_version = "1.0.0"
# Example of manifest file for a Blender extension
# Change the values according to your extension
id = "sequence_loader"
version = "0.3.3"
version = "0.3.4"
name = "Blender Sequence Loader"
tagline = "Just-in-time loader for meshio-supported mesh file sequences"
maintainer = "Stefan Rhys Jeske <contact@srjeske.de>"
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:
20 changes: 11 additions & 9 deletions bseq/importer.py
Original file line number Diff line number Diff line change
@@ -162,7 +162,7 @@ def update_mesh(meshio_mesh, mesh):
else:
mesh.clear_geometry()
mesh.vertices.add(n_verts)
mesh.edges.add(len(edge_data))
mesh.edges.add(len(edges))
mesh.loops.add(n_loop)
mesh.polygons.add(n_poly)

@@ -204,7 +204,7 @@ def update_mesh(meshio_mesh, mesh):

# set as split normal per vertex
if mesh.BSEQ.split_norm_att_name and mesh.BSEQ.split_norm_att_name == k:
# If blender version is less than 4.1.0, then dont set auto smooth.
# If blender version is greater than 4.1.0, then don't set auto smooth.
# It has been removed and normals will be used automatically if they are set.
# https://developer.blender.org/docs/release_notes/4.1/python_api/#mesh
if bpy.app.version < (4, 1, 0):
@@ -217,8 +217,9 @@ def update_mesh(meshio_mesh, mesh):

# set split normal per loop per vertex
if mesh.BSEQ.split_norm_att_name and mesh.BSEQ.split_norm_att_name == k:
# Currently hard-coded for .obj files
mesh.use_auto_smooth = True
if bpy.app.version < (4, 1, 0):
mesh.use_auto_smooth = True
# currently hard-coded for .obj files
indices = [item for sublist in meshio_mesh.cell_data["obj:vn_face_idx"][0] for item in sublist]
mesh.normals_split_custom_set([meshio_mesh.field_data["obj:vn"][i - 1] for i in indices])

@@ -261,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
97 changes: 47 additions & 50 deletions bseq/operators.py
Original file line number Diff line number Diff line change
@@ -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:
30 changes: 16 additions & 14 deletions bseq/utils.py
Original file line number Diff line number Diff line change
@@ -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:
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
@@ -7,9 +7,9 @@
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = 'blender-sequence-loader'
copyright = '2024, InteractiveComputerGraphics'
copyright = '2025, InteractiveComputerGraphics'
author = 'InteractiveComputerGraphics'
release = '0.3.3'
release = '0.3.4'

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration