feat: improving the user experience

This commit is contained in:
2024-08-20 14:21:10 +08:00
parent 051df670a5
commit 655b19bc7c
29 changed files with 1636 additions and 1167 deletions

View File

@@ -1,5 +1,10 @@
from ast import literal_eval
from pathlib import Path
import bpy
from ..constants import NODE_LIB_FILEPATH, VERSION
from ..utils import get_cache_dir
def move_node_to_node(node, target_node, offset=(0, 0)):
node.location.x = target_node.location.x + offset[0]
@@ -48,20 +53,65 @@ def get_container_obj(current_obj):
return None
def get_layer_prop_value(layer_obj: bpy.types.Object, prop: str):
def get_layer_prop_value(layer_obj: bpy.types.Object, prop_name: str):
node_group = layer_obj.modifiers[0].node_group
layer_node = get_nodes_by_type(node_group, "BioxelNodes__Layer")[0]
value = layer_node.inputs[prop].default_value
prop = layer_node.inputs.get(prop_name)
if prop is None:
return None
value = prop.default_value
if type(value).__name__ == "bpy_prop_array":
value = tuple(value)
return tuple([int(v) for v in value]) \
if prop in ["shape"] else value
elif type(value).__name__ == "str":
return str(value)
if type(value).__name__ == "float":
elif type(value).__name__ == "float":
value = float(value)
return round(value, 2) \
if prop in ["bioxel_size"] else value
elif type(value).__name__ == "int":
value = int(value)
return value
else:
return value
def get_layer_name(layer_obj):
return get_layer_prop_value(layer_obj, "name")
def get_layer_kind(layer_obj):
return get_layer_prop_value(layer_obj, "kind")
def get_layer_label(layer_obj):
name = get_layer_name(layer_obj)
# kind = get_layer_kind(layer_obj)
label = f"{name}"
if is_missing_layer(layer_obj):
return "**MISSING**" + label
elif is_temp_layer(layer_obj):
return "* " + label
else:
return label
def is_missing_layer(layer_obj):
cache_filepath = Path(bpy.path.abspath(
layer_obj.data.filepath)).resolve()
return not cache_filepath.is_file()
def is_temp_layer(layer_obj):
cache_filepath = Path(bpy.path.abspath(
layer_obj.data.filepath)).resolve()
cache_dirpath = Path(get_cache_dir())
return cache_dirpath in cache_filepath.parents
def set_layer_prop_value(layer_obj: bpy.types.Object, prop: str, value):
@@ -95,6 +145,7 @@ def get_all_layer_objs():
return layer_objs
def add_driver(target, target_prop, var_sources, expression):
driver = target.driver_add(target_prop)
is_vector = isinstance(driver, list)
@@ -125,4 +176,70 @@ def add_direct_driver(target, target_prop, source, source_prop):
}
]
expression = "var0"
add_driver(target, target_prop, drivers, expression)
add_driver(target, target_prop, drivers, expression)
def read_file_prop(content: str):
props = {}
for line in content.split("\n"):
line = line.replace(" ", "")
p = line.split("=")[0]
if p != "":
v = line.split("=")[-1]
props[p] = v
return props
def write_file_prop(props: dict):
lines = []
for p, v in props.items():
lines.append(f"{p} = {v}")
return "\n".join(lines)
def set_file_prop(prop, value):
if bpy.data.texts.get("BioxelNodes") is None:
bpy.data.texts.new("BioxelNodes")
props = read_file_prop(bpy.data.texts["BioxelNodes"].as_string())
props[prop] = value
bpy.data.texts["BioxelNodes"].clear()
bpy.data.texts["BioxelNodes"].write(write_file_prop(props))
def get_file_prop(prop):
if bpy.data.texts.get("BioxelNodes") is None:
bpy.data.texts.new("BioxelNodes")
props = read_file_prop(bpy.data.texts["BioxelNodes"].as_string())
return props.get(prop)
def is_incompatible():
if get_file_prop("addon_version") is None:
for node_group in bpy.data.node_groups:
if node_group.name.startswith("BioxelNodes"):
return True
else:
addon_version = literal_eval(get_file_prop("addon_version"))
if addon_version[0] != VERSION[0]\
or addon_version[1] != VERSION[1]:
return True
return False
def local_lib_not_updated():
use_local = False
for node_group in bpy.data.node_groups:
if node_group.name.startswith("BioxelNodes"):
node_group_lib = node_group.library
if node_group_lib:
abs_filepath = bpy.path.abspath(node_group_lib.filepath)
_local_lib_file = Path(abs_filepath).resolve().as_posix()
if _local_lib_file != NODE_LIB_FILEPATH.as_posix():
use_local = True
break
addon_version = literal_eval(get_file_prop("addon_version"))
not_update = addon_version != VERSION
return use_local and not_update

View File

@@ -6,12 +6,12 @@ from mathutils import Matrix, Vector
from .layer import Layer, layer_to_obj, obj_to_layer
from ..nodes import custom_nodes
from .utils import (get_container_layer_objs,
get_layer_prop_value,
get_nodes_by_type,
move_node_to_node)
from .common import (get_container_layer_objs,
get_layer_prop_value,
get_nodes_by_type,
move_node_to_node)
from .node import add_node_to_graph
from ..utils import get_use_link
NODE_TYPE = {
"label": "BioxelNodes_MaskByLabel",
@@ -82,13 +82,17 @@ def add_layers(layers: list[Layer],
cache_dir: str):
node_group = container_obj.modifiers[0].node_group
output_node = get_nodes_by_type(node_group,
'NodeGroupOutput')[0]
try:
output_node = get_nodes_by_type(node_group,
'NodeGroupOutput')[0]
except:
output_node = node_group.nodes.new("NodeGroupOutput")
for i, layer in enumerate(layers):
layer_obj = layer_to_obj(layer, container_obj, cache_dir)
fetch_node = custom_nodes.add_node(node_group,
"BioxelNodes_FetchLayer")
fetch_node = add_node_to_graph("FetchLayer",
node_group,
get_use_link())
fetch_node.label = get_layer_prop_value(layer_obj, "name")
fetch_node.inputs[0].default_value = layer_obj
@@ -104,6 +108,7 @@ def add_layers(layers: list[Layer],
def container_to_obj(container: Container,
scene_scale: float,
step_size: float,
cache_dir: str):
# Wrapper a Container
@@ -134,11 +139,26 @@ def container_to_obj(container: Container,
container_obj.name = container.name
container_obj.data.name = container.name
container_obj.show_in_front = True
container_obj.lock_location[0] = True
container_obj.lock_location[1] = True
container_obj.lock_location[2] = True
container_obj.lock_rotation[0] = True
container_obj.lock_rotation[1] = True
container_obj.lock_rotation[2] = True
container_obj.lock_scale[0] = True
container_obj.lock_scale[1] = True
container_obj.lock_scale[2] = True
container_obj['bioxel_container'] = True
container_obj["scene_scale"] = scene_scale
container_obj["step_size"] = step_size
modifier = container_obj.modifiers.new("GeometryNodes", 'NODES')
node_group = bpy.data.node_groups.new('GeometryNodes', 'GeometryNodeTree')
node_group.interface.new_socket(name="Component",
node_group.interface.new_socket(name="Output",
in_out="OUTPUT",
socket_type="NodeSocketGeometry")
modifier.node_group = node_group

View File

@@ -7,10 +7,11 @@ import pyopenvdb as vdb
from pathlib import Path
from uuid import uuid4
from ..nodes import custom_nodes
from ..bioxel.layer import Layer
from .utils import (get_layer_prop_value,
move_node_between_nodes, add_direct_driver)
from ..utils import get_use_link
from .node import add_node_to_graph
from .common import (get_layer_prop_value,
move_node_between_nodes)
def obj_to_layer(layer_obj: bpy.types.Object):
@@ -26,7 +27,11 @@ def obj_to_layer(layer_obj: bpy.types.Object):
grids, base_metadata = vdb.readAll(str(f))
grid = grids[0]
metadata = grid.metadata
data_frame = np.ndarray(grid["data_shape"], np.float32)
if grid["layer_kind"] in ['label', 'scalar']:
data_shape = grid["data_shape"]
else:
data_shape = tuple(list(grid["data_shape"]) + [3])
data_frame = np.ndarray(data_shape, np.float32)
grid.copyToArray(data_frame)
data_frames += (data_frame,)
data = np.stack(data_frames)
@@ -34,7 +39,11 @@ def obj_to_layer(layer_obj: bpy.types.Object):
grids, base_metadata = vdb.readAll(str(cache_filepath))
grid = grids[0]
metadata = grid.metadata
data = np.ndarray(grid["data_shape"], np.float32)
if grid["layer_kind"] in ['label', 'scalar']:
data_shape = grid["data_shape"]
else:
data_shape = tuple(list(grid["data_shape"]) + [3])
data = np.ndarray(data_shape, np.float32)
grid.copyToArray(data)
data = np.expand_dims(data, axis=0) # expend frame
@@ -137,13 +146,12 @@ def layer_to_obj(layer: Layer,
layer_data = bpy.data.volumes.new(layer_display_name)
layer_data.render.space = 'WORLD'
layer_data.render.step_size = container_obj.scale[0] * layer.bioxel_size[0]
scene_scale = container_obj.get("scene_scale") or 0.01
step_size = container_obj.get("step_size") or 1
layer_data.render.step_size = scene_scale * step_size
layer_data.sequence_mode = 'REPEAT'
layer_data.filepath = str(cache_filepaths[0])
# add_direct_driver(layer_data, "render.step_size",
# container_obj, "scale[0]")
if layer.frame_count > 1:
layer_data.is_sequence = True
layer_data.frame_duration = layer.frame_count
@@ -164,8 +172,7 @@ def layer_to_obj(layer: Layer,
socket_type="NodeSocketGeometry")
modifier.node_group = node_group
layer_node = custom_nodes.add_node(node_group,
"BioxelNodes__Layer")
layer_node = add_node_to_graph("_Layer", node_group, get_use_link())
layer_node.inputs['name'].default_value = layer.name
layer_node.inputs['shape'].default_value = layer.shape
@@ -176,11 +183,12 @@ def layer_to_obj(layer: Layer,
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['unique'].default_value = random.uniform(0, 1)
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['frame_count'].default_value = layer.frame_count
layer_node.inputs['channel_count'].default_value = layer.channel_count
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

View File

@@ -0,0 +1,68 @@
from pathlib import Path
import shutil
import bpy
from .common import get_file_prop, set_file_prop
from ..exceptions import Incompatible, NoFound
from ..constants import NODE_LIB_FILEPATH, VERSION
def get_node_group(node_type: str, use_link=True):
# unannotate below for local debug in node lib file.
# node_group = bpy.data.node_groups[node_type]
# return node_group
if get_file_prop("addon_version") is None:
set_file_prop("addon_version", VERSION)
local_lib_file = None
addon_lib_file = NODE_LIB_FILEPATH.as_posix()
for node_group in bpy.data.node_groups:
if node_group.name.startswith("BioxelNodes"):
node_group_lib = node_group.library
if node_group_lib:
abs_filepath = bpy.path.abspath(node_group_lib.filepath)
_local_lib_file = Path(abs_filepath).resolve().as_posix()
if _local_lib_file != addon_lib_file:
local_lib_file = _local_lib_file
break
# local lib first
lib_file = local_lib_file or addon_lib_file
bpy.ops.wm.append('EXEC_DEFAULT',
directory=f"{lib_file}/NodeTree",
filename=node_type,
link=use_link,
use_recursive=True,
do_reuse_local_id=True)
node_group = bpy.data.node_groups.get(node_type)
if node_group is None:
raise NoFound('No custom node found')
return node_group
def assign_node_group(node, node_type: str):
node.node_tree = bpy.data.node_groups[node_type]
node.width = 200.0
node.name = node_type
return node
def add_node_to_graph(node_name: str, node_group, use_link=True):
node_type = f"BioxelNodes_{node_name}"
# Deselect all nodes first
for node in node_group.nodes:
if node.select:
node.select = False
get_node_group(node_type, use_link)
node = node_group.nodes.new("GeometryNodeGroup")
assign_node_group(node, node_type)
node.show_options = False
return node