6 Commits

Author SHA1 Message Date
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
13 changed files with 202 additions and 59 deletions

Binary file not shown.

View File

@@ -61,7 +61,8 @@ def get_ext(filepath: Path) -> str:
elif filepath.name.endswith(".map.gz"):
return ".map.gz"
else:
return filepath.suffix
suffix = filepath.suffix
return "" if len(suffix) > 5 else suffix
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):
try:
stirng = reader.GetMetaData(0, key).removesuffix(" ")
stirng.encode('utf-8')
if stirng in ["No study description",
"No series description",
""]:

View File

@@ -2,7 +2,7 @@ from ast import literal_eval
from pathlib import Path
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
@@ -244,7 +244,7 @@ def get_node_lib_path(node_version):
def local_lib_not_updated():
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
for node_group in bpy.data.node_groups:
@@ -258,3 +258,12 @@ def local_lib_not_updated():
not_update = get_node_version() != addon_version
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 .common import (get_container_layer_objs,
get_layer_prop_value,
get_nodes_by_type,
get_nodes_by_type, get_output_node,
move_node_to_node)
from .node import add_node_to_graph
from ..utils import get_use_link
@@ -82,11 +82,7 @@ def add_layers(layers: list[Layer],
cache_dir: str):
node_group = container_obj.modifiers[0].node_group
try:
output_node = get_nodes_by_type(node_group,
'NodeGroupOutput')[0]
except:
output_node = node_group.nodes.new("NodeGroupOutput")
output_node = get_output_node(node_group)
for i, layer in enumerate(layers):
layer_obj = layer_to_obj(layer, container_obj, cache_dir)

View File

@@ -4,7 +4,7 @@ import bpy
from .common import get_file_prop, get_node_lib_path, set_file_prop
from ..exceptions import Incompatible, NoFound
from ..constants import VERSIONS
from ..constants import LATEST_NODE_LIB_PATH, VERSIONS
def get_node_group(node_type: str, use_link=True):
@@ -14,7 +14,7 @@ def get_node_group(node_type: str, use_link=True):
# added node is always from latest 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:
set_file_prop("node_version", addon_version)

View File

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

View File

@@ -1,12 +1,31 @@
from pathlib import Path
VERSIONS = [{"label": "Current", "node_version": (1, 0, 0)},
VERSIONS = [{"label": "Latest", "node_version": (1, 0, 1)},
{"label": "v0.3.x", "node_version": (0, 3, 3)},
{"label": "v0.2.x", "node_version": (0, 2, 9)}]
NODE_LIB_DIRPATH = Path(Path(__file__).parent,
"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 = [
{
'label': 'Component',
@@ -179,9 +198,9 @@ MENU_ITEMS = [
'icon': 'MODIFIER',
'items': [
{
'label': 'Fetch Surface',
'label': 'Fetch Mesh',
'icon': 'OUTLINER_OB_MESH',
'name': 'FetchSurface',
'name': 'FetchMesh',
'description': ''
},
{

View File

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

View File

@@ -1,13 +1,15 @@
import bpy
import bmesh
from ..constants import COMPONENT_OUTPUT_NODES
from ..utils import get_cache_dir, get_use_link, select_object
from ..bioxel.io import load_container, save_container
from ..bioxelutils.container import container_to_obj, obj_to_container
from ..bioxelutils.node import add_node_to_graph
from ..bioxelutils.common import (get_container_layer_objs, get_container_obj,
get_nodes_by_type, is_missing_layer,
from ..bioxelutils.common import (get_container_layer_objs, get_container_obj, get_node_type,
get_nodes_by_type, get_output_node, is_missing_layer,
move_node_between_nodes,
move_node_to_node,
get_all_layer_objs)
@@ -77,7 +79,7 @@ class LoadContainer(bpy.types.Operator):
return {'RUNNING_MODAL'}
class ExtractObject():
class ExtractNodeObject():
bl_options = {'UNDO'}
def execute(self, context):
@@ -87,6 +89,26 @@ class ExtractObject():
self.report({"WARNING"}, "Cannot find any bioxel container.")
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(
size=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
obj = bpy.context.active_object
@@ -106,6 +128,15 @@ class ExtractObject():
fetch_mesh_node.inputs[0].default_value = container_obj
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)
self.report({"INFO"}, f"Successfully picked")
@@ -113,24 +144,92 @@ class ExtractObject():
return {'FINISHED'}
class ExtractSurface(bpy.types.Operator, ExtractObject):
bl_idname = "bioxelnodes.fetch_mesh"
bl_label = "Extract Surface"
bl_description = "Extract Surface"
class ExtractNodeMesh(bpy.types.Operator, ExtractNodeObject):
bl_idname = "bioxelnodes.extract_node_mesh"
bl_label = "Extract Mesh"
bl_description = "Extract Mesh"
bl_icon = "OUTLINER_OB_MESH"
object_type = "Surface"
object_type = "Mesh"
class ExtractVolume(bpy.types.Operator, ExtractObject):
bl_idname = "bioxelnodes.pick_volume"
bl_label = "Extract Volume"
bl_description = "Extract Volume"
bl_icon = "OUTLINER_OB_VOLUME"
object_type = "Volume"
class ExtractNodeShapeWire(bpy.types.Operator, ExtractNodeObject):
bl_idname = "bioxelnodes.extract_node_shape_wire"
bl_label = "Extract Shape Wire"
bl_description = "Extract Shape Wire"
bl_icon = "FILE_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):
bl_idname = "bioxelnodes.pick_shape_wire"
bl_idname = "bioxelnodes.extract_shape_wire"
bl_label = "Extract Shape Wire"
bl_description = "Extract Shape Wire"
bl_icon = "FILE_VOLUME"
@@ -138,7 +237,7 @@ class ExtractShapeWire(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_description = "Extract Bbox Wire"
bl_icon = "MESH_CUBE"
@@ -253,8 +352,8 @@ class AddCutter():
node_group,
use_link=get_use_link())
output_node = get_nodes_by_type(node_group,
'NodeGroupOutput')[0]
output_node = get_output_node(node_group)
if len(output_node.inputs[0].links) == 0:
node_group.links.new(cut_node.outputs[0],
output_node.inputs[0])
@@ -277,7 +376,7 @@ class AddCutter():
node_name="ObjectCutter",
node_label=name)
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
return {'FINISHED'}
@@ -368,8 +467,8 @@ class AddSlicer(bpy.types.Operator):
slice_node.inputs[1].default_value = slicer_obj
output_node = get_nodes_by_type(node_group,
'NodeGroupOutput')[0]
output_node = get_output_node(node_group)
if len(output_node.inputs[0].links) == 0:
node_group.links.new(slice_node.outputs[0],
output_node.inputs[0])
@@ -419,8 +518,8 @@ class AddLocator(bpy.types.Operator):
parent_node.inputs[1].default_value = locator_obj
output_node = get_nodes_by_type(node_group,
'NodeGroupOutput')[0]
output_node = get_output_node(node_group)
if len(output_node.inputs[0].links) == 0:
node_group.links.new(parent_node.outputs[0],
output_node.inputs[0])

View File

@@ -391,6 +391,7 @@ class ParseVolumetricData(bpy.types.Operator):
def get_meta(key):
try:
stirng = single.GetMetaData(key).removesuffix(" ")
stirng.encode('utf-8')
if stirng in ["No study description",
"No series description",
""]:
@@ -416,13 +417,15 @@ class ParseVolumetricData(bpy.types.Operator):
if series_id == "":
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}")
series_items[series_id] = label
for key, value in series_items.items():
for series_id, label in series_items.items():
series_item = self.series_ids.add()
series_item.id = key
series_item.label = value
series_item.id = series_id
series_item.label = label
if len(series_items.keys()) > 1:
self.skip_series_select = False

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 .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
@@ -19,7 +19,8 @@ class ReLinkNodeLib(bpy.types.Operator):
def execute(self, context):
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 = []
for node_group in bpy.data.node_groups:
@@ -56,16 +57,23 @@ class SaveNodeLib(bpy.types.Operator):
def execute(self, context):
node_version = get_node_version()
if node_version is None:
node_version = VERSIONS[0]["node_version"]
else:
if node_version not in [v["node_version"] for v in VERSIONS]:
node_version = VERSIONS[0]["node_version"]
lib_path = get_node_lib_path(node_version)
if node_version == VERSIONS[0]["node_version"]:
lib_path = LATEST_NODE_LIB_PATH
else:
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)
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
blend_path = Path(bpy.path.abspath("//")).resolve()

View File

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