From 0783423b1860895235bb8b7a21275d53f2866c59 Mon Sep 17 00:00:00 2001
From: Stefan Jeske <stefan.jeske@rwth-aachen.de>
Date: Tue, 2 Apr 2024 18:27:33 +0200
Subject: [PATCH 1/2] 4.1 Compatibility (#34)

* fix importer for weird attributes

* fix for 4.1 compatibility with normals
---
 __init__.py      |  8 ++++++++
 bseq/importer.py | 12 +++++++++---
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/__init__.py b/__init__.py
index d256d28..c56266d 100644
--- a/__init__.py
+++ b/__init__.py
@@ -16,6 +16,14 @@
 current_folder = os.path.dirname(os.path.abspath(__file__))
 if current_folder not in sys.path:
     sys.path.append(current_folder)
+# add paths of external libraries to sys.path
+if os.path.exists(os.path.join(current_folder, "extern")):
+    external_libs = ["fileseq/src", "meshio/src", "python-future/src", "rich"]
+    for lib in external_libs:
+        lib_path = os.path.join(current_folder, "extern", lib)
+        if lib_path not in sys.path:
+            sys.path.append(lib_path)
+
 
 if bpy.context.preferences.filepaths.use_relative_paths == True:
     bpy.context.preferences.filepaths.use_relative_paths = False
diff --git a/bseq/importer.py b/bseq/importer.py
index 2ad93ed..9c67c52 100644
--- a/bseq/importer.py
+++ b/bseq/importer.py
@@ -104,7 +104,7 @@ def create_or_retrieve_attribute(mesh, k, v):
         if len(v.shape) == 2:
             dim = v.shape[1]
             if dim > 3:
-                show_message_box('higher than 3 dimensional attribue, ignored')
+                # show_message_box('higher than 3 dimensional attribue, ignored')
                 return None
             if dim == 1:
                 return mesh.attributes.new(k, "FLOAT", "POINT")
@@ -113,7 +113,7 @@ def create_or_retrieve_attribute(mesh, k, v):
             if dim == 3:
                 return mesh.attributes.new(k, "FLOAT_VECTOR", "POINT")
         if len(v.shape) > 2:
-            show_message_box('more than 2 dimensional tensor, ignored')
+            # show_message_box('more than 2 dimensional tensor, ignored')
             return None
     else:
         return mesh.attributes[k]
@@ -192,6 +192,8 @@ def update_mesh(meshio_mesh, mesh):
     for k, v in meshio_mesh.point_data.items():
         k = "bseq_" + k
         attribute = create_or_retrieve_attribute(mesh, k, v)
+        if attribute is None:
+            continue
         name_string = None
         if attribute.data_type == "FLOAT":
             name_string = "value"
@@ -202,7 +204,11 @@ 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:
-            mesh.use_auto_smooth = True
+            # If blender version is less than 4.1.0, then dont 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):
+                mesh.use_auto_smooth = True
             mesh.normals_split_custom_set_from_vertices(v)
 
     for k, v in meshio_mesh.field_data.items():

From d89c62dc45e77b485f30fcd8a3e4bfa967954ddc Mon Sep 17 00:00:00 2001
From: Stefan Jeske <stefan.jeske@rwth-aachen.de>
Date: Tue, 16 Apr 2024 12:50:01 +0200
Subject: [PATCH 2/2] Recursive load from folder and new template (#35)

* add a template for doing comparison renders

* update comparison render and add recursive sequence loading operator

* increase version number
---
 __init__.py                   |  5 +--
 bseq/__init__.py              |  5 +--
 bseq/operators.py             | 47 +++++++++++++++++++++++++
 bseq/panels.py                |  6 ++--
 docs/conf.py                  |  4 +--
 template/Comparison Render.py | 64 +++++++++++++++++++++++++++++++++++
 6 files changed, 123 insertions(+), 8 deletions(-)
 create mode 100644 template/Comparison Render.py

diff --git a/__init__.py b/__init__.py
index c56266d..97535c0 100644
--- a/__init__.py
+++ b/__init__.py
@@ -2,7 +2,7 @@
     "name": "Sequence Loader",
     "description": "Loader for meshio supported mesh files/ simulation sequences",
     "author": "Interactive Computer Graphics",
-    "version": (0, 3, 1),
+    "version": (0, 3, 2),
     "blender": (4, 0, 0),
     "warning": "",
     "support": "COMMUNITY",
@@ -65,7 +65,8 @@
     # BSEQ_OT_import_zip,
     # BSEQ_OT_delete_zips,
     # BSEQ_addon_preferences,
-    BSEQ_OT_load_all
+    BSEQ_OT_load_all,
+    BSEQ_OT_load_all_recursive
 ]
 
 def register():
diff --git a/bseq/__init__.py b/bseq/__init__.py
index 114296c..17cb3bf 100644
--- a/bseq/__init__.py
+++ b/bseq/__init__.py
@@ -1,5 +1,5 @@
 from bseq.utils import refresh_obj
-from .operators import BSEQ_OT_load, BSEQ_OT_edit, BSEQ_OT_resetpt, BSEQ_OT_resetmesh, BSEQ_OT_resetins, BSEQ_OT_set_as_split_norm, BSEQ_OT_remove_split_norm, BSEQ_OT_disable_selected, BSEQ_OT_enable_selected, BSEQ_OT_refresh_seq, BSEQ_OT_disable_all, BSEQ_OT_enable_all, BSEQ_OT_refresh_sequences, BSEQ_OT_set_start_end_frames, BSEQ_OT_batch_sequences, BSEQ_PT_batch_sequences_settings, BSEQ_OT_meshio_object, BSEQ_OT_import_zip, BSEQ_OT_delete_zips, BSEQ_addon_preferences, BSEQ_OT_load_all
+from .operators import BSEQ_OT_load, BSEQ_OT_edit, BSEQ_OT_resetpt, BSEQ_OT_resetmesh, BSEQ_OT_resetins, BSEQ_OT_set_as_split_norm, BSEQ_OT_remove_split_norm, BSEQ_OT_disable_selected, BSEQ_OT_enable_selected, BSEQ_OT_refresh_seq, BSEQ_OT_disable_all, BSEQ_OT_enable_all, BSEQ_OT_refresh_sequences, BSEQ_OT_set_start_end_frames, BSEQ_OT_batch_sequences, BSEQ_PT_batch_sequences_settings, BSEQ_OT_meshio_object, BSEQ_OT_import_zip, BSEQ_OT_delete_zips, BSEQ_addon_preferences, BSEQ_OT_load_all, BSEQ_OT_load_all_recursive
 from .properties import BSEQ_scene_property, BSEQ_obj_property, BSEQ_mesh_property
 from .panels import BSEQ_UL_Obj_List, BSEQ_List_Panel, BSEQ_Settings, BSEQ_PT_Import, BSEQ_PT_Import_Child1, BSEQ_PT_Import_Child2, BSEQ_Globals_Panel, BSEQ_Advanced_Panel, BSEQ_Templates, BSEQ_UL_Att_List, draw_template
 from .messenger import subscribe_to_selected, unsubscribe_to_selected
@@ -62,5 +62,6 @@ def BSEQ_initialize(scene):
     "BSEQ_OT_import_zip",
     "BSEQ_OT_delete_zips",
     "BSEQ_addon_preferences",
-    "BSEQ_OT_load_all"
+    "BSEQ_OT_load_all",
+    "BSEQ_OT_load_all_recursive"
 ]
diff --git a/bseq/operators.py b/bseq/operators.py
index a7c7582..525f407 100644
--- a/bseq/operators.py
+++ b/bseq/operators.py
@@ -6,6 +6,7 @@
 from .utils import refresh_obj, show_message_box, get_relative_path
 from .importer import create_obj, create_meshio_obj
 import numpy as np
+import os
 
 addon_name = "blendersequenceloader"
 
@@ -541,6 +542,52 @@ def execute(self, context):
         for s in seqs:
             create_obj_wrapper(s, importer_prop)
         return {'FINISHED'}
+
+class BSEQ_OT_load_all_recursive(bpy.types.Operator):
+    """Load all sequences from selected folder recursively"""
+    bl_idname = "bseq.load_all_recursive"
+    bl_label = "Load All Recursive"
+    bl_options = {'PRESET', 'UNDO'}
+
+    def execute(self, context):
+        importer_prop = context.scene.BSEQ
+
+        if importer_prop.use_relative and not bpy.data.is_saved:
+            return relative_path_error()
+
+        root_dir = importer_prop.path
+        # 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 = bpy.context.scene.collection
+                layer_collection = bpy.context.view_layer.layer_collection
+                for coll in coll_list:
+                    cur_coll = bpy.data.collections.get(coll) if bpy.data.collections.get(coll) is not None else bpy.data.collections.new(coll)
+                    if last_coll is not None and cur_coll.name not in last_coll.children:
+                        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)
+        return {'FINISHED'}
     
 
 class BSEQ_OT_meshio_object(bpy.types.Operator, ImportHelper):
diff --git a/bseq/panels.py b/bseq/panels.py
index 598bd79..be1c142 100644
--- a/bseq/panels.py
+++ b/bseq/panels.py
@@ -292,11 +292,13 @@ def draw(self, context):
             col3.prop(importer_prop, "fileseq", text="")
             col4.operator("bseq.refreshall", text='', icon="FILE_REFRESH")
 
-        split = layout.split(factor=0.7)
+        split = layout.split(factor=0.5)
         col1 = split.column()
         col2 = split.column()
         col1.operator("sequence.load")
-        col2.operator("bseq.load_all")
+        row = col2.row()
+        row.operator("bseq.load_all")
+        row.operator("bseq.load_all_recursive")
 
         # split = layout.split(factor=0.5)
         # col1 = split.column()
diff --git a/docs/conf.py b/docs/conf.py
index acfaddc..bf923be 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -7,9 +7,9 @@
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
 
 project = 'blender-sequence-loader'
-copyright = '2022, InteractiveComputerGraphics'
+copyright = '2024, InteractiveComputerGraphics'
 author = 'InteractiveComputerGraphics'
-release = '0.3.1'
+release = '0.3.2'
 
 # -- General configuration ---------------------------------------------------
 # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
diff --git a/template/Comparison Render.py b/template/Comparison Render.py
new file mode 100644
index 0000000..f8b3e57
--- /dev/null
+++ b/template/Comparison Render.py	
@@ -0,0 +1,64 @@
+import bpy
+
+# Utilities for comparison rendering
+def toggle_on_single(obj):
+    obj.hide_render = False
+    if isinstance(obj, bpy.types.Object) and obj.BSEQ.init:
+        obj.BSEQ.enabled = True
+        for child in obj.children:
+            toggle_on_single(child)
+    elif isinstance(obj, bpy.types.Collection):
+        for child in obj.objects:
+            toggle_on_single(child)
+        for child in obj.children:
+            toggle_on_single(child)
+
+def toggle_on(objs):
+    if type(objs) == list:
+        for obj in objs:
+            toggle_on_single(obj)
+    else:
+        toggle_on_single(objs)
+
+def toggle_off_single(obj):
+    obj.hide_render = True
+    if isinstance(obj, bpy.types.Object) and obj.BSEQ.init:
+        obj.BSEQ.enabled = False
+        for child in obj.children:
+            toggle_off_single(child)
+    elif isinstance(obj, bpy.types.Collection):
+        for child in obj.objects:
+            toggle_off_single(child)
+        for child in obj.children:
+            toggle_off_single(child)
+
+def toggle_off(objs):
+    if type(objs) == list:
+        for obj in objs:
+            toggle_off_single(obj)
+    else:
+        toggle_off_single(objs)
+            
+def toggle_off_all():
+    for obj in bpy.data.objects:
+        toggle_off_single(obj)    
+            
+def toggle_on_all():
+    for obj in bpy.data.objects:
+        toggle_on_single(obj)  
+
+# Declare which collection to render comparison for
+# Change this to the name of the collection you want to render
+comparison_collection = "Sequences"
+
+# Iterate over children in the collection
+comparison_objects = list(bpy.data.collections[comparison_collection].children) + list(bpy.data.collections[comparison_collection].objects)
+orig_path = bpy.context.scene.render.filepath
+for obj in comparison_objects:
+    toggle_off(comparison_objects)
+    toggle_on(obj)
+    bpy.context.scene.render.filepath = f"{orig_path}/{obj.name}/"
+#    bpy.ops.render.render(write_still=True)
+    bpy.ops.render.render(animation=True)
+    
+bpy.context.scene.render.filepath = orig_path
\ No newline at end of file
