feat: improving the user experience
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
68
bioxelnodes/bioxelutils/node.py
Normal file
68
bioxelnodes/bioxelutils/node.py
Normal 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
|
||||
Reference in New Issue
Block a user