Skip to content
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: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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

Expand All @@ -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.

Expand Down
18 changes: 11 additions & 7 deletions blender_manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 <contact@srjeske.de>"
Expand All @@ -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.
Expand All @@ -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:
Expand Down Expand Up @@ -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"
]
8 changes: 4 additions & 4 deletions bseq/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"
]
55 changes: 10 additions & 45 deletions bseq/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
146 changes: 73 additions & 73 deletions bseq/operators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down
3 changes: 0 additions & 3 deletions bseq/panels.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)")

Expand Down
1 change: 0 additions & 1 deletion bseq/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 3 additions & 4 deletions download_wheels.sh
Original file line number Diff line number Diff line change
@@ -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
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
Binary file added images/install_marketplace.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions template/Comparison Render.py
Original file line number Diff line number Diff line change
@@ -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 `<original_path>/<sequence_name>/` for each render, where `<original_path>` is the original render output path set in the scene settings, and `<sequence_name>` 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
Expand Down