8 Commits

Author SHA1 Message Date
Ma Nan
92f85ff1f3 Merge pull request #45 from OmooLab/feature/dev
fix: relocate cannot work on missing layer
2024-09-19 12:28:35 +08:00
63c593f09a fix: relocate cannot work on missing layer
fix: import fail if label are all zero
fix: add cutter name to node
fix: cannot append node lib file on NAS (UNC path)
fix: join component cause disappear
feat: smooth label
feat: better topo on cutout by range
2024-09-19 12:27:39 +08:00
Ma Nan
3c5ff64afd Merge pull request #44 from OmooLab/feature/dev
Feature/dev
2024-09-13 17:12:41 +08:00
0ebc6dafbd fix cannot extract wire 2024-09-13 17:00:00 +08:00
c22ab2c85b Merge branch 'main' into feature/dev 2024-09-13 16:43:51 +08:00
133702619e fix: join component cause missing
feat: extract mesh from node
2024-09-13 16:42:18 +08:00
Ma Nan
e384798184 Merge pull request #43 from OmooLab/feature/dev
fix: cannot decode utf-8 issue
2024-09-11 15:36:19 +08:00
80da400891 fix: cannot decode utf-8 issue 2024-09-11 15:17:00 +08:00
16 changed files with 262 additions and 100 deletions

Binary file not shown.

View File

@@ -77,7 +77,7 @@ class Layer():
for f in range(self.frame_count): for f in range(self.frame_count):
mask_frame = mask[f, :, :, :] mask_frame = mask[f, :, :, :]
if smooth > 0: if smooth > 0:
mask_frame = scipy.minimum_filter(mask_frame.astype(np.float32), mask_frame = scipy.median_filter(mask_frame.astype(np.float32),
mode="nearest", mode="nearest",
size=smooth) size=smooth)
# mask_frame = scipy.median_filter( # mask_frame = scipy.median_filter(
@@ -87,7 +87,7 @@ class Layer():
for f in range(self.frame_count): for f in range(self.frame_count):
mask_frame = mask[:, :, :] mask_frame = mask[:, :, :]
if smooth > 0: if smooth > 0:
mask_frame = scipy.minimum_filter(mask_frame.astype(np.float32), mask_frame = scipy.median_filter(mask_frame.astype(np.float32),
mode="nearest", mode="nearest",
size=smooth) size=smooth)
# mask_frame = scipy.median_filter( # mask_frame = scipy.median_filter(
@@ -119,10 +119,6 @@ class Layer():
if progress_callback: if progress_callback:
progress_callback(f, self.frame_count) progress_callback(f, self.frame_count)
# frame = ski.resize(data[f, :, :, :],
# shape,
# preserve_range=True,
# anti_aliasing=data.dtype.kind != "b")
frame = data[f, :, :, :, :] frame = data[f, :, :, :, :]
if smooth > 0: if smooth > 0:
frame = scipy.median_filter(frame.astype(np.float32), frame = scipy.median_filter(frame.astype(np.float32),

View File

@@ -61,7 +61,8 @@ def get_ext(filepath: Path) -> str:
elif filepath.name.endswith(".map.gz"): elif filepath.name.endswith(".map.gz"):
return ".map.gz" return ".map.gz"
else: else:
return filepath.suffix suffix = filepath.suffix
return "" if len(suffix) > 5 else suffix
def get_filename(filepath: Path): def get_filename(filepath: Path):
@@ -310,6 +311,7 @@ def parse_volumetric_data(data_file: str, series_id="", progress_callback=None):
def get_meta(key): def get_meta(key):
try: try:
stirng = reader.GetMetaData(0, key).removesuffix(" ") stirng = reader.GetMetaData(0, key).removesuffix(" ")
stirng.encode('utf-8')
if stirng in ["No study description", if stirng in ["No study description",
"No series description", "No series description",
""]: ""]:

View File

@@ -2,7 +2,7 @@ from ast import literal_eval
from pathlib import Path from pathlib import Path
import bpy import bpy
from ..constants import NODE_LIB_DIRPATH, VERSIONS from ..constants import LATEST_NODE_LIB_PATH, NODE_LIB_DIRPATH, VERSIONS
from ..utils import get_cache_dir from ..utils import get_cache_dir
@@ -244,7 +244,7 @@ def get_node_lib_path(node_version):
def local_lib_not_updated(): def local_lib_not_updated():
addon_version = VERSIONS[0]["node_version"] addon_version = VERSIONS[0]["node_version"]
addon_lib_path = get_node_lib_path(addon_version) addon_lib_path = LATEST_NODE_LIB_PATH
use_local = False use_local = False
for node_group in bpy.data.node_groups: for node_group in bpy.data.node_groups:
@@ -258,3 +258,13 @@ def local_lib_not_updated():
not_update = get_node_version() != addon_version not_update = get_node_version() != addon_version
return use_local and not_update return use_local and not_update
def get_output_node(node_group):
try:
output_node = get_nodes_by_type(node_group,
'NodeGroupOutput')[0]
except:
output_node = node_group.nodes.new("NodeGroupOutput")
return output_node

View File

@@ -8,7 +8,7 @@ from mathutils import Matrix, Vector
from .layer import Layer, layer_to_obj, obj_to_layer from .layer import Layer, layer_to_obj, obj_to_layer
from .common import (get_container_layer_objs, from .common import (get_container_layer_objs,
get_layer_prop_value, get_layer_prop_value,
get_nodes_by_type, get_nodes_by_type, get_output_node,
move_node_to_node) move_node_to_node)
from .node import add_node_to_graph from .node import add_node_to_graph
from ..utils import get_use_link from ..utils import get_use_link
@@ -82,11 +82,7 @@ def add_layers(layers: list[Layer],
cache_dir: str): cache_dir: str):
node_group = container_obj.modifiers[0].node_group node_group = container_obj.modifiers[0].node_group
try: output_node = get_output_node(node_group)
output_node = get_nodes_by_type(node_group,
'NodeGroupOutput')[0]
except:
output_node = node_group.nodes.new("NodeGroupOutput")
for i, layer in enumerate(layers): for i, layer in enumerate(layers):
layer_obj = layer_to_obj(layer, container_obj, cache_dir) layer_obj = layer_to_obj(layer, container_obj, cache_dir)

View File

@@ -1,54 +1,55 @@
from pathlib import Path from pathlib import Path
import bpy import bpy
from .common import get_file_prop, get_node_lib_path, set_file_prop from .common import get_file_prop, set_file_prop
from ..exceptions import Incompatible, NoFound from ..exceptions import NoFound
from ..constants import VERSIONS from ..constants import LATEST_NODE_LIB_PATH, VERSIONS
def get_node_group(node_type: str, use_link=True): def get_node_tree(node_type: str, use_link=True):
# unannotate below for local debug in node lib file. # unannotate below for local debug in node lib file.
# node_group = bpy.data.node_groups[node_type] # node_group = bpy.data.node_groups[node_type]
# return node_group # return node_group
# added node is always from latest node version # added node is always from latest node version
addon_version = VERSIONS[0]["node_version"] addon_version = VERSIONS[0]["node_version"]
addon_lib_path = get_node_lib_path(addon_version) addon_lib_path = LATEST_NODE_LIB_PATH
if get_file_prop("node_version") is None: if get_file_prop("node_version") is None:
set_file_prop("node_version", addon_version) set_file_prop("node_version", addon_version)
local_lib_path = None local_lib = None
for node_group in bpy.data.node_groups: for node_group in bpy.data.node_groups:
if node_group.name.startswith("BioxelNodes"): if node_group.name.startswith("BioxelNodes"):
lib = node_group.library lib = node_group.library
if lib: if lib:
lib_path = Path(bpy.path.abspath(lib.filepath)).resolve() lib_path = Path(bpy.path.abspath(lib.filepath)).resolve()
if lib_path != addon_lib_path: if lib_path != addon_lib_path:
local_lib_path = lib_path local_lib = lib.filepath
break break
# local lib first # local lib first
lib_path = local_lib_path or addon_lib_path lib_path_str = local_lib or str(addon_lib_path)
bpy.ops.wm.append('EXEC_DEFAULT',
directory=f"{lib_path.as_posix()}/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: with bpy.data.libraries.load(lib_path_str,
link=use_link,
relative=True) as (data_from, data_to):
data_to.node_groups = [n for n in data_from.node_groups
if n == node_type]
node_tree = data_to.node_groups[0]
if node_tree is None:
raise NoFound('No custom node found') raise NoFound('No custom node found')
return node_group return node_tree
def assign_node_group(node, node_type: str): def assign_node_tree(node, node_tree):
node.node_tree = bpy.data.node_groups[node_type] node.node_tree = node_tree
node.width = 200.0 node.width = 200.0
node.name = node_type node.name = node_tree.name
return node return node
@@ -61,9 +62,9 @@ def add_node_to_graph(node_name: str, node_group, node_label=None, use_link=True
if node.select: if node.select:
node.select = False node.select = False
get_node_group(node_type, use_link) node_tree = get_node_tree(node_type, use_link)
node = node_group.nodes.new("GeometryNodeGroup") node = node_group.nodes.new("GeometryNodeGroup")
assign_node_group(node, node_type) assign_node_tree(node, node_tree)
node.label = node_label node.label = node_label
node.show_options = False node.show_options = False

View File

@@ -1,7 +1,7 @@
schema_version = "1.0.0" schema_version = "1.0.0"
id = "bioxelnodes" id = "bioxelnodes"
version = "1.0.0" version = "1.0.2"
name = "Bioxel Nodes" name = "Bioxel Nodes"
tagline = "For scientific volumetric data visualization in Blender" tagline = "For scientific volumetric data visualization in Blender"
maintainer = "Ma Nan <icrdr2010@outlook.com>" maintainer = "Ma Nan <icrdr2010@outlook.com>"
@@ -10,7 +10,7 @@ website = "https://omoolab.github.io/BioxelNodes/latest"
tags = ["Geometry Nodes", "Render", "Import-Export"] tags = ["Geometry Nodes", "Render", "Import-Export"]
blender_version_min = "4.2.0" blender_version_min = "4.2.0"
license = ["SPDX:MIT"] license = ['SPDX:GPL-3.0-or-later']
copyright = ["2024 OmooLab"] copyright = ["2024 OmooLab"]
platforms = ["windows-x64", "linux-x64", "macos-arm64", "macos-x64"] platforms = ["windows-x64", "linux-x64", "macos-arm64", "macos-x64"]

View File

@@ -1,12 +1,31 @@
from pathlib import Path from pathlib import Path
VERSIONS = [{"label": "Current", "node_version": (1, 0, 0)}, VERSIONS = [{"label": "Latest", "node_version": (1, 0, 2)},
{"label": "v0.3.x", "node_version": (0, 3, 3)}, {"label": "v0.3.x", "node_version": (0, 3, 3)},
{"label": "v0.2.x", "node_version": (0, 2, 9)}] {"label": "v0.2.x", "node_version": (0, 2, 9)}]
NODE_LIB_DIRPATH = Path(Path(__file__).parent, NODE_LIB_DIRPATH = Path(Path(__file__).parent,
"assets/Nodes").resolve() "assets/Nodes").resolve()
LATEST_NODE_LIB_PATH = lib_path = Path(NODE_LIB_DIRPATH,
"BioxelNodes_latest.blend").resolve()
COMPONENT_OUTPUT_NODES = [
"CutoutByThreshold",
"CutoutByRange",
"CutoutByHue",
"JoinComponent",
"SetProperties",
"SetColor",
"SetColorByColor",
"SetColorByColor",
"SetColorByRamp2",
"SetColorByRamp3",
"SetColorByRamp4",
"SetColorByRamp5",
"Cut",
]
MENU_ITEMS = [ MENU_ITEMS = [
{ {
'label': 'Component', 'label': 'Component',
@@ -179,9 +198,9 @@ MENU_ITEMS = [
'icon': 'MODIFIER', 'icon': 'MODIFIER',
'items': [ 'items': [
{ {
'label': 'Fetch Surface', 'label': 'Fetch Mesh',
'icon': 'OUTLINER_OB_MESH', 'icon': 'OUTLINER_OB_MESH',
'name': 'FetchSurface', 'name': 'FetchMesh',
'description': '' 'description': ''
}, },
{ {

View File

@@ -10,16 +10,19 @@ from .operators.layer import (FetchLayer, RelocateLayer, RetimeLayer, RenameLaye
RemoveSelectedLayers, SaveSelectedLayersCache, RemoveSelectedLayers, SaveSelectedLayersCache,
ResampleLayer, SignScalar, CombineLabels, ResampleLayer, SignScalar, CombineLabels,
FillByLabel, FillByThreshold, FillByRange) FillByLabel, FillByThreshold, FillByRange)
from .operators.container import (AddLocator, AddSlicer, ExtractShapeWire, from .operators.container import (AddLocator, AddSlicer, ContainerProps,
SaveContainerLayersCache, RemoveContainerMissingLayers, SaveContainerLayersCache, RemoveContainerMissingLayers,
SaveContainer, LoadContainer, SaveContainer, LoadContainer,
AddPieCutter, AddPlaneCutter, AddPieCutter, AddPlaneCutter,
AddCylinderCutter, AddCubeCutter, AddSphereCutter, AddCylinderCutter, AddCubeCutter, AddSphereCutter,
ExtractBboxWire, ExtractSurface, ExtractVolume, ContainerProps) ExtractBboxWire, ExtractMesh, ExtractShapeWire,
ExtractNodeMesh, ExtractNodeBboxWire, ExtractNodeShapeWire)
from .operators.io import (ImportAsLabel, ImportAsScalar, ImportAsColor) from .operators.io import (ImportAsLabel, ImportAsScalar, ImportAsColor)
from .operators.misc import (CleanTemp, from .operators.misc import (CleanTemp,
ReLinkNodeLib, RemoveAllMissingLayers, RenderSettingPreset, SaveAllLayersCache, SaveNodeLib, SliceViewer) ReLinkNodeLib, RemoveAllMissingLayers,
RenderSettingPreset, SaveAllLayersCache,
SaveNodeLib, SliceViewer)
class IncompatibleMenu(bpy.types.Menu): class IncompatibleMenu(bpy.types.Menu):
@@ -76,8 +79,7 @@ class ExtractFromContainerMenu(bpy.types.Menu):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.operator(ExtractSurface.bl_idname) layout.operator(ExtractMesh.bl_idname)
layout.operator(ExtractVolume.bl_idname)
layout.operator(ExtractShapeWire.bl_idname) layout.operator(ExtractShapeWire.bl_idname)
layout.operator(ExtractBboxWire.bl_idname) layout.operator(ExtractBboxWire.bl_idname)
@@ -199,8 +201,8 @@ class BioxelNodesTopbarMenu(bpy.types.Menu):
layout.operator(RemoveAllMissingLayers.bl_idname) layout.operator(RemoveAllMissingLayers.bl_idname)
layout.separator() layout.separator()
layout.operator(SaveNodeLib.bl_idname)
layout.menu(ReLinkNodeLibMenu.bl_idname) layout.menu(ReLinkNodeLibMenu.bl_idname)
layout.operator(SaveNodeLib.bl_idname)
layout.separator() layout.separator()
layout.menu(DangerZoneMenu.bl_idname) layout.menu(DangerZoneMenu.bl_idname)
@@ -257,6 +259,11 @@ class NodeContextMenu(bpy.types.Menu):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.operator(ExtractNodeMesh.bl_idname)
layout.operator(ExtractNodeShapeWire.bl_idname)
layout.operator(ExtractNodeBboxWire.bl_idname)
layout.separator()
layout.operator(SaveSelectedLayersCache.bl_idname, layout.operator(SaveSelectedLayersCache.bl_idname,
icon=SaveSelectedLayersCache.bl_icon) icon=SaveSelectedLayersCache.bl_icon)
layout.operator(RemoveSelectedLayers.bl_idname) layout.operator(RemoveSelectedLayers.bl_idname)

View File

@@ -1,13 +1,15 @@
import bpy import bpy
import bmesh import bmesh
from ..constants import COMPONENT_OUTPUT_NODES
from ..utils import get_cache_dir, get_use_link, select_object from ..utils import get_cache_dir, get_use_link, select_object
from ..bioxel.io import load_container, save_container from ..bioxel.io import load_container, save_container
from ..bioxelutils.container import container_to_obj, obj_to_container from ..bioxelutils.container import container_to_obj, obj_to_container
from ..bioxelutils.node import add_node_to_graph from ..bioxelutils.node import add_node_to_graph
from ..bioxelutils.common import (get_container_layer_objs, get_container_obj, from ..bioxelutils.common import (get_container_layer_objs, get_container_obj, get_node_type,
get_nodes_by_type, is_missing_layer, get_nodes_by_type, get_output_node, is_missing_layer,
move_node_between_nodes, move_node_between_nodes,
move_node_to_node, move_node_to_node,
get_all_layer_objs) get_all_layer_objs)
@@ -77,7 +79,7 @@ class LoadContainer(bpy.types.Operator):
return {'RUNNING_MODAL'} return {'RUNNING_MODAL'}
class ExtractObject(): class ExtractNodeObject():
bl_options = {'UNDO'} bl_options = {'UNDO'}
def execute(self, context): def execute(self, context):
@@ -87,6 +89,26 @@ class ExtractObject():
self.report({"WARNING"}, "Cannot find any bioxel container.") self.report({"WARNING"}, "Cannot find any bioxel container.")
return {'FINISHED'} return {'FINISHED'}
# nodes = [node for node in context.selected_nodes
# if get_node_type(node).removeprefix("BioxelNodes_") in COMPONENT_OUTPUT_NODES]
if len(context.selected_nodes) == 0:
self.report({"WARNING"}, "No node selected.")
return {'FINISHED'}
selected_node = context.selected_nodes[0]
container_node_group = container_obj.modifiers[0].node_group
container_output_node = get_output_node(container_node_group)
if len(container_output_node.inputs[0].links) == 0:
pre_socket = None
else:
pre_socket = container_output_node.inputs[0].links[0].from_socket
container_node_group.links.new(selected_node.outputs[0],
container_output_node.inputs[0])
bpy.ops.mesh.primitive_cube_add( bpy.ops.mesh.primitive_cube_add(
size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
obj = bpy.context.active_object obj = bpy.context.active_object
@@ -106,6 +128,15 @@ class ExtractObject():
fetch_mesh_node.inputs[0].default_value = container_obj fetch_mesh_node.inputs[0].default_value = container_obj
node_group.links.new(fetch_mesh_node.outputs[0], output_node.inputs[0]) node_group.links.new(fetch_mesh_node.outputs[0], output_node.inputs[0])
for modifier in obj.modifiers:
bpy.ops.object.modifier_apply(modifier=modifier.name)
obj.data.materials.clear()
if pre_socket:
container_node_group.links.new(pre_socket,
container_output_node.inputs[0])
select_object(obj) select_object(obj)
self.report({"INFO"}, f"Successfully picked") self.report({"INFO"}, f"Successfully picked")
@@ -113,24 +144,92 @@ class ExtractObject():
return {'FINISHED'} return {'FINISHED'}
class ExtractSurface(bpy.types.Operator, ExtractObject): class ExtractNodeMesh(bpy.types.Operator, ExtractNodeObject):
bl_idname = "bioxelnodes.fetch_mesh" bl_idname = "bioxelnodes.extract_node_mesh"
bl_label = "Extract Surface" bl_label = "Extract Mesh"
bl_description = "Extract Surface" bl_description = "Extract Mesh"
bl_icon = "OUTLINER_OB_MESH" bl_icon = "OUTLINER_OB_MESH"
object_type = "Surface" object_type = "Mesh"
class ExtractVolume(bpy.types.Operator, ExtractObject): class ExtractNodeShapeWire(bpy.types.Operator, ExtractNodeObject):
bl_idname = "bioxelnodes.pick_volume" bl_idname = "bioxelnodes.extract_node_shape_wire"
bl_label = "Extract Volume" bl_label = "Extract Shape Wire"
bl_description = "Extract Volume" bl_description = "Extract Shape Wire"
bl_icon = "OUTLINER_OB_VOLUME" bl_icon = "FILE_VOLUME"
object_type = "Volume" object_type = "ShapeWire"
class ExtractNodeBboxWire(bpy.types.Operator, ExtractNodeObject):
bl_idname = "bioxelnodes.extract_node_bbox_wire"
bl_label = "Extract Bbox Wire"
bl_description = "Extract Bbox Wire"
bl_icon = "MESH_CUBE"
object_type = "BboxWire"
class ExtractObject():
bl_options = {'UNDO'}
def execute(self, context):
container_obj = get_container_obj(context.object)
if container_obj is None:
self.report({"WARNING"}, "Cannot find any bioxel container.")
return {'FINISHED'}
bpy.ops.mesh.primitive_cube_add(
size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
obj = bpy.context.active_object
obj.name = f"{container_obj.name}_{self.object_type}"
bpy.ops.node.new_geometry_nodes_modifier()
modifier = obj.modifiers[0]
node_group = modifier.node_group
output_node = get_output_node(node_group)
fetch_mesh_node = add_node_to_graph(f"Fetch{self.object_type}",
node_group,
node_label=f"Fetch {self.object_type}",
use_link=get_use_link())
fetch_mesh_node.inputs[0].default_value = container_obj
if self.object_type == "Mesh":
node_group.links.new(
fetch_mesh_node.outputs[0], output_node.inputs[0])
else:
curve_to_mesh_node = node_group.nodes.new(
"GeometryNodeCurveToMesh")
node_group.links.new(
fetch_mesh_node.outputs[0], curve_to_mesh_node.inputs[0])
node_group.links.new(
curve_to_mesh_node.outputs[0], output_node.inputs[0])
for modifier in obj.modifiers:
bpy.ops.object.modifier_apply(modifier=modifier.name)
obj.data.materials.clear()
select_object(obj)
self.report({"INFO"}, f"Successfully picked")
return {'FINISHED'}
class ExtractMesh(bpy.types.Operator, ExtractObject):
bl_idname = "bioxelnodes.extract_mesh"
bl_label = "Extract Mesh"
bl_description = "Extract Mesh"
bl_icon = "OUTLINER_OB_MESH"
object_type = "Mesh"
class ExtractShapeWire(bpy.types.Operator, ExtractObject): class ExtractShapeWire(bpy.types.Operator, ExtractObject):
bl_idname = "bioxelnodes.pick_shape_wire" bl_idname = "bioxelnodes.extract_shape_wire"
bl_label = "Extract Shape Wire" bl_label = "Extract Shape Wire"
bl_description = "Extract Shape Wire" bl_description = "Extract Shape Wire"
bl_icon = "FILE_VOLUME" bl_icon = "FILE_VOLUME"
@@ -138,7 +237,7 @@ class ExtractShapeWire(bpy.types.Operator, ExtractObject):
class ExtractBboxWire(bpy.types.Operator, ExtractObject): class ExtractBboxWire(bpy.types.Operator, ExtractObject):
bl_idname = "bioxelnodes.pick_bbox_wire" bl_idname = "bioxelnodes.extract_bbox_wire"
bl_label = "Extract Bbox Wire" bl_label = "Extract Bbox Wire"
bl_description = "Extract Bbox Wire" bl_description = "Extract Bbox Wire"
bl_icon = "MESH_CUBE" bl_icon = "MESH_CUBE"
@@ -244,7 +343,7 @@ class AddCutter():
if len(cut_nodes) == 0: if len(cut_nodes) == 0:
cutter_node = add_node_to_graph("ObjectCutter", cutter_node = add_node_to_graph("ObjectCutter",
node_group, node_group,
node_label=name, node_label=f"{self.cutter_type.capitalize()} Cutter",
use_link=get_use_link()) use_link=get_use_link())
cutter_node.inputs[0].default_value = self.cutter_type.capitalize() cutter_node.inputs[0].default_value = self.cutter_type.capitalize()
cutter_node.inputs[1].default_value = cutter_obj cutter_node.inputs[1].default_value = cutter_obj
@@ -253,8 +352,8 @@ class AddCutter():
node_group, node_group,
use_link=get_use_link()) use_link=get_use_link())
output_node = get_nodes_by_type(node_group, output_node = get_output_node(node_group)
'NodeGroupOutput')[0]
if len(output_node.inputs[0].links) == 0: if len(output_node.inputs[0].links) == 0:
node_group.links.new(cut_node.outputs[0], node_group.links.new(cut_node.outputs[0],
output_node.inputs[0]) output_node.inputs[0])
@@ -277,7 +376,7 @@ class AddCutter():
node_name="ObjectCutter", node_name="ObjectCutter",
node_label=name) node_label=name)
node = bpy.context.active_node node = bpy.context.active_node
node.inputs[0].default_value = name node.inputs[0].default_value = self.cutter_type.capitalize()
node.inputs[1].default_value = cutter_obj node.inputs[1].default_value = cutter_obj
return {'FINISHED'} return {'FINISHED'}
@@ -368,8 +467,8 @@ class AddSlicer(bpy.types.Operator):
slice_node.inputs[1].default_value = slicer_obj slice_node.inputs[1].default_value = slicer_obj
output_node = get_nodes_by_type(node_group, output_node = get_output_node(node_group)
'NodeGroupOutput')[0]
if len(output_node.inputs[0].links) == 0: if len(output_node.inputs[0].links) == 0:
node_group.links.new(slice_node.outputs[0], node_group.links.new(slice_node.outputs[0],
output_node.inputs[0]) output_node.inputs[0])
@@ -419,8 +518,8 @@ class AddLocator(bpy.types.Operator):
parent_node.inputs[1].default_value = locator_obj parent_node.inputs[1].default_value = locator_obj
output_node = get_nodes_by_type(node_group, output_node = get_output_node(node_group)
'NodeGroupOutput')[0]
if len(output_node.inputs[0].links) == 0: if len(output_node.inputs[0].links) == 0:
node_group.links.new(parent_node.outputs[0], node_group.links.new(parent_node.outputs[0],
output_node.inputs[0]) output_node.inputs[0])

View File

@@ -164,7 +164,7 @@ class ParseVolumetricData(bpy.types.Operator):
read_as: bpy.props.EnumProperty(name="Read as", read_as: bpy.props.EnumProperty(name="Read as",
default="SCALAR", default="SCALAR",
items=[("SCALAR", "Scalar", ""), items=[("SCALAR", "Scalar", ""),
("LABEL", "Labels", ""), ("LABEL", "Label", ""),
("COLOR", "Color", "")]) # type: ignore ("COLOR", "Color", "")]) # type: ignore
series_id: bpy.props.EnumProperty(name="Select Series", series_id: bpy.props.EnumProperty(name="Select Series",
@@ -284,6 +284,10 @@ class ParseVolumetricData(bpy.types.Operator):
self.report({"ERROR"}, "Invaild label data.") self.report({"ERROR"}, "Invaild label data.")
return {'CANCELLED'} return {'CANCELLED'}
if self.label_count == 0:
self.report({"ERROR"}, "Get no label.")
return {'CANCELLED'}
orig_shape = self.meta['xyz_shape'] orig_shape = self.meta['xyz_shape']
orig_spacing = self.meta['spacing'] orig_spacing = self.meta['spacing']
@@ -391,6 +395,7 @@ class ParseVolumetricData(bpy.types.Operator):
def get_meta(key): def get_meta(key):
try: try:
stirng = single.GetMetaData(key).removesuffix(" ") stirng = single.GetMetaData(key).removesuffix(" ")
stirng.encode('utf-8')
if stirng in ["No study description", if stirng in ["No study description",
"No series description", "No series description",
""]: ""]:
@@ -416,13 +421,15 @@ class ParseVolumetricData(bpy.types.Operator):
if series_id == "": if series_id == "":
series_id = "empty" series_id = "empty"
series_items[series_id] = "{:<20} {:>1}".format(f"{study_description}>{series_description}({series_modality})", label = "{:<20} {:>1}".format(f"{study_description}>{series_description}({series_modality})",
f"({size_x}x{size_y})x{count}") f"({size_x}x{size_y})x{count}")
for key, value in series_items.items(): series_items[series_id] = label
for series_id, label in series_items.items():
series_item = self.series_ids.add() series_item = self.series_ids.add()
series_item.id = key series_item.id = series_id
series_item.label = value series_item.label = label
if len(series_items.keys()) > 1: if len(series_items.keys()) > 1:
self.skip_series_select = False self.skip_series_select = False
@@ -497,10 +504,13 @@ class ImportVolumetricDataDialog(bpy.types.Operator):
label_count: bpy.props.IntProperty() # type: ignore label_count: bpy.props.IntProperty() # type: ignore
smooth: bpy.props.IntProperty(name="Smooth Size (Larger takes longer time)",
default=3) # type: ignore
read_as: bpy.props.EnumProperty(name="Read as", read_as: bpy.props.EnumProperty(name="Read as",
default="SCALAR", default="SCALAR",
items=[("SCALAR", "Scalar", ""), items=[("SCALAR", "Scalar", ""),
("LABEL", "Labels", ""), ("LABEL", "Label", ""),
("COLOR", "Color", "")]) # type: ignore ("COLOR", "Color", "")]) # type: ignore
bioxel_size: bpy.props.FloatProperty(name="Bioxel Size (Larger size means small resolution)", bioxel_size: bpy.props.FloatProperty(name="Bioxel Size (Larger size means small resolution)",
@@ -613,12 +623,15 @@ class ImportVolumetricDataDialog(bpy.types.Operator):
progress_callback = progress_callback_factory(name_i, progress_callback = progress_callback_factory(name_i,
progress, progress,
progress_step) progress_step)
label_data = data == np.full_like(data, i+1)
label_data = label_data.astype(np.float32)
try: try:
layer = Layer(data=data == np.full_like(data, i+1), layer = Layer(data=label_data,
name=name_i, name=name_i,
kind=kind) kind=kind)
layer.resize(shape=shape, layer.resize(shape=shape,
smooth=self.smooth,
progress_callback=progress_callback) progress_callback=progress_callback)
layer.affine = affine layer.affine = affine
@@ -921,6 +934,8 @@ class ImportVolumetricDataDialog(bpy.types.Operator):
if self.read_as == "SCALAR": if self.read_as == "SCALAR":
panel.prop(self, "split_channel", panel.prop(self, "split_channel",
text=f"Split Channel as Multi Layer") text=f"Split Channel as Multi Layer")
elif self.read_as == "LABEL":
panel.prop(self, "smooth")
panel.label( panel.label(
text="Dimension Order: [Frame, (X-axis,Y-axis,Z-axis), Channel]") text="Dimension Order: [Frame, (X-axis,Y-axis,Z-axis), Channel]")

View File

@@ -4,7 +4,6 @@ import bpy
import numpy as np import numpy as np
from ..exceptions import NoContent from ..exceptions import NoContent
from ..bioxel.layer import Layer from ..bioxel.layer import Layer
from ..bioxelutils.node import add_node_to_graph from ..bioxelutils.node import add_node_to_graph
@@ -199,7 +198,7 @@ class RetimeLayer(bpy.types.Operator, LayerOperator):
return self.execute(context) return self.execute(context)
class RelocateLayer(bpy.types.Operator, LayerOperator): class RelocateLayer(bpy.types.Operator):
bl_idname = "bioxelnodes.relocate_layer" bl_idname = "bioxelnodes.relocate_layer"
bl_label = "Relocate Layer Cache" bl_label = "Relocate Layer Cache"
bl_description = "Relocate layer cache" bl_description = "Relocate layer cache"
@@ -207,6 +206,16 @@ class RelocateLayer(bpy.types.Operator, LayerOperator):
filepath: bpy.props.StringProperty(subtype="FILE_PATH") # type: ignore filepath: bpy.props.StringProperty(subtype="FILE_PATH") # type: ignore
def execute(self, context):
layer_obj = get_selected_layer(context)
if layer_obj == None:
self.report({"WARNING"}, "Get no layer.")
return {'FINISHED'}
self.operate(layer_obj, context)
return {'FINISHED'}
def operate(self, layer_obj, context): def operate(self, layer_obj, context):
layer_obj.data.filepath = self.filepath layer_obj.data.filepath = self.filepath
@@ -259,7 +268,7 @@ class ResampleLayer(bpy.types.Operator, OutputLayerOperator):
bl_description = "Resample value" bl_description = "Resample value"
bl_icon = "ALIASED" bl_icon = "ALIASED"
smooth: bpy.props.IntProperty(name="Smooth Iteration", smooth: bpy.props.IntProperty(name="Smooth Size",
default=0, default=0,
soft_min=0, soft_max=5, soft_min=0, soft_max=5,
options={"SKIP_SAVE"}) # type: ignore options={"SKIP_SAVE"}) # type: ignore
@@ -389,7 +398,7 @@ class FillByThreshold(bpy.types.Operator, FillOperator):
if self.invert else data > self.threshold if self.invert else data > self.threshold
new_layer = orig_layer.copy() new_layer = orig_layer.copy()
new_layer.fill(self.fill_value, mask, 3) new_layer.fill(self.fill_value, mask, 0)
new_layer.name = self.new_layer_name \ new_layer.name = self.new_layer_name \
or f"{orig_layer.name}_F-{self.threshold:.2f}" or f"{orig_layer.name}_F-{self.threshold:.2f}"
return new_layer return new_layer
@@ -419,7 +428,7 @@ class FillByRange(bpy.types.Operator, FillOperator):
(data > self.from_min) & (data < self.from_max) (data > self.from_min) & (data < self.from_max)
new_layer = orig_layer.copy() new_layer = orig_layer.copy()
new_layer.fill(self.fill_value, mask, 3) new_layer.fill(self.fill_value, mask, 0)
new_layer.name = self.new_layer_name \ new_layer.name = self.new_layer_name \
or f"{orig_layer.name}_F-{self.from_min:.2f}-{self.from_max:.2f}" or f"{orig_layer.name}_F-{self.from_min:.2f}-{self.from_max:.2f}"
return new_layer return new_layer
@@ -431,7 +440,7 @@ class FillByLabel(bpy.types.Operator, FillOperator):
bl_description = "Fill value by label" bl_description = "Fill value by label"
bl_icon = "MESH_CAPSULE" bl_icon = "MESH_CAPSULE"
smooth: bpy.props.IntProperty(name="Smooth Iteration", smooth: bpy.props.IntProperty(name="Smooth Size",
default=0, default=0,
soft_min=0, soft_max=5, soft_min=0, soft_max=5,
options={"SKIP_SAVE"}) # type: ignore options={"SKIP_SAVE"}) # type: ignore
@@ -451,7 +460,7 @@ class FillByLabel(bpy.types.Operator, FillOperator):
mask = 1 - mask mask = 1 - mask
new_layer = orig_layer.copy() new_layer = orig_layer.copy()
new_layer.fill(self.fill_value, mask, 3) new_layer.fill(self.fill_value, mask, 0)
new_layer.name = self.new_layer_name \ new_layer.name = self.new_layer_name \
or f"{orig_layer.name}_F-{label_layer.name}" or f"{orig_layer.name}_F-{label_layer.name}"
return new_layer return new_layer

View File

@@ -5,7 +5,7 @@ import shutil
from ..bioxelutils.common import get_all_layer_objs, get_node_lib_path, get_node_version, is_missing_layer, set_file_prop from ..bioxelutils.common import get_all_layer_objs, get_node_lib_path, get_node_version, is_missing_layer, set_file_prop
from .layer import RemoveLayers, SaveLayersCache from .layer import RemoveLayers, SaveLayersCache
from ..constants import NODE_LIB_DIRPATH, VERSIONS from ..constants import LATEST_NODE_LIB_PATH, VERSIONS
from ..utils import get_cache_dir from ..utils import get_cache_dir
@@ -19,7 +19,8 @@ class ReLinkNodeLib(bpy.types.Operator):
def execute(self, context): def execute(self, context):
node_version = VERSIONS[self.index]['node_version'] node_version = VERSIONS[self.index]['node_version']
lib_path = get_node_lib_path(node_version) lib_path = get_node_lib_path(node_version) \
if self.index != 0 else LATEST_NODE_LIB_PATH
node_libs = [] node_libs = []
for node_group in bpy.data.node_groups: for node_group in bpy.data.node_groups:
@@ -56,16 +57,23 @@ class SaveNodeLib(bpy.types.Operator):
def execute(self, context): def execute(self, context):
node_version = get_node_version() node_version = get_node_version()
if node_version is None: if node_version is None:
node_version = VERSIONS[0]["node_version"] node_version = VERSIONS[0]["node_version"]
else: else:
if node_version not in [v["node_version"] for v in VERSIONS]: if node_version not in [v["node_version"] for v in VERSIONS]:
node_version = VERSIONS[0]["node_version"] node_version = VERSIONS[0]["node_version"]
if node_version == VERSIONS[0]["node_version"]:
lib_path = LATEST_NODE_LIB_PATH
else:
lib_path = get_node_lib_path(node_version) lib_path = get_node_lib_path(node_version)
version_str = "v"+".".join([str(i) for i in list(node_version)])
lib_dir = bpy.path.abspath(self.lib_dir) lib_dir = bpy.path.abspath(self.lib_dir)
local_lib_path: Path = Path(lib_dir, lib_path.name).resolve() local_lib_path: Path = Path(lib_dir,
f"BioxelNodes_{version_str}.blend").resolve()
node_lib_path: Path = lib_path node_lib_path: Path = lib_path
blend_path = Path(bpy.path.abspath("//")).resolve() blend_path = Path(bpy.path.abspath("//")).resolve()

View File

@@ -1,7 +1,7 @@
import bpy import bpy
from ..bioxelutils.common import is_incompatible, local_lib_not_updated from ..bioxelutils.common import is_incompatible, local_lib_not_updated
from ..bioxelutils.node import assign_node_group, get_node_group from ..bioxelutils.node import assign_node_tree, get_node_tree
from ..utils import get_use_link from ..utils import get_use_link
@@ -41,14 +41,14 @@ class AddNode(bpy.types.Operator):
"Current addon verison is not compatible to this file. If you insist on editing this file please keep the same addon version.") "Current addon verison is not compatible to this file. If you insist on editing this file please keep the same addon version.")
return {'CANCELLED'} return {'CANCELLED'}
get_node_group(self.node_type, get_use_link()) node_tree = get_node_tree(self.node_type, get_use_link())
bpy.ops.node.add_node( bpy.ops.node.add_node(
'INVOKE_DEFAULT', 'INVOKE_DEFAULT',
type='GeometryNodeGroup', type='GeometryNodeGroup',
use_transform=True use_transform=True
) )
node = bpy.context.active_node node = bpy.context.active_node
assign_node_group(node, self.node_type) assign_node_tree(node, node_tree)
node.label = self.node_label node.label = self.node_label
node.show_options = False node.show_options = False

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "bioxelnodes" name = "bioxelnodes"
version = "1.0.0" version = "1.0.2"
description = "" description = ""
authors = ["Ma Nan <icrdr2010@outlook.com>"] authors = ["Ma Nan <icrdr2010@outlook.com>"]
license = "MIT" license = "MIT"