* 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. * Add unit tests for bioxel module and documentation - Created README.md for test instructions and categories. - Implemented comprehensive unit tests for the Data, Layer, and utility functions in the bioxel module. - Added tests for reading NRRD and DICOM formats, data properties, and edge cases. - Included tests for real data files to ensure functionality with actual datasets. * feat: Update .gitattributes and .gitignore for improved asset management and ignore patterns chore: Add app.json and omoolab.css for Obsidian integration refactor: Clean up AGENTS.md and remove outdated test files * feat: Update .gitignore to include scipy_ndimage directory * feat: Update release workflow and manifest for improved dependency management and layer handling * feat: Update .gitignore and add binary files for scipy_ndimage across multiple platforms * feat: Add OPSX commands for archiving, exploring, and proposing changes * feat: Refactor asset library management and update manifest for improved organization * feat: Downgrade h5py version to 3.14.0 in requirements and manifest * feat: Update Help operator and panel labels to use English terminology * Translate documentation to Chinese, add support format details, and enhance styling with new fonts. Update mkdocs configuration for multilingual support and improve package dependencies. * feat: Remove default version setting from extra configuration in mkdocs.yml * fix: Update documentation links to point to the correct URLs for Chinese and English versions * Add documentation for basic usage, supported formats, and performance tips - Created English and Chinese documentation for basic usage of the add-on, including data download, import, extraction, and rendering steps. - Added support format documentation listing compatible file types for the add-on. - Included links to open databases for volumetric data. - Provided performance improvement tips for voxel reconstruction and rendering, including settings for low-resolution previews and balanced rendering configurations. * Add OpenSpec skills for change management and exploration - Implement openspec-apply-change skill for applying tasks from OpenSpec changes. - Create openspec-archive-change skill for archiving completed changes. - Introduce openspec-explore skill for exploring ideas and clarifying requirements. - Add openspec-propose skill for proposing new changes with generated artifacts. - Migrate source command skills: source-command-opsx-apply, source-command-opsx-archive, source-command-opsx-explore, and source-command-opsx-propose for improved command handling. * feat: Update version to 2.0.1, refactor asset library handling, and enhance layer import process * fix: downgrade scipy version to 1.16.3 in build.py and pyproject.toml * Remove deprecated modules and functions from bioxel package - Deleted _ni_support.py, _utils.py, and dtype.py as they contained unused or redundant functions. - Removed the resize function and related utilities from _warps.py to streamline image processing. - Cleaned up the skimage module by removing unnecessary imports and functions. * feat: Add SKIP_MODULE_NAMES to exclude specific modules during import and refactor io_worker import logic * refactor: Remove known limitations section from documentation for EEVEE NEXT support
274 lines
9.8 KiB
Python
274 lines
9.8 KiB
Python
import json
|
||
import uuid
|
||
import webbrowser
|
||
import bpy
|
||
from pathlib import Path
|
||
import shutil
|
||
|
||
from ..asset_library import add_bioxel_asset_library
|
||
from ..constants import LATEST_NODE_LIB_PATH
|
||
from ..utils import (
|
||
get_all_layer_objs,
|
||
get_cache_dir,
|
||
get_node_version,
|
||
is_missing_layer,
|
||
)
|
||
|
||
|
||
class CleanTemp(bpy.types.Operator):
|
||
bl_idname = "bioxel.clear_temp"
|
||
bl_label = "Clean Temp"
|
||
bl_description = "Clean all cache in temp (include other project cache)"
|
||
|
||
def execute(self, context):
|
||
cache_dir = get_cache_dir()
|
||
try:
|
||
shutil.rmtree(cache_dir)
|
||
self.report({"INFO"}, f"Successfully cleaned temp.")
|
||
return {"FINISHED"}
|
||
except:
|
||
self.report(
|
||
{"WARNING"}, "Fail to clean temp, you may do it manually.")
|
||
return {"CANCELLED"}
|
||
|
||
def invoke(self, context, event):
|
||
context.window_manager.invoke_confirm(
|
||
self,
|
||
event,
|
||
message="All temp files will be removed, include other project cache, do you still want to clean?",
|
||
)
|
||
return {"RUNNING_MODAL"}
|
||
|
||
|
||
class RenderSettingPreset(bpy.types.Operator):
|
||
bl_idname = "bioxel.render_setting_preset"
|
||
bl_label = "Render Setting Presets"
|
||
bl_description = "Render setting presets for bioxel"
|
||
bl_options = {"UNDO"}
|
||
|
||
PRESETS = {
|
||
"performance": "Performance",
|
||
"balance": "Balance",
|
||
"quality": "Quality",
|
||
}
|
||
|
||
preset: bpy.props.EnumProperty(
|
||
name="Preset", default="balance", items=[(k, v, "") for k, v in PRESETS.items()]
|
||
) # type: ignore
|
||
|
||
def execute(self, context):
|
||
if self.preset == "performance":
|
||
# EEVEE
|
||
bpy.context.scene.eevee.use_taa_reprojection = False
|
||
# bpy.context.scene.eevee.use_shadow_jitter_viewport = True
|
||
bpy.context.scene.eevee.volumetric_tile_size = "4"
|
||
bpy.context.scene.eevee.volumetric_shadow_samples = 32
|
||
bpy.context.scene.eevee.volumetric_samples = 64
|
||
bpy.context.scene.eevee.volumetric_ray_depth = 1
|
||
bpy.context.scene.eevee.use_volumetric_shadows = True
|
||
|
||
# Cycles
|
||
bpy.context.scene.cycles.volume_bounces = 4
|
||
bpy.context.scene.cycles.transparent_max_bounces = 4
|
||
bpy.context.scene.cycles.volume_biased = True
|
||
bpy.context.scene.cycles.volume_preview_step_rate = 100
|
||
bpy.context.scene.cycles.volume_step_rate = 100
|
||
|
||
elif self.preset == "balance":
|
||
# EEVEE
|
||
bpy.context.scene.eevee.use_taa_reprojection = False
|
||
# bpy.context.scene.eevee.use_shadow_jitter_viewport = True
|
||
bpy.context.scene.eevee.volumetric_tile_size = "2"
|
||
bpy.context.scene.eevee.volumetric_shadow_samples = 64
|
||
bpy.context.scene.eevee.volumetric_samples = 128
|
||
bpy.context.scene.eevee.volumetric_ray_depth = 8
|
||
bpy.context.scene.eevee.use_volumetric_shadows = True
|
||
|
||
# Cycles
|
||
bpy.context.scene.cycles.volume_bounces = 8
|
||
bpy.context.scene.cycles.transparent_max_bounces = 16
|
||
bpy.context.scene.cycles.volume_biased = True
|
||
bpy.context.scene.cycles.volume_preview_step_rate = 10
|
||
bpy.context.scene.cycles.volume_step_rate = 10
|
||
|
||
elif self.preset == "quality":
|
||
# EEVEE
|
||
bpy.context.scene.eevee.use_taa_reprojection = False
|
||
# bpy.context.scene.eevee.use_shadow_jitter_viewport = True
|
||
bpy.context.scene.eevee.volumetric_tile_size = "1"
|
||
bpy.context.scene.eevee.volumetric_shadow_samples = 128
|
||
bpy.context.scene.eevee.volumetric_samples = 256
|
||
bpy.context.scene.eevee.volumetric_ray_depth = 16
|
||
bpy.context.scene.eevee.use_volumetric_shadows = True
|
||
|
||
# Cycles
|
||
bpy.context.scene.cycles.volume_bounces = 16
|
||
bpy.context.scene.cycles.transparent_max_bounces = 32
|
||
bpy.context.scene.cycles.volume_biased = False
|
||
|
||
return {"FINISHED"}
|
||
|
||
|
||
class Help(bpy.types.Operator):
|
||
bl_idname = "bioxel.help"
|
||
bl_label = "Need Help?"
|
||
bl_description = "Online Manual for Beginner"
|
||
bl_icon = "HELP"
|
||
|
||
def execute(self, context):
|
||
webbrowser.open("https://docs.omoolab.xyz/bioxelnodes/latest/", new=2)
|
||
|
||
return {"FINISHED"}
|
||
|
||
|
||
class AddAssetLibrary(bpy.types.Operator):
|
||
bl_idname = "bioxel.add_asset_library"
|
||
bl_label = "Add Nodes Library"
|
||
bl_description = "Add the bundled O Bioxel asset library"
|
||
|
||
def execute(self, context):
|
||
try:
|
||
add_bioxel_asset_library()
|
||
except FileNotFoundError as e:
|
||
self.report({"ERROR"}, str(e))
|
||
return {"CANCELLED"}
|
||
|
||
for window in context.window_manager.windows:
|
||
for area in window.screen.areas:
|
||
if area.type == "NODE_EDITOR":
|
||
area.tag_redraw()
|
||
|
||
self.report({"INFO"}, "O Bioxel asset library is ready.")
|
||
return {"FINISHED"}
|
||
|
||
|
||
# 定义虚无化切换操作
|
||
class TogglePhantom(bpy.types.Operator):
|
||
"""Toggle object phantom state (ray invisible, no shadows, semi-transparent wireframe)"""
|
||
|
||
bl_idname = "bioxel.toggle_phantom"
|
||
bl_label = "Toggle Phantom"
|
||
bl_options = {"REGISTER", "UNDO"}
|
||
|
||
def execute(self, context):
|
||
|
||
obj = context.active_object
|
||
if not obj:
|
||
self.report({"WARNING"}, "No object selected")
|
||
return {"CANCELLED"}
|
||
|
||
# 获取3D视图空间
|
||
view_space = None
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == "VIEW_3D":
|
||
for space in area.spaces:
|
||
if space.type == "VIEW_3D":
|
||
view_space = space
|
||
break
|
||
if view_space:
|
||
break
|
||
|
||
# 确保存储状态的文本数据块存在
|
||
text_name = "phantom_orig_states"
|
||
if text_name not in bpy.data.texts:
|
||
bpy.data.texts.new(text_name)
|
||
|
||
# 从文本数据块加载状态
|
||
states_text = bpy.data.texts[text_name]
|
||
try:
|
||
states = json.loads(states_text.as_string())
|
||
except:
|
||
states = {}
|
||
|
||
def get_obj_uuid(obj):
|
||
if "bioxel_uuid" not in obj:
|
||
obj["bioxel_uuid"] = uuid.uuid4().hex
|
||
return obj["bioxel_uuid"]
|
||
|
||
# 使用物体的唯一标识符来追踪
|
||
obj_uuid = get_obj_uuid(obj)
|
||
|
||
# 检查物体是否已经处于虚无化状态
|
||
is_phantom = (
|
||
obj.display_type == "SOLID"
|
||
and obj.show_wire
|
||
and obj.show_in_front
|
||
and round(obj.color[3], 2) == 0.10
|
||
and not obj.visible_camera
|
||
and not obj.visible_diffuse
|
||
and not obj.visible_glossy
|
||
and not obj.visible_transmission
|
||
and not obj.visible_volume_scatter
|
||
and not obj.visible_shadow
|
||
)
|
||
|
||
if is_phantom:
|
||
# 恢复原始状态
|
||
if obj_uuid in states:
|
||
state = states[obj_uuid]
|
||
obj.display_type = state["display_type"]
|
||
obj.show_wire = state["show_wire"]
|
||
obj.show_in_front = state["show_in_front"]
|
||
obj.color = state["color"]
|
||
obj.visible_camera = state["visible_camera"]
|
||
obj.visible_diffuse = state["visible_diffuse"]
|
||
obj.visible_glossy = state["visible_glossy"]
|
||
obj.visible_transmission = state["visible_transmission"]
|
||
obj.visible_volume_scatter = state["visible_volume_scatter"]
|
||
obj.visible_shadow = state["visible_shadow"]
|
||
|
||
# 恢复视图着色设置(如果之前有保存)
|
||
if view_space and "viewport_color_type" in state:
|
||
view_space.shading.color_type = state["viewport_color_type"]
|
||
|
||
del states[obj_uuid]
|
||
self.report({"INFO"}, f"Object restored")
|
||
else:
|
||
# 保存原始状态
|
||
state_data = {
|
||
"display_type": obj.display_type,
|
||
"show_wire": obj.show_wire,
|
||
"show_in_front": obj.show_in_front,
|
||
"color": list(obj.color), # 保存RGBA颜色值
|
||
"visible_camera": obj.visible_camera,
|
||
"visible_diffuse": obj.visible_diffuse,
|
||
"visible_glossy": obj.visible_glossy,
|
||
"visible_transmission": obj.visible_transmission,
|
||
"visible_volume_scatter": obj.visible_volume_scatter,
|
||
"visible_shadow": obj.visible_shadow,
|
||
}
|
||
|
||
# 检查并处理视图着色设置
|
||
if view_space:
|
||
# 保存当前视图着色设置
|
||
state_data["viewport_color_type"] = view_space.shading.color_type
|
||
|
||
# 检查当前是否为object, random或attribute
|
||
valid_types = {"OBJECT", "RANDOM", "ATTRIBUTE"}
|
||
if view_space.shading.color_type not in valid_types:
|
||
# 改为object模式
|
||
view_space.shading.color_type = "OBJECT"
|
||
|
||
states[obj_uuid] = state_data
|
||
|
||
# 设置虚无化状态
|
||
obj.display_type = "SOLID" # 以实体方式显示
|
||
obj.show_wire = True # 显示线框
|
||
obj.show_in_front = True # 显示在其他物体前面
|
||
# 保持RGB不变,仅修改alpha透明度为0.1
|
||
obj.color[3] = 0.1
|
||
# 关闭所有射线可见性和投影
|
||
obj.visible_camera = False
|
||
obj.visible_diffuse = False
|
||
obj.visible_glossy = False
|
||
obj.visible_transmission = False
|
||
obj.visible_volume_scatter = False
|
||
obj.visible_shadow = False
|
||
self.report({"INFO"}, f"Object phantomed")
|
||
|
||
# 保存状态到文本数据块
|
||
states_text.clear()
|
||
states_text.write(json.dumps(states, indent=2))
|
||
|
||
return {"FINISHED"}
|