feat: more time sequence choice
fix: contrast formulation cause leak of high value fix: empty series_id cause fail to read chore: restructure folder chore!: change layer vars BREAKING CHANGE: not support 4.1 any more
This commit is contained in:
0
bioxelnodes/bioxelutils/__init__.py
Normal file
0
bioxelnodes/bioxelutils/__init__.py
Normal file
180
bioxelnodes/bioxelutils/container.py
Normal file
180
bioxelnodes/bioxelutils/container.py
Normal file
@@ -0,0 +1,180 @@
|
||||
import bpy
|
||||
|
||||
from ..bioxel.container import Container
|
||||
from bpy_extras.io_utils import axis_conversion
|
||||
from mathutils import Matrix, Vector
|
||||
|
||||
|
||||
from .layer import Layer, get_container_layer_objs, layer_to_obj, obj_to_layer
|
||||
from ..nodes import custom_nodes
|
||||
from .node import get_nodes_by_type, move_node_to_node
|
||||
|
||||
|
||||
NODE_TYPE = {
|
||||
"label": "BioxelNodes_MaskByLabel",
|
||||
"scalar": "BioxelNodes_MaskByThreshold"
|
||||
}
|
||||
|
||||
|
||||
def calc_bbox_verts(origin: tuple, size: tuple):
|
||||
bbox_origin = Vector(
|
||||
(origin[0], origin[1], origin[2]))
|
||||
bbox_size = Vector(
|
||||
(size[0], size[1], size[2]))
|
||||
bbox_verts = [
|
||||
(
|
||||
bbox_origin[0] + 0,
|
||||
bbox_origin[1] + 0,
|
||||
bbox_origin[2] + 0
|
||||
),
|
||||
(
|
||||
bbox_origin[0] + 0,
|
||||
bbox_origin[1] + 0,
|
||||
bbox_origin[2] + bbox_size[2]
|
||||
),
|
||||
(
|
||||
bbox_origin[0] + 0,
|
||||
bbox_origin[1] + bbox_size[1],
|
||||
bbox_origin[2] + 0
|
||||
),
|
||||
(
|
||||
bbox_origin[0] + 0,
|
||||
bbox_origin[1] + bbox_size[1],
|
||||
bbox_origin[2] + bbox_size[2]
|
||||
),
|
||||
(
|
||||
bbox_origin[0] + bbox_size[0],
|
||||
bbox_origin[1] + 0,
|
||||
bbox_origin[2] + 0
|
||||
),
|
||||
(
|
||||
bbox_origin[0] + bbox_size[0],
|
||||
bbox_origin[1] + 0,
|
||||
bbox_origin[2] + bbox_size[2],
|
||||
),
|
||||
(
|
||||
bbox_origin[0] + bbox_size[0],
|
||||
bbox_origin[1] + bbox_size[1],
|
||||
bbox_origin[2] + 0,
|
||||
),
|
||||
(
|
||||
bbox_origin[0] + bbox_size[0],
|
||||
bbox_origin[1] + bbox_size[1],
|
||||
bbox_origin[2] + bbox_size[2],
|
||||
),
|
||||
]
|
||||
return bbox_verts
|
||||
|
||||
|
||||
def get_container_objs_from_selection():
|
||||
container_objs = []
|
||||
for obj in bpy.context.selected_objects:
|
||||
if get_container_obj(obj):
|
||||
container_objs.append(obj)
|
||||
|
||||
return list(set(container_objs))
|
||||
|
||||
|
||||
def get_container_obj(current_obj):
|
||||
if current_obj:
|
||||
if current_obj.get('bioxel_container'):
|
||||
return current_obj
|
||||
elif current_obj.get('bioxel_layer'):
|
||||
parent = current_obj.parent
|
||||
return parent if parent.get('bioxel_container') else None
|
||||
return None
|
||||
|
||||
|
||||
def add_layers(layers: list[Layer],
|
||||
container_obj: bpy.types.Object,
|
||||
cache_dir: str):
|
||||
|
||||
container_node_group = container_obj.modifiers[0].node_group
|
||||
|
||||
for i, layer in enumerate(layers):
|
||||
layer_obj = layer_to_obj(layer, container_obj, cache_dir)
|
||||
mask_node = custom_nodes.add_node(container_node_group,
|
||||
NODE_TYPE[layer.kind])
|
||||
mask_node.label = layer_obj.name
|
||||
mask_node.inputs[0].default_value = layer_obj
|
||||
|
||||
# Connect to output if no output linked
|
||||
output_node = get_nodes_by_type(container_node_group,
|
||||
'NodeGroupOutput')[0]
|
||||
if len(output_node.inputs[0].links) == 0:
|
||||
container_node_group.links.new(mask_node.outputs[0],
|
||||
output_node.inputs[0])
|
||||
move_node_to_node(mask_node, output_node, (-300, 0))
|
||||
else:
|
||||
move_node_to_node(mask_node, output_node, (0, -100 * (i+1)))
|
||||
|
||||
return container_obj
|
||||
|
||||
|
||||
def obj_to_container(container_obj: bpy.types.Object):
|
||||
layer_objs = get_container_layer_objs(container_obj)
|
||||
layers = [obj_to_layer(obj) for obj in layer_objs]
|
||||
container = Container(name=container_obj.name,
|
||||
layers=layers)
|
||||
return container
|
||||
|
||||
|
||||
def container_to_obj(container: Container,
|
||||
scene_scale: float,
|
||||
cache_dir: str):
|
||||
# Wrapper a Container
|
||||
|
||||
# Make transformation
|
||||
# (S)uperior -Z -> Y
|
||||
# (A)osterior Y -> Z
|
||||
mat_ras2blender = axis_conversion(from_forward='-Z',
|
||||
from_up='Y',
|
||||
to_forward='Y',
|
||||
to_up='Z').to_4x4()
|
||||
|
||||
mat_scene_scale = Matrix.Scale(scene_scale,
|
||||
4)
|
||||
|
||||
bpy.ops.mesh.primitive_cube_add(enter_editmode=False,
|
||||
align='WORLD',
|
||||
location=(0, 0, 0),
|
||||
scale=(1, 1, 1))
|
||||
|
||||
container_obj = bpy.context.active_object
|
||||
|
||||
bbox_verts = calc_bbox_verts((0, 0, 0), container.layers[0].shape)
|
||||
for i, vert in enumerate(container_obj.data.vertices):
|
||||
transform = Matrix(container.layers[0].affine)
|
||||
vert.co = transform @ Vector(bbox_verts[i])
|
||||
|
||||
container_obj.matrix_world = mat_ras2blender @ mat_scene_scale
|
||||
container_obj.name = container.name
|
||||
container_obj.data.name = container.name
|
||||
container_obj.show_in_front = True
|
||||
container_obj['bioxel_container'] = True
|
||||
|
||||
bpy.ops.node.new_geometry_nodes_modifier()
|
||||
container_node_group = container_obj.modifiers[0].node_group
|
||||
input_node = get_nodes_by_type(container_node_group,
|
||||
'NodeGroupInput')[0]
|
||||
container_node_group.links.remove(
|
||||
input_node.outputs[0].links[0])
|
||||
|
||||
for i, layer in enumerate(container.layers):
|
||||
layer_obj = layer_to_obj(layer, container_obj, cache_dir)
|
||||
mask_node = custom_nodes.add_node(container_node_group,
|
||||
NODE_TYPE[layer.kind])
|
||||
mask_node.label = layer_obj.name
|
||||
mask_node.inputs[0].default_value = layer_obj
|
||||
|
||||
# Connect to output if no output linked
|
||||
output_node = get_nodes_by_type(container_node_group,
|
||||
'NodeGroupOutput')[0]
|
||||
if len(output_node.inputs[0].links) == 0:
|
||||
container_node_group.links.new(mask_node.outputs[0],
|
||||
output_node.inputs[0])
|
||||
move_node_to_node(mask_node, output_node, (-300, 0))
|
||||
else:
|
||||
move_node_to_node(mask_node, output_node, (0, -100 * (i+1)))
|
||||
|
||||
return container_obj
|
||||
232
bioxelnodes/bioxelutils/layer.py
Normal file
232
bioxelnodes/bioxelutils/layer.py
Normal file
@@ -0,0 +1,232 @@
|
||||
import random
|
||||
import re
|
||||
import bpy
|
||||
import numpy as np
|
||||
|
||||
import pyopenvdb as vdb
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
from ..nodes import custom_nodes
|
||||
from ..bioxel.layer import Layer
|
||||
from .node import get_nodes_by_type, move_node_between_nodes
|
||||
|
||||
|
||||
def get_layer_obj(current_obj: bpy.types.Object):
|
||||
if current_obj:
|
||||
if current_obj.get('bioxel_layer') and current_obj.parent:
|
||||
if current_obj.parent.get('bioxel_container'):
|
||||
return current_obj
|
||||
return None
|
||||
|
||||
|
||||
def get_container_layer_objs(container_obj: bpy.types.Object):
|
||||
layer_objs = []
|
||||
for obj in bpy.context.scene.objects:
|
||||
if obj.parent == container_obj and get_layer_obj(obj):
|
||||
layer_objs.append(obj)
|
||||
|
||||
return layer_objs
|
||||
|
||||
|
||||
def get_all_layer_objs():
|
||||
layer_objs = []
|
||||
for obj in bpy.context.scene.objects:
|
||||
if get_layer_obj(obj):
|
||||
layer_objs.append(obj)
|
||||
|
||||
return layer_objs
|
||||
|
||||
|
||||
def obj_to_layer(layer_obj: bpy.types.Object):
|
||||
cache_filepath = Path(bpy.path.abspath(layer_obj.data.filepath)).resolve()
|
||||
is_sequence = re.search(r'\.\d{4}\.',
|
||||
cache_filepath.name) is not None
|
||||
if is_sequence:
|
||||
cache_path = cache_filepath.parent
|
||||
data_frames = ()
|
||||
for f in cache_path.iterdir():
|
||||
if not f.is_file() or f.suffix != ".vdb":
|
||||
continue
|
||||
grids, base_metadata = vdb.readAll(str(f))
|
||||
grid = grids[0]
|
||||
metadata = grid.metadata
|
||||
data_frame = np.ndarray(grid["data_shape"], np.float32)
|
||||
grid.copyToArray(data_frame)
|
||||
data_frames += (data_frame,)
|
||||
data = np.stack(data_frames)
|
||||
else:
|
||||
grids, base_metadata = vdb.readAll(str(cache_filepath))
|
||||
grid = grids[0]
|
||||
metadata = grid.metadata
|
||||
data = np.ndarray(grid["data_shape"], np.float32)
|
||||
grid.copyToArray(data)
|
||||
data = np.expand_dims(data, axis=0) # expend frame
|
||||
|
||||
name = metadata["layer_name"]
|
||||
kind = metadata["layer_kind"]
|
||||
affine = metadata["layer_affine"]
|
||||
dtype = metadata.get("data_dtype") or "float32"
|
||||
offset = metadata.get("data_offset") or 0
|
||||
data = data - np.full_like(data, offset)
|
||||
data = data.astype(dtype)
|
||||
|
||||
if kind in ["scalar", "label"]:
|
||||
data = np.expand_dims(data, axis=-1) # expend channel
|
||||
|
||||
layer = Layer(data=data,
|
||||
name=name,
|
||||
kind=kind,
|
||||
affine=affine)
|
||||
|
||||
return layer
|
||||
|
||||
|
||||
def layer_to_obj(layer: Layer,
|
||||
container_obj: bpy.types.Object,
|
||||
cache_dir: str):
|
||||
|
||||
data = layer.data
|
||||
|
||||
# TXYZC > TXYZ
|
||||
if layer.kind in ['label', 'scalar']:
|
||||
data = np.amax(data, -1)
|
||||
|
||||
offset = 0
|
||||
if layer.kind in ['scalar']:
|
||||
data = data.astype(np.float32)
|
||||
orig_min = float(np.min(data))
|
||||
if orig_min < 0:
|
||||
offset = -orig_min
|
||||
|
||||
data = data + np.full_like(data, offset)
|
||||
|
||||
metadata = {
|
||||
"layer_name": layer.name,
|
||||
"layer_kind": layer.kind,
|
||||
"layer_affine": layer.affine.tolist(),
|
||||
"data_shape": layer.shape,
|
||||
"data_dtype": layer.data.dtype.str,
|
||||
"data_offset": offset
|
||||
}
|
||||
|
||||
if layer.frame_count > 1:
|
||||
print(f"Saving the Cache of {layer.name}...")
|
||||
vdb_name = str(uuid4())
|
||||
sequence_path = Path(cache_dir, vdb_name)
|
||||
sequence_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
cache_filepaths = []
|
||||
for f in range(layer.frame_count):
|
||||
grid = vdb.FloatGrid()
|
||||
grid.copyFromArray(data[f, :, :, :].copy().astype(np.float32))
|
||||
grid.transform = vdb.createLinearTransform(
|
||||
layer.affine.transpose())
|
||||
grid.metadata = metadata
|
||||
grid.name = layer.kind
|
||||
|
||||
cache_filepath = Path(sequence_path,
|
||||
f"{vdb_name}.{str(f+1).zfill(4)}.vdb")
|
||||
vdb.write(str(cache_filepath), grids=[grid])
|
||||
cache_filepaths.append(cache_filepath)
|
||||
|
||||
print(f"Loading the Cache of {layer.name}...")
|
||||
files = [{"name": str(cache_filepath.name), "name": str(cache_filepath.name)}
|
||||
for cache_filepath in cache_filepaths]
|
||||
|
||||
bpy.ops.object.volume_import(filepath=str(cache_filepaths[0]),
|
||||
directory=str(cache_filepaths[0].parent),
|
||||
files=files, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
|
||||
|
||||
else:
|
||||
grid = vdb.FloatGrid()
|
||||
grid.copyFromArray(data[0, :, :, :].copy().astype(np.float32))
|
||||
grid.transform = vdb.createLinearTransform(
|
||||
layer.affine.transpose())
|
||||
grid.metadata = metadata
|
||||
grid.name = layer.kind
|
||||
|
||||
print(f"Saving the Cache of {layer.name}...")
|
||||
cache_filepath = Path(cache_dir, f"{uuid4()}.vdb")
|
||||
vdb.write(str(cache_filepath), grids=[grid])
|
||||
cache_filepaths = [cache_filepath]
|
||||
|
||||
print(f"Loading the Cache of {layer.name}...")
|
||||
bpy.ops.object.volume_import(filepath=str(cache_filepaths[0]),
|
||||
align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
|
||||
|
||||
layer_obj = bpy.context.active_object
|
||||
layer_obj.data.sequence_mode = 'REPEAT'
|
||||
|
||||
# Set props to VDB object
|
||||
layer_obj.name = f"{container_obj.name}_{layer.name}"
|
||||
layer_obj.data.name = f"{container_obj.name}_{layer.name}"
|
||||
|
||||
layer_obj.lock_location[0] = True
|
||||
layer_obj.lock_location[1] = True
|
||||
layer_obj.lock_location[2] = True
|
||||
layer_obj.lock_rotation[0] = True
|
||||
layer_obj.lock_rotation[1] = True
|
||||
layer_obj.lock_rotation[2] = True
|
||||
layer_obj.lock_scale[0] = True
|
||||
layer_obj.lock_scale[1] = True
|
||||
layer_obj.lock_scale[2] = True
|
||||
|
||||
layer_obj.visible_camera = False
|
||||
layer_obj.visible_diffuse = False
|
||||
layer_obj.visible_glossy = False
|
||||
layer_obj.visible_transmission = False
|
||||
layer_obj.visible_volume_scatter = False
|
||||
layer_obj.visible_shadow = False
|
||||
|
||||
layer_obj.hide_select = True
|
||||
layer_obj.hide_render = True
|
||||
layer_obj.hide_viewport = True
|
||||
|
||||
layer_obj['bioxel_layer'] = True
|
||||
layer_obj.parent = container_obj
|
||||
|
||||
for collection in layer_obj.users_collection:
|
||||
collection.objects.unlink(layer_obj)
|
||||
|
||||
for collection in container_obj.users_collection:
|
||||
collection.objects.link(layer_obj)
|
||||
|
||||
print(f"Creating Node for {layer.name}...")
|
||||
bpy.ops.node.new_geometry_nodes_modifier()
|
||||
node_group = layer_obj.modifiers[0].node_group
|
||||
layer_node = custom_nodes.add_node(node_group,
|
||||
"BioxelNodes__Layer")
|
||||
|
||||
layer_node.inputs['name'].default_value = layer.name
|
||||
layer_node.inputs['shape'].default_value = layer.shape
|
||||
layer_node.inputs['kind'].default_value = layer.kind
|
||||
|
||||
for i in range(layer.affine.shape[1]):
|
||||
for j in range(layer.affine.shape[0]):
|
||||
affine_key = f"affine{i}{j}"
|
||||
layer_node.inputs[affine_key].default_value = layer.affine[j, i]
|
||||
|
||||
layer_node.inputs['id'].default_value = random.randint(-200000000,
|
||||
200000000)
|
||||
layer_node.inputs['bioxel_size'].default_value = layer.bioxel_size[0]
|
||||
layer_node.inputs['dtype'].default_value = layer.dtype.str
|
||||
layer_node.inputs['dtype_num'].default_value = layer.dtype.num
|
||||
layer_node.inputs['offset'].default_value = max(0, -layer.min)
|
||||
layer_node.inputs['min'].default_value = layer.min
|
||||
layer_node.inputs['max'].default_value = layer.max
|
||||
|
||||
input_node = get_nodes_by_type(node_group,
|
||||
'NodeGroupInput')[0]
|
||||
output_node = get_nodes_by_type(node_group,
|
||||
'NodeGroupOutput')[0]
|
||||
|
||||
node_group.links.new(input_node.outputs[0],
|
||||
layer_node.inputs[0])
|
||||
node_group.links.new(layer_node.outputs[0],
|
||||
output_node.inputs[0])
|
||||
|
||||
move_node_between_nodes(
|
||||
layer_node, [input_node, output_node])
|
||||
|
||||
return layer_obj
|
||||
26
bioxelnodes/bioxelutils/node.py
Normal file
26
bioxelnodes/bioxelutils/node.py
Normal file
@@ -0,0 +1,26 @@
|
||||
def move_node_to_node(node, target_node, offset=(0, 0)):
|
||||
node.location.x = target_node.location.x + offset[0]
|
||||
node.location.y = target_node.location.y + offset[1]
|
||||
|
||||
|
||||
def move_node_between_nodes(node, target_nodes, offset=(0, 0)):
|
||||
xs = []
|
||||
ys = []
|
||||
for target_node in target_nodes:
|
||||
xs.append(target_node.location.x)
|
||||
ys.append(target_node.location.y)
|
||||
|
||||
node.location.x = sum(xs) / len(xs) + offset[0]
|
||||
node.location.y = sum(ys) / len(ys) + offset[1]
|
||||
|
||||
|
||||
def get_node_type(node):
|
||||
node_type = type(node).__name__
|
||||
if node_type == "GeometryNodeGroup":
|
||||
node_type = node.node_tree.name
|
||||
|
||||
return node_type
|
||||
|
||||
|
||||
def get_nodes_by_type(node_group, type_name: str):
|
||||
return [node for node in node_group.nodes if get_node_type(node) == type_name]
|
||||
Reference in New Issue
Block a user