feat: Add Bioxel asset catalog and layer management functionality
- Introduced a new asset catalog definition file for Blender to manage Bioxel assets. - Implemented layer caching and snapshot functionality in `layer.py`, allowing for VDB file generation and low-resolution snapshots. - Created node management functions in `node.py` for handling Bioxel nodes within Blender's Geometry Node Editor. - Added an operator to extract mesh data from selected nodes in `structure.py`. - Developed UI panels in `panels.py` for layer management, including import options and layer library display.
This commit is contained in:
180
src/bioxelnodes/panels.py
Normal file
180
src/bioxelnodes/panels.py
Normal file
@@ -0,0 +1,180 @@
|
||||
from pathlib import Path
|
||||
import bpy
|
||||
|
||||
from .node import get_layer_nodes, get_main_node_group
|
||||
from .utils import load_icon
|
||||
from .layer import get_layer_caches
|
||||
from .operators.io import ImportAsColor, ImportAsLabel, ImportAsScalar, ImportData
|
||||
from .operators.misc import Help, RenderSettingPreset
|
||||
from .operators.layer import (
|
||||
AddLayerNode,
|
||||
DeleteLayer,
|
||||
RelocatePath,
|
||||
RenameLayer,
|
||||
SaveLayer,
|
||||
)
|
||||
|
||||
|
||||
class ImportMenu(bpy.types.Menu):
|
||||
bl_label = "Bioxel Import"
|
||||
bl_idname = "BIOXEL_MT_import"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.operator(ImportAsScalar.bl_idname,
|
||||
text="Import As Scalar").filepath = ""
|
||||
layout.operator(ImportAsLabel.bl_idname,
|
||||
text="Import As Label").filepath = ""
|
||||
layout.operator(ImportAsColor.bl_idname,
|
||||
text="Import As Color").filepath = ""
|
||||
|
||||
|
||||
class RenderSettingMenu(bpy.types.Menu):
|
||||
bl_label = "Render Setting Presets"
|
||||
bl_idname = "BIOXEL_MT_render_setting"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
for k, v in RenderSettingPreset.PRESETS.items():
|
||||
op = layout.operator(RenderSettingPreset.bl_idname, text=v)
|
||||
op.preset = k
|
||||
|
||||
|
||||
class BioxelPanelBase:
|
||||
"""
|
||||
Abstract base panel for all Bioxel UI panels.
|
||||
|
||||
- Ensures the panel appears only when the active area is the Node Editor
|
||||
and the Node Editor is editing a Geometry Nodes tree.
|
||||
- Places the panel under the "Bioxel" sidebar tab.
|
||||
- Provides an optional bl_order for panel ordering inside the category.
|
||||
"""
|
||||
|
||||
bl_space_type = "NODE_EDITOR"
|
||||
bl_region_type = "UI"
|
||||
bl_context = "geometry_node"
|
||||
bl_category = "胞素(Bioxel)"
|
||||
# try to make Bioxel panels order early (may be respected by Blender)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
node_group = get_main_node_group(context)
|
||||
return node_group is not None
|
||||
|
||||
|
||||
class HeaderPanel(bpy.types.Panel, BioxelPanelBase):
|
||||
bl_label = "胞素(Bioxel)"
|
||||
bl_idname = "BIOXEL_PT_header_panel"
|
||||
bl_order = 0
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator(
|
||||
Help.bl_idname, text="Help", icon=Help.bl_icon # 复用原操作的图标定义
|
||||
)
|
||||
|
||||
layout.menu(
|
||||
RenderSettingMenu.bl_idname, text="Render Preset", icon="RENDER_STILL"
|
||||
)
|
||||
|
||||
|
||||
class LayerNodePanel(BioxelPanelBase, bpy.types.Panel):
|
||||
bl_label = "Layer Nodes"
|
||||
bl_idname = "BIOXEL_PT_o_layer_nodes"
|
||||
bl_order = 1
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
node_group = get_main_node_group(context)
|
||||
|
||||
layout.template_list(
|
||||
"BIOXEL_UL_layer_nodes",
|
||||
"",
|
||||
node_group,
|
||||
"nodes",
|
||||
context.window_manager,
|
||||
"bioxel_layer_list_index",
|
||||
rows=3,
|
||||
)
|
||||
|
||||
|
||||
class LibraryPanel(BioxelPanelBase, bpy.types.Panel):
|
||||
bl_label = "Layer Library"
|
||||
bl_idname = "BIOXEL_PT_library_panel"
|
||||
bl_order = 2
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
wm = context.window_manager
|
||||
caches = get_layer_caches()
|
||||
|
||||
row = layout.row()
|
||||
row.scale_y = 2.0
|
||||
row.operator(ImportData.bl_idname, icon="IMPORT")
|
||||
# row.menu(ImportMenu.bl_idname, icon="IMPORT", text="Import")
|
||||
layout.separator()
|
||||
layout.template_icon_view(
|
||||
bpy.context.window_manager,
|
||||
"bioxel_layer_library",
|
||||
show_labels=False,
|
||||
scale=10.0,
|
||||
scale_popup=6.0,
|
||||
)
|
||||
|
||||
selected_id = getattr(wm, "bioxel_layer_library", None)
|
||||
selected_id = None if selected_id == "nothing_found" else selected_id
|
||||
entry = next((c for c in caches if c.get("id") == selected_id), None)
|
||||
if entry:
|
||||
cache_id = entry["id"]
|
||||
path = entry["path"]
|
||||
name = entry.get("name", "?")
|
||||
kind = entry.get("kind", "?")
|
||||
path_exists = Path(bpy.path.abspath(path)).exists()
|
||||
|
||||
layout.prop(wm, "bioxel_snapshot_z", text="Z Slice", slider=True)
|
||||
layout.separator()
|
||||
# operations and meta_box as before
|
||||
ops_row = layout.row(align=True)
|
||||
if path_exists:
|
||||
ops_row.operator(
|
||||
AddLayerNode.bl_idname, text="Add", icon="PLUS"
|
||||
).cache_id = selected_id
|
||||
ops_row.operator(
|
||||
SaveLayer.bl_idname, text="Save", icon="FILE_TICK"
|
||||
).cache_id = selected_id
|
||||
ops_row.operator(
|
||||
RenameLayer.bl_idname, text="Name", icon="FONT_DATA"
|
||||
).cache_id = selected_id
|
||||
ops_row.operator(
|
||||
DeleteLayer.bl_idname, text="", icon="TRASH"
|
||||
).cache_id = selected_id
|
||||
else:
|
||||
ops_row.operator(
|
||||
RelocatePath.bl_idname, text="Relocate", icon="FILE_FOLDER"
|
||||
).cache_id = selected_id
|
||||
ops_row.operator(
|
||||
DeleteLayer.bl_idname, text="", icon="TRASH"
|
||||
).cache_id = selected_id
|
||||
|
||||
meta_box = layout.box()
|
||||
if hasattr(meta_box, "alert"):
|
||||
meta_box.alert = True
|
||||
meta_box.label(text=f"Cache is missing...")
|
||||
meta_box.label(text=f"Please relocate the cache path!")
|
||||
return
|
||||
|
||||
meta_box = layout.box()
|
||||
meta_box.label(text=f"Name: {name}")
|
||||
meta_box.label(text=f"Path: {path}")
|
||||
meta_box.label(text=f"Kind: {kind}")
|
||||
meta_box.label(
|
||||
text=f"Dims: [{entry.get('frame_count',1)},{tuple(entry.get('shape',''))},{entry.get('channel_count',1)}]"
|
||||
)
|
||||
if kind in ["scalar", "color", "vector"]:
|
||||
path = Path(bpy.path.abspath(entry["path"]))
|
||||
histogram = load_icon(
|
||||
path / "histogram.png", f"{cache_id}_histogram")
|
||||
meta_box.template_icon(histogram, scale=10.0)
|
||||
else:
|
||||
layout.label(text="No layer selected", icon="QUESTION")
|
||||
Reference in New Issue
Block a user