diff --git a/__init__.py b/__init__.py index d256d28..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", @@ -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 @@ -57,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/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(): 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