Skip to content
Merged

5.0 #63

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a558d1a
feat: Add Bioxel asset catalog and layer management functionality
icrdr Jan 21, 2026
ced0898
Add unit tests for bioxel module and documentation
icrdr Jan 22, 2026
b83dd0a
feat: Update .gitattributes and .gitignore for improved asset managem…
icrdr May 25, 2026
e06cadc
feat: Update .gitignore to include scipy_ndimage directory
icrdr May 25, 2026
cd00c2b
feat: Update release workflow and manifest for improved dependency ma…
icrdr May 25, 2026
9ba0c6a
feat: Update .gitignore and add binary files for scipy_ndimage across…
icrdr May 25, 2026
564a47d
feat: Add OPSX commands for archiving, exploring, and proposing changes
icrdr May 25, 2026
3a18d01
feat: Refactor asset library management and update manifest for impro…
icrdr May 25, 2026
2671f48
feat: Downgrade h5py version to 3.14.0 in requirements and manifest
icrdr May 25, 2026
5dbc246
feat: Update Help operator and panel labels to use English terminology
icrdr May 25, 2026
15c53fe
Translate documentation to Chinese, add support format details, and e…
icrdr May 25, 2026
1dd0847
feat: Remove default version setting from extra configuration in mkdo…
icrdr May 25, 2026
89dc08c
fix: Update documentation links to point to the correct URLs for Chin…
icrdr May 25, 2026
3879219
Add documentation for basic usage, supported formats, and performance…
icrdr May 26, 2026
720233b
Add OpenSpec skills for change management and exploration
icrdr Jul 6, 2026
7b91510
feat: Update version to 2.0.1, refactor asset library handling, and e…
icrdr Jul 7, 2026
6fe7e90
fix: downgrade scipy version to 1.16.3 in build.py and pyproject.toml
icrdr Jul 7, 2026
35faebb
Remove deprecated modules and functions from bioxel package
icrdr Jul 7, 2026
efb28ee
feat: Add SKIP_MODULE_NAMES to exclude specific modules during import…
icrdr Jul 7, 2026
0e71ff6
refactor: Remove known limitations section from documentation for EEV…
icrdr Jul 8, 2026
ec87d59
Merge remote-tracking branch 'origin/main' into 5.0
icrdr Jul 8, 2026
433cfa0
Merge branch 'main' of https://github.com/OmooLab/BioxelNodes into 5.0
icrdr Jul 8, 2026
3756330
feat: Implement CLI command registration for bioxelnodes_import_worke…
icrdr Jul 8, 2026
ea76d1a
chore: Update version to 2.0.2 in project files and dependencies
icrdr Jul 8, 2026
8116b24
feat: Enhance volumetric data parsing by adding progress updates and …
icrdr Jul 8, 2026
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "bioxelnodes"
version = "2.0.1"
version = "2.0.2"
description = ""
authors = [{ name = "MaNan", email = "icrdr2010@outlook.com" }]
requires-python = ">=3.11.0,<3.12.dev0"
Expand Down
22 changes: 22 additions & 0 deletions src/bioxelnodes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

auto_load.init()

CLI_COMMAND_NAME = "bioxelnodes_import_worker"


def register():
pcoll = previews.new()
Expand Down Expand Up @@ -41,9 +43,11 @@ def register():

auto_load.register()
menus.add()
register_cli_commands()


def unregister():
unregister_cli_commands()
menus.remove()
auto_load.unregister()

Expand All @@ -56,3 +60,21 @@ def unregister():
for pcoll in PREVIEW_COLLECTIONS.values():
previews.remove(pcoll)
PREVIEW_COLLECTIONS.clear()


def import_worker_cli(args):
from .operators import io_worker

return io_worker.main(args)


def register_cli_commands():
unregister_cli_commands()
bpy.utils.register_cli_command(CLI_COMMAND_NAME, import_worker_cli)


def unregister_cli_commands():
try:
bpy.utils.unregister_cli_command(CLI_COMMAND_NAME)
except Exception:
pass
2 changes: 1 addition & 1 deletion src/bioxelnodes/blender_manifest.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
schema_version = "1.0.0"

id = "bioxelnodes"
version = "2.0.1"
version = "2.0.2"
name = "Bioxel Nodes"
tagline = "For scientific volumetric data visualization in Blender"
maintainer = "Nan <icrdr2010@outlook.com>"
Expand Down
96 changes: 35 additions & 61 deletions src/bioxelnodes/operators/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from ..props import BIOXEL_Series
from ..utils import get_layer_obj, wrapped_label

from ..bioxel.parse import DICOM_EXTS, SUPPORT_EXTS, get_ext
from ..bioxel.parse import DICOM_EXTS, SUPPORT_EXTS, get_ext, parse_volumetric_data

from ..utils import get_cache_dir, progress_update, progress_bar
from ..layer import get_layer_caches, set_layer_caches
Expand Down Expand Up @@ -61,6 +61,14 @@ def remove_progress_bar_safe():
pass


def get_addon_module_name():
package_name = __package__ or "bioxelnodes.operators"
suffix = ".operators"
if package_name.endswith(suffix):
return package_name[: -len(suffix)]
return package_name


def start_worker_process(owner, command: str, payload: dict):
job_dir = Path(tempfile.mkdtemp(prefix="bioxel_import_", dir=str(get_cache_dir())))
config_path = job_dir / "config.json"
Expand All @@ -69,24 +77,29 @@ def start_worker_process(owner, command: str, payload: dict):
cancel_path = job_dir / "cancel"
log_path = job_dir / "worker.log"

addon_name = get_addon_module_name()
config = {
**payload,
"command": command,
"addon_name": addon_name,
"progress_path": str(progress_path),
"result_path": str(result_path),
"cancel_path": str(cancel_path),
}
write_worker_json(config_path, config)
write_worker_json(progress_path, {"factor": 0.0, "text": "Starting..."})
write_worker_json(
progress_path,
{"factor": 0.0, "text": "Starting background Blender..."},
)

worker_path = Path(__file__).with_name("io_worker.py")
print(f"Starting Bioxel worker with addon module: {addon_name}")
cmd = [
bpy.app.binary_path,
"--background",
"--factory-startup",
"--python",
str(worker_path),
"--",
"--addons",
addon_name,
"--command",
"bioxelnodes_import_worker",
str(config_path),
]

Expand Down Expand Up @@ -188,7 +201,6 @@ def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}


class ImportData(bpy.types.Operator, ImportDataBase):
bl_idname = "bioxel.import_data"
bl_label = "Import Data"
Expand Down Expand Up @@ -291,64 +303,26 @@ class ParseVolumetricData(bpy.types.Operator):

def execute(self, context):
print("Collecting Meta Data...")
self.is_cancelled = False
self.has_error = None
self.meta = None
self.label_count = 0
self.dtype = None

start_worker_process(
self,
"read_meta",
{
"filepath": self.filepath,
"series_id": self.series_id,
},
)
progress_update(context, 0.0, "Collecting Meta Data...")

self._timer = context.window_manager.event_timer_add(
time_step=0.1, window=context.window
)
bpy.types.STATUSBAR_HT_header.append(progress_bar)
context.window_manager.modal_handler_add(self)
return {"RUNNING_MODAL"}
def progress_callback(factor, text):
progress_update(context, factor, text)

def modal(self, context, event):
if event.type == "ESC":
cancel_worker_process(self, context)
return {"PASS_THROUGH"}

if event.type != "TIMER":
return {"PASS_THROUGH"}

bpy.context.workspace.status_text_set_internal(None)
update_worker_progress(self, context)

if self.process.poll() is None:
return {"PASS_THROUGH"}
try:
series_id = self.series_id if self.series_id != "empty" else ""
data, meta = parse_volumetric_data(
data_file=self.filepath,
series_id=series_id,
progress_callback=progress_callback,
)
except Exception as e:
raise e

context.window_manager.event_timer_remove(self._timer)
remove_progress_bar_safe()
self.meta = meta
self.label_count = int(np.max(data))
self.dtype = data.dtype
progress_update(context, 1.0)

result = read_worker_json(self.result_path)
if self.is_cancelled or (result and result.get("cancelled")):
self.report({"WARNING"}, "Canncelled by user.")
return {"CANCELLED"}

if not result:
self.report({"ERROR"}, f"Import worker failed. See log: {self.log_path}")
return {"CANCELLED"}

if not result.get("ok"):
print(result.get("traceback", ""))
self.report({"ERROR"}, result.get("error", "Import worker failed."))
return {"CANCELLED"}

self.meta = result["meta"]
self.label_count = int(result["label_count"])
self.dtype = np.dtype(result["dtype"])

for key, value in self.meta.items():
print(f"{key}: {value}")

Expand Down
42 changes: 19 additions & 23 deletions src/bioxelnodes/operators/io_worker.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,7 @@
import json
import sys
import traceback
from pathlib import Path

import numpy as np
import transforms3d

if __package__:
from ..bioxel.layer import Layer
from ..bioxel.parse import parse_volumetric_data
from ..layer import save_layers_to_cache
else:
PACKAGE_PARENT = Path(__file__).resolve().parents[2]
if str(PACKAGE_PARENT) not in sys.path:
sys.path.insert(0, str(PACKAGE_PARENT))

from bioxelnodes.bioxel.layer import Layer
from bioxelnodes.bioxel.parse import parse_volumetric_data
from bioxelnodes.layer import save_layers_to_cache


def get_layer_shape(bioxel_size: float, orig_shape: tuple, orig_spacing: tuple):
shape = (
Expand Down Expand Up @@ -72,6 +55,10 @@ def progress_callback(frame, total):


def read_meta(config, progress_path: Path, cancel_path: Path):
import numpy as np

from ..bioxel.parse import parse_volumetric_data

progress_callback = make_progress_writer(progress_path, cancel_path)
series_id = config["series_id"] if config["series_id"] != "empty" else ""
data, meta = parse_volumetric_data(
Expand All @@ -94,6 +81,13 @@ def read_meta(config, progress_path: Path, cancel_path: Path):


def build_layers(config, progress_path: Path, cancel_path: Path):
import numpy as np
import transforms3d

from ..bioxel.layer import Layer
from ..bioxel.parse import parse_volumetric_data
from ..layer import save_layers_to_cache

write_json(progress_path, {"factor": 0.0, "text": "Parsing Volumetirc Data..."})
progress_callback = make_progress_writer(progress_path, cancel_path, scale=0.2)
data, meta = parse_volumetric_data(
Expand Down Expand Up @@ -229,14 +223,19 @@ def build_layers(config, progress_path: Path, cancel_path: Path):
return {"cache_infos": cache_infos, "added_ids": [item["id"] for item in cache_infos]}


def main():
config_path = Path(sys.argv[-1])
def main(args):
if not args:
print("Usage: blender --command bioxelnodes_import_worker <config_path>")
return 2

config_path = Path(args[0])
config = json.loads(config_path.read_text(encoding="utf-8"))
progress_path = Path(config["progress_path"])
result_path = Path(config["result_path"])
cancel_path = Path(config["cancel_path"])

try:
write_json(progress_path, {"factor": 0.0, "text": "Loading import modules..."})
if config["command"] == "read_meta":
result = read_meta(config, progress_path, cancel_path)
elif config["command"] == "import_layers":
Expand All @@ -257,7 +256,4 @@ def main():
"traceback": traceback.format_exc(),
},
)


if __name__ == "__main__":
main()
return 0
30 changes: 15 additions & 15 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading