diff --git a/README.md b/README.md index c51b8bd..0554610 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,12 @@ ![GitHub release (latest by date)](https://img.shields.io/github/v/release/InteractiveComputerGraphics/blender-sequence-loader) [![Documentation Status](https://readthedocs.org/projects/blender-sequence-loader/badge/?version=latest)](https://blender-sequence-loader.readthedocs.io/en/latest/?badge=latest) +## News + +* Now available for installation as an extension from the [Blender Marketplace](https://extensions.blender.org/add-ons/sequence-loader/)! + +*** + This is an addon for Blender 4.2+ (might work with 2.8+ but is not extensively tested on less recent versions) that enables loading of file sequences. All data is loaded *just-in-time* when the Blender frame changes, in order to avoid excessive memory consumption. By default, the addon is able to load vertices, lines, triangles and quads. It is also able to automatically extract triangle and quad surface meshes from tetrahedral and hexahedral volume meshes. Scalar and vector attributes on vertices are also imported for visualization purposes. The addon comes bundled together with [meshio](https://github.com/nschloe/meshio) which enables the loading of geometric data from a multitude of file formats. As stated there, the supported formats are listed in the following. Note that not all of the formats have been tested and some issues may still occur. @@ -22,9 +28,11 @@ It also loads any additional supported data as geometry node attributes that can **DISCLAIMER: This project is still very much under development, so breaking changes may occur at any time!** +- [News](#news) - [1. Installation](#1-installation) - - [1.1 Build from source (optional)](#11-build-from-source-optional) - - [1.2 Install Addon](#12-install-addon) + - [1.1 Install within blender.](#11-install-within-blender) + - [1.2 Build from source (optional)](#12-build-from-source-optional) + - [1.3 Install Addon](#13-install-addon) - [1.3 FAQs](#13-faqs) - [2. How to use](#2-how-to-use) - [1. Load the animation sequence you want](#1-load-the-animation-sequence-you-want) @@ -56,7 +64,15 @@ It also loads any additional supported data as geometry node attributes that can ## 1. Installation -### 1.1 Build from source (optional) +### 1.1 Install within blender. + +1. Go to `Preferences->Get Extensions` tab within Blender. +2. Search for `Sequence Loader` +3. Click install. + +![](images/install_marketplace.png) + +### 1.2 Build from source (optional) 1. Clone the project to download both project and dependencies @@ -71,7 +87,7 @@ git clone https://github.com/InteractiveComputerGraphics/blender-sequence-loader blender --command extension build ``` -### 1.2 Install Addon +### 1.3 Install Addon After obtaining an installable `.zip` file either from the releases page or from manually building the addon, this should be installed into blender. For more information on how to install addons see [here](https://docs.blender.org/manual/en/latest/editors/preferences/addons.html#installing-add-ons) for more details. diff --git a/blender_manifest.toml b/blender_manifest.toml index 5d190ea..7fc3571 100644 --- a/blender_manifest.toml +++ b/blender_manifest.toml @@ -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.7" +version = "0.3.9" name = "Sequence Loader" tagline = "Just-in-time loader for meshio-supported mesh file sequences" maintainer = "Stefan Rhys Jeske " @@ -15,7 +15,7 @@ website = "https://github.com/InteractiveComputerGraphics/blender-sequence-loade # # Optional: tag list defined by Blender and server, see: # # https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html -tags = ["Animation", "Object"] +tags = ["Import-Export"] blender_version_min = "4.2.0" # # Optional: Blender version that the extension does not support, earlier versions are supported. @@ -41,10 +41,9 @@ license = [ # # Optional: bundle 3rd party Python modules. # # https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html wheels = [ - "./wheels/Fileseq-1.15.2-py3-none-any.whl", - "./wheels/future-0.18.3-py3-none-any.whl", - "./wheels/meshio-5.3.4-py3-none-any.whl", - "./wheels/rich-13.7.0-py3-none-any.whl", + "./wheels/fileseq-2.2.1-py3-none-any.whl", + "./wheels/meshio-5.3.5-py3-none-any.whl", + "./wheels/rich-14.2.0-py3-none-any.whl", ] # # Optional: add-ons can list which resources they will require: @@ -77,5 +76,10 @@ paths_exclude_pattern = [ "/docs/", "/images/", "build_addon.py", - "download_wheels.sh" + "download_wheels.sh", + ".readthedocs.yaml", + ".gitignore", + ".gitmodules", + "template/dim3.py", + "template/template.py" ] \ No newline at end of file diff --git a/bseq/__init__.py b/bseq/__init__.py index 08a8e37..7bd5af0 100644 --- a/bseq/__init__.py +++ b/bseq/__init__.py @@ -1,5 +1,5 @@ from .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, BSEQ_OT_load_all_recursive +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_load_all, BSEQ_OT_load_all_recursive #, BSEQ_OT_import_zip, BSEQ_OT_delete_zips, BSEQ_addon_preferences 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,9 +62,9 @@ def BSEQ_initialize(scene): "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_import_zip", + # "BSEQ_OT_delete_zips", + # "BSEQ_addon_preferences", "BSEQ_OT_load_all", "BSEQ_OT_load_all_recursive" ] diff --git a/bseq/importer.py b/bseq/importer.py index 523b6f7..1d0b588 100644 --- a/bseq/importer.py +++ b/bseq/importer.py @@ -306,54 +306,19 @@ def update_obj(scene, depsgraph=None): fs = fileseq.FileSequence(full_path) - if obj.BSEQ.use_advance and obj.BSEQ.script_name: - script = bpy.data.texts[obj.BSEQ.script_name] - try: - exec(script.as_string()) - except Exception as e: - show_message_box(traceback.format_exc(), "running script: " + obj.BSEQ.script_name + " failed: " + str(e), - "ERROR") - continue - - if 'process' in locals(): - user_process = locals()['process'] - try: - user_process(fs, current_frame, obj.data) - obj.BSEQ.current_file = "Controlled by user process" - except Exception as e: - show_message_box("Error when calling user process: " + traceback.format_exc(), icon="ERROR") - del locals()['process'] - # this continue means if process exist, all the remaining code will be ignored, whethere or not error occurs - continue - - elif 'preprocess' in locals(): - user_preprocess = locals()['preprocess'] - try: - meshio_mesh = user_preprocess(fs, current_frame) - obj.BSEQ.current_file = "Controlled by user preprocess" - except Exception as e: - show_message_box("Error when calling user preprocess: " + traceback.format_exc(), icon="ERROR") - # this continue means only if error occures, then goes to next bpy.object - continue - finally: - del locals()['preprocess'] - else: - if obj.BSEQ.match_frames: - fs_frames = fs.frameSet() - if current_frame in fs_frames: - filepath = fs[fs_frames.index(current_frame)] - filepath = os.path.normpath(filepath) - meshio_mesh = load_meshio_from_path(fs, filepath, obj) - else: - meshio_mesh = meshio.Mesh([], []) - else: - filepath = fs[current_frame % len(fs)] + if obj.BSEQ.match_frames: + fs_frames = fs.frameSet() + if current_frame in fs_frames: + filepath = fs[fs_frames.index(current_frame)] filepath = os.path.normpath(filepath) meshio_mesh = load_meshio_from_path(fs, filepath, obj) + else: + meshio_mesh = meshio.Mesh([], []) + else: + filepath = fs[current_frame % len(fs)] + filepath = os.path.normpath(filepath) + meshio_mesh = load_meshio_from_path(fs, filepath, obj) - if not isinstance(meshio_mesh, meshio.Mesh): - show_message_box('function preprocess does not return meshio object', "ERROR") - continue update_mesh(meshio_mesh, obj.data) apply_transformation(meshio_mesh, obj, depsgraph) diff --git a/bseq/operators.py b/bseq/operators.py index 7168868..95d7627 100644 --- a/bseq/operators.py +++ b/bseq/operators.py @@ -440,87 +440,87 @@ def draw(self, context): # if importer_prop.use_relative: # layout.prop(importer_prop, "root_path", text="Root Directory") -class BSEQ_addon_preferences(bpy.types.AddonPreferences): - bl_idname = __package__ - - zips_folder: bpy.props.StringProperty( - name="Zips Folder", - subtype='DIR_PATH', - ) - - def draw(self, context): - # layout = self.layout - # layout.label(text="Please set a folder to store the extracted zip files") - # layout.prop(self, "zips_folder", text="Zips Folder") - pass - -zip_folder_name = '/tmp_zips' - -class BSEQ_OT_import_zip(bpy.types.Operator, ImportHelper): - """Import a zip file""" - bl_idname = "bseq.import_zip" - bl_label = "Import Zip" - bl_options = {'PRESET', 'UNDO'} - - filename_ext = ".zip" - filter_glob: bpy.props.StringProperty( - default="*.zip", - options={'HIDDEN', 'LIBRARY_EDITABLE'}, - ) - - files: bpy.props.CollectionProperty(type=bpy.types.PropertyGroup) +# class BSEQ_addon_preferences(bpy.types.AddonPreferences): +# bl_idname = __package__ + +# zips_folder: bpy.props.StringProperty( +# name="Zips Folder", +# subtype='DIR_PATH', +# ) + +# def draw(self, context): +# # layout = self.layout +# # layout.label(text="Please set a folder to store the extracted zip files") +# # layout.prop(self, "zips_folder", text="Zips Folder") +# pass + +# zip_folder_name = '/tmp_zips' + +# class BSEQ_OT_import_zip(bpy.types.Operator, ImportHelper): +# """Import a zip file""" +# bl_idname = "bseq.import_zip" +# bl_label = "Import Zip" +# bl_options = {'PRESET', 'UNDO'} + +# filename_ext = ".zip" +# filter_glob: bpy.props.StringProperty( +# default="*.zip", +# options={'HIDDEN', 'LIBRARY_EDITABLE'}, +# ) + +# files: bpy.props.CollectionProperty(type=bpy.types.PropertyGroup) - def execute(self, context): - importer_prop = context.scene.BSEQ - - import zipfile - zip_file = zipfile.ZipFile(self.filepath) - - addon_prefs = context.preferences.addons[addon_name].preferences - # Check if a string is empty: - if not addon_prefs.zips_folder: - show_message_box("Please set a folder to store the extracted zip files", icon="ERROR") - return {"CANCELLED"} - zips_folder = addon_prefs.zips_folder + zip_folder_name - - valid_files = [info.filename for info in zip_file.infolist() if not info.filename.startswith('__MACOSX/')] - zip_file.extractall(zips_folder, members=valid_files) - zip_file.close() - - folder = str(zips_folder) + '/' + str(Path(self.filepath).name)[:-4] - print(folder) - - seqs = fileseq.findSequencesOnDisk(str(folder)) - if not seqs: - show_message_box("No sequences found in the zip file", icon="ERROR") - return {"CANCELLED"} - - for s in seqs: - # Import it with absolute paths - create_obj(s, False, folder, transform_matrix=get_transform_matrix(importer_prop)) +# def execute(self, context): +# importer_prop = context.scene.BSEQ + +# import zipfile +# zip_file = zipfile.ZipFile(self.filepath) + +# addon_prefs = context.preferences.addons[addon_name].preferences +# # Check if a string is empty: +# if not addon_prefs.zips_folder: +# show_message_box("Please set a folder to store the extracted zip files", icon="ERROR") +# return {"CANCELLED"} +# zips_folder = addon_prefs.zips_folder + zip_folder_name + +# valid_files = [info.filename for info in zip_file.infolist() if not info.filename.startswith('__MACOSX/')] +# zip_file.extractall(zips_folder, members=valid_files) +# zip_file.close() + +# folder = str(zips_folder) + '/' + str(Path(self.filepath).name)[:-4] +# print(folder) + +# seqs = fileseq.findSequencesOnDisk(str(folder)) +# if not seqs: +# show_message_box("No sequences found in the zip file", icon="ERROR") +# return {"CANCELLED"} + +# for s in seqs: +# # Import it with absolute paths +# create_obj(s, False, folder, transform_matrix=get_transform_matrix(importer_prop)) - # created_folder = context.scene.BSEQ.imported_zips.add() - # created_folder.path = folder +# # created_folder = context.scene.BSEQ.imported_zips.add() +# # created_folder.path = folder - return {'FINISHED'} +# return {'FINISHED'} -class BSEQ_OT_delete_zips(bpy.types.Operator): - """Delete a zip file""" - bl_idname = "bseq.delete_zips" - bl_label = "Delete Zip" - bl_options = {'PRESET', 'UNDO'} +# class BSEQ_OT_delete_zips(bpy.types.Operator): +# """Delete a zip file""" +# bl_idname = "bseq.delete_zips" +# bl_label = "Delete Zip" +# bl_options = {'PRESET', 'UNDO'} - def execute(self, context): - # folders = context.scene.BSEQ.imported_zips - # for folder in folders: +# def execute(self, context): +# # folders = context.scene.BSEQ.imported_zips +# # for folder in folders: - addon_prefs = context.preferences.addons[addon_name].preferences - zips_folder = addon_prefs.zips_folder + zip_folder_name +# addon_prefs = context.preferences.addons[addon_name].preferences +# zips_folder = addon_prefs.zips_folder + zip_folder_name - import shutil - shutil.rmtree(zips_folder) +# import shutil +# shutil.rmtree(zips_folder) - return {'FINISHED'} +# return {'FINISHED'} class BSEQ_OT_load_all(bpy.types.Operator): """Load all sequences from selected folder and its subfolders""" diff --git a/bseq/panels.py b/bseq/panels.py index c70cdd2..bac1206 100644 --- a/bseq/panels.py +++ b/bseq/panels.py @@ -112,9 +112,6 @@ def draw(self, context): if not obj.BSEQ.init: return - col1.label(text='Script') - col2.prop_search(obj.BSEQ, 'script_name', bpy.data, 'texts', text="") - # geometry nodes settings layout.label(text="Geometry Nodes (select sequence first)") diff --git a/bseq/properties.py b/bseq/properties.py index dd93647..a2f5fe5 100644 --- a/bseq/properties.py +++ b/bseq/properties.py @@ -121,7 +121,6 @@ class BSEQ_obj_property(bpy.types.PropertyGroup): name="Activate/Deactivate", description="If deactivated, sequence won't be updated each frame") use_advance: bpy.props.BoolProperty(default=False) - script_name: bpy.props.StringProperty(name="Script name") path: bpy.props.StringProperty(name="Path of sequence", subtype="DIR_PATH", options={'PATH_SUPPORTS_BLEND_RELATIVE' if bpy.app.version >= (4, 5, 0) else ''}) pattern: bpy.props.StringProperty(name="Pattern of sequence") current_file: bpy.props.StringProperty(description="File of sequence that is currently loaded") diff --git a/docs/conf.py b/docs/conf.py index 3b2ec6b..6903eac 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,7 +9,7 @@ project = 'Sequence Loader' copyright = '2025, InteractiveComputerGraphics' author = 'InteractiveComputerGraphics' -release = '0.3.7' +release = '0.3.9' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/download_wheels.sh b/download_wheels.sh index 1abd6b0..2d8bd86 100755 --- a/download_wheels.sh +++ b/download_wheels.sh @@ -1,4 +1,3 @@ -pip wheel fileseq==1.15.2 -w ./wheels --no-deps -pip wheel meshio==5.3.4 -w ./wheels --no-deps -pip wheel future==0.18.3 -w ./wheels --no-deps -pip wheel rich==13.7.0 -w ./wheels --no-deps \ No newline at end of file +pip wheel fileseq==2.2.1 -w ./wheels --no-deps +pip wheel meshio==5.3.5 -w ./wheels --no-deps +pip wheel rich==14.2.0 -w ./wheels --no-deps \ No newline at end of file diff --git a/images/install_marketplace.png b/images/install_marketplace.png new file mode 100644 index 0000000..b4d676d Binary files /dev/null and b/images/install_marketplace.png differ diff --git a/template/Comparison Render.py b/template/Comparison Render.py index f8b3e57..b2e3acd 100644 --- a/template/Comparison Render.py +++ b/template/Comparison Render.py @@ -1,3 +1,14 @@ +""" +This template script allows you to render each sequence in a specified collection. It toggles the visibility of each sequence one at a time, disabling all others and rendering them individually. + +This is mainly useful for creating comparison renders of different sequences in a scene, such as for different models of a physical simulation that were run outside of Blender. + +The path is automatically set to `//` for each render, where `` is the original render output path set in the scene settings, and `` is the name of the sequence being rendered. + +Usage: +1. Set the `comparison_collection` variable to the name of the collection containing the sequences you want to render. +2. Run the script in Blender's scripting environment. +""" import bpy # Utilities for comparison rendering