feat: Add Bioxel asset catalog and layer management functionality

- Introduced a new asset catalog definition file for Blender to manage Bioxel assets.
- Implemented layer caching and snapshot functionality in `layer.py`, allowing for VDB file generation and low-resolution snapshots.
- Created node management functions in `node.py` for handling Bioxel nodes within Blender's Geometry Node Editor.
- Added an operator to extract mesh data from selected nodes in `structure.py`.
- Developed UI panels in `panels.py` for layer management, including import options and layer library display.
This commit is contained in:
2026-01-21 12:42:12 +08:00
parent afae532a2c
commit a558d1ae30
43 changed files with 2933 additions and 3701 deletions

View File

@@ -78,8 +78,8 @@ class Layer():
mask_frame = mask[f, :, :, :]
if smooth > 0:
mask_frame = scipy.median_filter(mask_frame.astype(np.float32),
mode="nearest",
size=smooth)
mode="nearest",
size=smooth)
# mask_frame = scipy.median_filter(
# mask_frame.astype(np.float32), size=2)
mask_frames += (mask_frame,)
@@ -88,8 +88,8 @@ class Layer():
mask_frame = mask[:, :, :]
if smooth > 0:
mask_frame = scipy.median_filter(mask_frame.astype(np.float32),
mode="nearest",
size=smooth)
mode="nearest",
size=smooth)
# mask_frame = scipy.median_filter(
# mask_frame.astype(np.float32), size=2)
mask_frames += (mask_frame,)
@@ -150,3 +150,32 @@ class Layer():
mat_scale = transforms3d.zooms.zfdir2aff(factors[0])
self.affine = np.dot(self.affine, mat_scale)
def snapshot(self, shape: tuple, smooth: int = 0):
if len(shape) != 3:
raise Exception("Shape must be 3 dim")
data = self.data
snapshot = data[0, :, :, :, :]
factors = np.divide(self.shape, shape)
zoom_factors = [1 / f for f in factors]
order = 0 if snapshot.dtype == bool else 1
snapshot = ndi.zoom(snapshot,
zoom_factors+[1.0],
mode="nearest",
grid_mode=False,
order=order)
snapshot = snapshot.astype(np.float32)
if self.kind in ['scalar', 'vector', 'color']:
mn = float(np.nanmin(snapshot))
mx = float(np.nanmax(snapshot))
if mx - mn > 1e-8:
snapshot = (snapshot - mn) / (mx - mn)
else:
snapshot = np.clip(snapshot, 0.0, 1.0)
return snapshot

View File

@@ -39,7 +39,6 @@ from . import _ni_support
from . import _nd_image
def _invalid_origin(origin, lenw):
return (origin < -(lenw // 2)) or (origin > (lenw - 1) // 2)
@@ -73,7 +72,6 @@ def _complex_via_real_components(func, input, weights, output, cval, **kwargs):
return output
def correlate1d(input, weights, axis=-1, output=None, mode="reflect",
cval=0.0, origin=0):
"""Calculate a 1-D correlation along the given axis.
@@ -133,7 +131,6 @@ def correlate1d(input, weights, axis=-1, output=None, mode="reflect",
return output
def convolve1d(input, weights, axis=-1, output=None, mode="reflect",
cval=0.0, origin=0):
"""Calculate a 1-D convolution along the given axis.
@@ -205,7 +202,6 @@ def _gaussian_kernel1d(sigma, order, radius):
return q * phi_x
def gaussian_filter1d(input, sigma, axis=-1, order=0, output=None,
mode="reflect", cval=0.0, truncate=4.0, *, radius=None):
"""1-D Gaussian filter.
@@ -274,7 +270,6 @@ def gaussian_filter1d(input, sigma, axis=-1, order=0, output=None,
return correlate1d(input, weights, axis, output, mode, cval, 0)
def gaussian_filter(input, sigma, order=0, output=None,
mode="reflect", cval=0.0, truncate=4.0, *, radius=None,
axes=None):
@@ -381,7 +376,6 @@ def gaussian_filter(input, sigma, order=0, output=None,
return output
def prewitt(input, axis=-1, output=None, mode="reflect", cval=0.0):
"""Calculate a Prewitt filter.
@@ -443,7 +437,6 @@ def prewitt(input, axis=-1, output=None, mode="reflect", cval=0.0):
return output
def sobel(input, axis=-1, output=None, mode="reflect", cval=0.0):
"""Calculate a Sobel filter.
@@ -501,7 +494,6 @@ def sobel(input, axis=-1, output=None, mode="reflect", cval=0.0):
return output
def generic_laplace(input, derivative2, output=None, mode="reflect",
cval=0.0,
extra_arguments=(),
@@ -549,7 +541,6 @@ def generic_laplace(input, derivative2, output=None, mode="reflect",
return output
def laplace(input, output=None, mode="reflect", cval=0.0):
"""N-D Laplace filter based on approximate second derivatives.
@@ -584,7 +575,6 @@ def laplace(input, output=None, mode="reflect", cval=0.0):
return generic_laplace(input, derivative2, output, mode, cval)
def gaussian_laplace(input, sigma, output=None, mode="reflect",
cval=0.0, **kwargs):
"""Multidimensional Laplace filter using Gaussian second derivatives.
@@ -637,7 +627,6 @@ def gaussian_laplace(input, sigma, output=None, mode="reflect",
extra_keywords=kwargs)
def generic_gradient_magnitude(input, derivative, output=None,
mode="reflect", cval=0.0,
extra_arguments=(), extra_keywords=None):
@@ -690,7 +679,6 @@ def generic_gradient_magnitude(input, derivative, output=None,
return output
def gaussian_gradient_magnitude(input, sigma, output=None,
mode="reflect", cval=0.0, **kwargs):
"""Multidimensional gradient magnitude using Gaussian derivatives.
@@ -792,7 +780,6 @@ def _correlate_or_convolve(input, weights, output, mode, cval, origin,
return output
def correlate(input, weights, output=None, mode='reflect', cval=0.0,
origin=0):
"""
@@ -856,7 +843,6 @@ def correlate(input, weights, output=None, mode='reflect', cval=0.0,
origin, False)
def convolve(input, weights, output=None, mode='reflect', cval=0.0,
origin=0):
"""
@@ -967,7 +953,6 @@ def convolve(input, weights, output=None, mode='reflect', cval=0.0,
origin, True)
def uniform_filter1d(input, size, axis=-1, output=None,
mode="reflect", cval=0.0, origin=0):
"""Calculate a 1-D uniform filter along the given axis.
@@ -1018,7 +1003,6 @@ def uniform_filter1d(input, size, axis=-1, output=None,
return output
def uniform_filter(input, size=3, output=None, mode="reflect",
cval=0.0, origin=0, *, axes=None):
"""Multidimensional uniform filter.
@@ -1088,7 +1072,6 @@ def uniform_filter(input, size=3, output=None, mode="reflect",
return output
def minimum_filter1d(input, size, axis=-1, output=None,
mode="reflect", cval=0.0, origin=0):
"""Calculate a 1-D minimum filter along the given axis.
@@ -1145,7 +1128,6 @@ def minimum_filter1d(input, size, axis=-1, output=None,
return output
def maximum_filter1d(input, size, axis=-1, output=None,
mode="reflect", cval=0.0, origin=0):
"""Calculate a 1-D maximum filter along the given axis.
@@ -1302,7 +1284,6 @@ def _min_or_max_filter(input, size, footprint, structure, output, mode,
return output
def minimum_filter(input, size=None, footprint=None, output=None,
mode="reflect", cval=0.0, origin=0, *, axes=None):
"""Calculate a multidimensional minimum filter.
@@ -1350,7 +1331,6 @@ def minimum_filter(input, size=None, footprint=None, output=None,
cval, origin, 1, axes)
def maximum_filter(input, size=None, footprint=None, output=None,
mode="reflect", cval=0.0, origin=0, *, axes=None):
"""Calculate a multidimensional maximum filter.
@@ -1398,7 +1378,6 @@ def maximum_filter(input, size=None, footprint=None, output=None,
cval, origin, 0, axes)
def _rank_filter(input, rank, size=None, footprint=None, output=None,
mode="reflect", cval=0.0, origin=0, operation='rank',
axes=None):
@@ -1491,7 +1470,6 @@ def _rank_filter(input, rank, size=None, footprint=None, output=None,
return output
def rank_filter(input, rank, size=None, footprint=None, output=None,
mode="reflect", cval=0.0, origin=0, *, axes=None):
"""Calculate a multidimensional rank filter.
@@ -1535,7 +1513,6 @@ def rank_filter(input, rank, size=None, footprint=None, output=None,
origin, 'rank', axes=axes)
def median_filter(input, size=None, footprint=None, output=None,
mode="reflect", cval=0.0, origin=0, *, axes=None):
"""
@@ -1586,7 +1563,6 @@ def median_filter(input, size=None, footprint=None, output=None,
origin, 'median', axes=axes)
def percentile_filter(input, percentile, size=None, footprint=None,
output=None, mode="reflect", cval=0.0, origin=0, *,
axes=None):
@@ -1630,7 +1606,6 @@ def percentile_filter(input, percentile, size=None, footprint=None,
cval, origin, 'percentile', axes=axes)
def generic_filter1d(input, function, filter_size, axis=-1,
output=None, mode="reflect", cval=0.0, origin=0,
extra_arguments=(), extra_keywords=None):
@@ -1718,7 +1693,6 @@ def generic_filter1d(input, function, filter_size, axis=-1,
return output
def generic_filter(input, function, size=None, footprint=None,
output=None, mode="reflect", cval=0.0, origin=0,
extra_arguments=(), extra_keywords=None):
@@ -1846,4 +1820,4 @@ def generic_filter(input, function, size=None, footprint=None,
mode = _ni_support._extend_mode_to_code(mode)
_nd_image.generic_filter(input, function, footprint, output, mode,
cval, origins, extra_arguments, extra_keywords)
return output
return output

View File

@@ -11,7 +11,7 @@ def _prepad_for_spline_filter(input, mode, cval):
npad = 12
if mode == 'grid-constant':
padded = np.pad(input, npad, mode='constant',
constant_values=cval)
constant_values=cval)
elif mode == 'nearest':
padded = np.pad(input, npad, mode='edge')
else:
@@ -21,6 +21,7 @@ def _prepad_for_spline_filter(input, mode, cval):
padded = input
return padded, npad
def spline_filter1d(input, order=3, axis=-1, output=np.float64,
mode='mirror'):
"""

View File

@@ -83,7 +83,8 @@ def _get_output(output, input, shape=None, complex_output=False):
elif isinstance(output, (type, np.dtype)):
# Classes (like `np.float32`) and dtypes are interpreted as dtype
if complex_output and np.dtype(output).kind != 'c':
warnings.warn("promoting specified output dtype to complex", stacklevel=3)
warnings.warn(
"promoting specified output dtype to complex", stacklevel=3)
output = np.promote_types(output, np.complex64)
output = np.zeros(shape, dtype=output)
elif isinstance(output, str):

View File

@@ -7,4 +7,4 @@ def normalize_axis_index(axis, ndim):
if axis < 0:
axis = axis + ndim
return axis
return axis

View File

@@ -3,6 +3,7 @@ import numpy as np
from .. import scipy as ndi
from ._utils import convert_to_float
def _clip_warp_output(input_image, output_image, mode, cval, clip):
"""Clip output image to range of values of input image.

View File

@@ -39,7 +39,8 @@ _integer_types = (
np.uintp,
np.uintc,
)
_integer_ranges = {t: (np.iinfo(t).min, np.iinfo(t).max) for t in _integer_types}
_integer_ranges = {t: (np.iinfo(t).min, np.iinfo(t).max)
for t in _integer_types}
dtype_range = {
bool: (False, True),
np.bool_: (False, True),
@@ -175,7 +176,8 @@ def _scale(a, n, m, copy=True):
# downscale with precision loss
if copy:
b = np.empty(a.shape, _dtype_bits(kind, m))
np.floor_divide(a, 2 ** (n - m), out=b, dtype=a.dtype, casting='unsafe')
np.floor_divide(a, 2 ** (n - m), out=b,
dtype=a.dtype, casting='unsafe')
return b
else:
a //= 2 ** (n - m)
@@ -282,7 +284,8 @@ def _convert(image, dtype, force_copy=False, uniform=False):
return image
if not (dtype_in in _supported_types and dtype_out in _supported_types):
raise ValueError(f'Cannot convert from {dtypeobj_in} to ' f'{dtypeobj_out}.')
raise ValueError(
f'Cannot convert from {dtypeobj_in} to ' f'{dtypeobj_out}.')
if kind_in in 'ui':
imin_in = np.iinfo(dtype_in).min
@@ -318,7 +321,8 @@ def _convert(image, dtype, force_copy=False, uniform=False):
if not uniform:
if kind_out == 'u':
image_out = np.multiply(image, imax_out, dtype=computation_type)
image_out = np.multiply(
image, imax_out, dtype=computation_type)
else:
image_out = np.multiply(
image, (imax_out - imin_out) / 2, dtype=computation_type
@@ -327,7 +331,8 @@ def _convert(image, dtype, force_copy=False, uniform=False):
np.rint(image_out, out=image_out)
np.clip(image_out, imin_out, imax_out, out=image_out)
elif kind_out == 'u':
image_out = np.multiply(image, imax_out + 1, dtype=computation_type)
image_out = np.multiply(
image, imax_out + 1, dtype=computation_type)
np.clip(image_out, 0, imax_out, out=image_out)
else:
image_out = np.multiply(