601 lines
20 KiB
Python
601 lines
20 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
SUWood 区域分割工具(六面切割)
|
||
翻译自: SUWZoneDiv1Tool.rb
|
||
"""
|
||
|
||
import logging
|
||
from typing import Optional, List, Tuple, Dict, Any
|
||
|
||
# 尝试导入Blender模块
|
||
try:
|
||
import bpy
|
||
import bmesh
|
||
import mathutils
|
||
from bpy_extras import view3d_utils
|
||
from bpy.props import StringProperty, FloatProperty
|
||
from bpy.types import Operator, Panel
|
||
BLENDER_AVAILABLE = True
|
||
except ImportError:
|
||
BLENDER_AVAILABLE = False
|
||
|
||
try:
|
||
from .suw_constants import *
|
||
from .suw_client import set_cmd
|
||
except ImportError:
|
||
# 绝对导入作为后备
|
||
try:
|
||
from suw_constants import *
|
||
from suw_client import set_cmd
|
||
except ImportError as e:
|
||
print(f"⚠️ 导入SUWood模块失败: {e}")
|
||
# 提供默认实现
|
||
|
||
def set_cmd(cmd, params):
|
||
print(f"Command: {cmd}, Params: {params}")
|
||
|
||
# 提供缺失的常量
|
||
VSSpatialPos_F = 1 # 前
|
||
VSSpatialPos_K = 2 # 后
|
||
VSSpatialPos_L = 3 # 左
|
||
VSSpatialPos_R = 4 # 右
|
||
VSSpatialPos_B = 5 # 底
|
||
VSSpatialPos_T = 6 # 顶
|
||
SUZoneDiv1 = 21
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 全局变量存储回调函数
|
||
_divide_callback = None
|
||
|
||
# Blender输入对话框Operator
|
||
if BLENDER_AVAILABLE:
|
||
class SUWZoneDivideInputOperator(Operator):
|
||
"""区域分割输入对话框"""
|
||
bl_idname = "suw.zone_divide_input"
|
||
bl_label = "区域分割"
|
||
bl_description = "输入分割长度"
|
||
|
||
# 输入属性
|
||
length: FloatProperty(
|
||
name="分割长度(mm)",
|
||
description="输入分割长度,单位:毫米",
|
||
default=200.0,
|
||
min=0.1,
|
||
max=10000.0,
|
||
unit='LENGTH'
|
||
)
|
||
|
||
direction_name: StringProperty(
|
||
name="方向",
|
||
description="分割方向",
|
||
default=""
|
||
)
|
||
|
||
def execute(self, context):
|
||
"""执行输入确认"""
|
||
global _divide_callback
|
||
if _divide_callback:
|
||
_divide_callback(self.length)
|
||
_divide_callback = None # 清除回调
|
||
return {'FINISHED'}
|
||
|
||
def invoke(self, context, event):
|
||
"""显示输入对话框"""
|
||
wm = context.window_manager
|
||
return wm.invoke_props_dialog(self, width=300)
|
||
|
||
def draw(self, context):
|
||
"""绘制对话框界面"""
|
||
layout = self.layout
|
||
layout.label(text=f"{self.direction_name}分割")
|
||
layout.prop(self, "length", text="长度(mm)")
|
||
layout.separator()
|
||
layout.label(text="点击确定执行分割操作")
|
||
|
||
# 状态栏管理类
|
||
|
||
|
||
class StatusBarManager:
|
||
"""Blender状态栏管理器"""
|
||
|
||
@staticmethod
|
||
def set_status_text(text: str):
|
||
"""设置状态栏文本"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
# 使用Blender的UI系统设置状态文本
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == 'VIEW_3D':
|
||
# 设置状态文本到3D视图的header
|
||
area.header_text_set(text)
|
||
break
|
||
logger.debug(f"📝 状态栏更新: {text}")
|
||
else:
|
||
print(f"📝 状态: {text}")
|
||
except Exception as e:
|
||
logger.debug(f"设置状态文本失败: {e}")
|
||
|
||
@staticmethod
|
||
def clear_status_text():
|
||
"""清除状态栏文本"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == 'VIEW_3D':
|
||
area.header_text_set(None)
|
||
break
|
||
logger.debug("🧹 状态栏已清除")
|
||
except Exception as e:
|
||
logger.debug(f"清除状态文本失败: {e}")
|
||
|
||
# 消息框管理类
|
||
|
||
|
||
class MessageBoxManager:
|
||
"""Blender消息框管理器"""
|
||
|
||
@staticmethod
|
||
def show_message(message: str, title: str = "SUWood", icon: str = 'INFO'):
|
||
"""显示消息框"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
def draw_message(self, context):
|
||
layout = self.layout
|
||
layout.label(text=message)
|
||
layout.separator()
|
||
layout.operator("wm.ok", text="确定")
|
||
|
||
bpy.context.window_manager.popup_menu(
|
||
draw_message,
|
||
title=title,
|
||
icon=icon
|
||
)
|
||
logger.info(f"💬 消息框: {message}")
|
||
else:
|
||
print(f"💬 {title}: {message}")
|
||
except Exception as e:
|
||
logger.error(f"显示消息框失败: {e}")
|
||
print(f"💬 {title}: {message}")
|
||
|
||
|
||
class SWZoneDiv1Tool:
|
||
"""区域分割工具类(六面切割)"""
|
||
|
||
def __init__(self):
|
||
"""初始化区域分割工具"""
|
||
self.pattern = "up" # "up" 或 "back" 分割模式
|
||
self._reset_status_text()
|
||
|
||
logger.info("🔧 初始化区域分割工具")
|
||
|
||
def activate(self):
|
||
"""激活工具"""
|
||
try:
|
||
self._set_status_text(self.tooltip)
|
||
self._clear_selection()
|
||
logger.info("✅ 区域分割工具激活")
|
||
|
||
except Exception as e:
|
||
logger.error(f"激活工具失败: {e}")
|
||
|
||
def resume(self):
|
||
"""恢复工具"""
|
||
try:
|
||
self._set_status_text(self.tooltip)
|
||
logger.debug("🔄 区域分割工具恢复")
|
||
|
||
except Exception as e:
|
||
logger.debug(f"恢复工具失败: {e}")
|
||
|
||
def _reset_status_text(self):
|
||
"""重置状态文本"""
|
||
try:
|
||
self.tooltip = "选择一个要分割的区域, "
|
||
|
||
if self.pattern == "up":
|
||
self.tooltip += "按方向键进行上下左右分割"
|
||
else:
|
||
self.tooltip += "按方向键上下进行前后分割"
|
||
|
||
self.tooltip += ", 按ctrl键可切换模式"
|
||
|
||
self._set_status_text(self.tooltip)
|
||
logger.debug(f"📝 状态文本更新: {self.pattern}模式")
|
||
|
||
except Exception as e:
|
||
logger.debug(f"重置状态文本失败: {e}")
|
||
|
||
def divide(self, direction: int):
|
||
"""执行分割操作"""
|
||
try:
|
||
# 获取选中的区域
|
||
selected_zone = self._get_selected_zone()
|
||
if not selected_zone:
|
||
self._show_message("请先选择要分割的区域!")
|
||
return
|
||
|
||
# 获取方向名称
|
||
dir_name = self._get_direction_name(direction)
|
||
|
||
# 显示输入对话框
|
||
self._show_divide_input_dialog(dir_name, direction)
|
||
|
||
logger.debug(f"✂️ 准备分割: {dir_name}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"区域分割失败: {e}")
|
||
|
||
def _execute_divide(self, direction: int, length: float):
|
||
"""执行实际的分割操作"""
|
||
try:
|
||
# 获取选中的区域
|
||
selected_zone = self._get_selected_zone()
|
||
if not selected_zone:
|
||
self._show_message("请先选择要分割的区域!")
|
||
return
|
||
|
||
# 构建参数
|
||
params = {
|
||
"method": SUZoneDiv1,
|
||
"uid": self._get_entity_attr(selected_zone, "uid"),
|
||
"zid": self._get_entity_attr(selected_zone, "zid"),
|
||
"dir": direction,
|
||
"len": length
|
||
}
|
||
|
||
# 发送命令
|
||
set_cmd("r00", params)
|
||
|
||
dir_name = self._get_direction_name(direction)
|
||
logger.info(f"✂️ 区域分割执行: {dir_name}, 长度={length}mm")
|
||
|
||
except Exception as e:
|
||
logger.error(f"执行分割失败: {e}")
|
||
|
||
def _get_direction_name(self, direction: int) -> str:
|
||
"""获取方向名称"""
|
||
direction_names = {
|
||
VSSpatialPos_T: "上",
|
||
VSSpatialPos_B: "下",
|
||
VSSpatialPos_L: "左",
|
||
VSSpatialPos_R: "右",
|
||
VSSpatialPos_F: "前",
|
||
VSSpatialPos_K: "后"
|
||
}
|
||
return direction_names.get(direction, "未知")
|
||
|
||
def _show_divide_input_dialog(self, dir_name: str, direction: int):
|
||
"""显示分割输入对话框"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
self._blender_divide_input(dir_name, direction)
|
||
else:
|
||
self._stub_divide_input(dir_name, direction)
|
||
|
||
except Exception as e:
|
||
logger.error(f"分割输入对话框失败: {e}")
|
||
|
||
def _blender_divide_input(self, dir_name: str, direction: int):
|
||
"""Blender分割输入对话框"""
|
||
try:
|
||
global _divide_callback
|
||
|
||
# 设置回调函数
|
||
def callback(length):
|
||
self._execute_divide(direction, length)
|
||
|
||
_divide_callback = callback
|
||
|
||
# 创建并显示输入对话框
|
||
if hasattr(bpy.ops, 'suw') and hasattr(bpy.ops.suw, 'zone_divide_input'):
|
||
# 设置对话框属性
|
||
bpy.context.window_manager.suw_zone_divide_input_direction_name = dir_name
|
||
|
||
# 显示对话框
|
||
bpy.ops.suw.zone_divide_input('INVOKE_DEFAULT')
|
||
else:
|
||
# 如果operator未注册,使用默认值
|
||
default_length = 200.0
|
||
print(f"📐 {dir_name}分割: {default_length}mm (使用默认值)")
|
||
self._execute_divide(direction, default_length)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Blender分割输入失败: {e}")
|
||
# 降级到默认值
|
||
default_length = 200.0
|
||
self._execute_divide(direction, default_length)
|
||
|
||
def _stub_divide_input(self, dir_name: str, direction: int):
|
||
"""存根模式分割输入"""
|
||
default_length = 200.0
|
||
print(f"📐 区域分割输入: {dir_name}分割={default_length}mm")
|
||
self._execute_divide(direction, default_length)
|
||
|
||
def on_left_button_down(self, x: int, y: int):
|
||
"""鼠标左键点击事件"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
self._blender_pick_zone(x, y)
|
||
else:
|
||
self._stub_pick_zone(x, y)
|
||
|
||
# 清除选择
|
||
self._clear_selection()
|
||
|
||
except Exception as e:
|
||
logger.debug(f"鼠标点击处理失败: {e}")
|
||
|
||
def _blender_pick_zone(self, x: int, y: int):
|
||
"""Blender中拾取区域"""
|
||
try:
|
||
# 使用拾取助手
|
||
region = bpy.context.region
|
||
rv3d = bpy.context.region_data
|
||
|
||
# 创建拾取射线
|
||
view_vector = view3d_utils.region_2d_to_vector_3d(
|
||
region, rv3d, (x, y))
|
||
ray_origin = view3d_utils.region_2d_to_origin_3d(
|
||
region, rv3d, (x, y))
|
||
|
||
# 执行射线检测
|
||
result, location, normal, index, obj, matrix = bpy.context.scene.ray_cast(
|
||
bpy.context.view_layer.depsgraph, ray_origin, view_vector
|
||
)
|
||
|
||
if result and obj:
|
||
# 检查是否是有效的区域对象
|
||
if self._is_valid_zone(obj):
|
||
uid = self._get_entity_attr(obj, "uid")
|
||
zid = self._get_entity_attr(obj, "zid")
|
||
typ = self._get_entity_attr(obj, "typ")
|
||
|
||
if typ == "zid":
|
||
current_selected = self._get_selected_zone()
|
||
if current_selected != obj:
|
||
# 选择新区域
|
||
data = {
|
||
"uid": uid,
|
||
"zid": zid,
|
||
"pid": -1,
|
||
"cp": -1
|
||
}
|
||
|
||
# 发送选择命令
|
||
set_cmd("r01", data) # select_client
|
||
|
||
# 本地选择
|
||
self._sel_zone_local(data)
|
||
|
||
logger.info(f"🎯 选择区域: uid={uid}, zid={zid}")
|
||
|
||
except Exception as e:
|
||
logger.debug(f"Blender区域拾取失败: {e}")
|
||
|
||
def _stub_pick_zone(self, x: int, y: int):
|
||
"""存根模式区域拾取"""
|
||
# 模拟选择一个区域
|
||
if x % 40 == 0: # 简单的命中检测
|
||
data = {
|
||
"uid": "test_uid",
|
||
"zid": "test_zid",
|
||
"pid": -1,
|
||
"cp": -1
|
||
}
|
||
|
||
print(f"🎯 存根模式选择区域: uid={data['uid']}, zid={data['zid']}")
|
||
|
||
# 发送选择命令
|
||
set_cmd("r01", data)
|
||
self._sel_zone_local(data)
|
||
|
||
def on_key_down(self, key: str):
|
||
"""按键按下事件"""
|
||
try:
|
||
if key == "CTRL":
|
||
# 切换分割模式
|
||
self.pattern = "back" if self.pattern == "up" else "up"
|
||
self._reset_status_text()
|
||
logger.debug(f"🔄 切换分割模式: {self.pattern}")
|
||
|
||
except Exception as e:
|
||
logger.debug(f"按键处理失败: {e}")
|
||
|
||
def on_key_up(self, key: str):
|
||
"""按键释放事件"""
|
||
try:
|
||
if key == "UP":
|
||
direction = VSSpatialPos_K if self.pattern == "back" else VSSpatialPos_T
|
||
self.divide(direction)
|
||
|
||
elif key == "DOWN":
|
||
direction = VSSpatialPos_F if self.pattern == "back" else VSSpatialPos_B
|
||
self.divide(direction)
|
||
|
||
elif key == "LEFT" and self.pattern == "up":
|
||
self.divide(VSSpatialPos_L)
|
||
|
||
elif key == "RIGHT" and self.pattern == "up":
|
||
self.divide(VSSpatialPos_R)
|
||
|
||
logger.debug(f"⌨️ 分割方向键: {key}, 模式: {self.pattern}")
|
||
|
||
except Exception as e:
|
||
logger.debug(f"方向键处理失败: {e}")
|
||
|
||
def draw(self):
|
||
"""绘制工具预览"""
|
||
try:
|
||
# 更新状态文本
|
||
self._set_status_text(self.tooltip)
|
||
|
||
if BLENDER_AVAILABLE:
|
||
self._draw_blender()
|
||
else:
|
||
self._draw_stub()
|
||
|
||
except Exception as e:
|
||
logger.debug(f"绘制失败: {e}")
|
||
|
||
def _draw_blender(self):
|
||
"""Blender绘制"""
|
||
try:
|
||
# 这里可以绘制分割预览线条或其他辅助元素
|
||
# 暂时只更新状态
|
||
logger.debug("🎨 Blender区域分割绘制")
|
||
|
||
except Exception as e:
|
||
logger.debug(f"Blender绘制失败: {e}")
|
||
|
||
def _draw_stub(self):
|
||
"""存根绘制"""
|
||
# print(f"🎨 区域分割模式: {self.pattern}")
|
||
pass
|
||
|
||
# 辅助方法
|
||
def _get_selected_zone(self):
|
||
"""获取选中的区域"""
|
||
try:
|
||
from .suw_core import get_selection_manager
|
||
selection_manager = get_selection_manager()
|
||
return selection_manager.selected_zone()
|
||
except:
|
||
return None
|
||
|
||
def _sel_zone_local(self, data: Dict[str, Any]):
|
||
"""本地区域选择"""
|
||
try:
|
||
from .suw_core import get_selection_manager
|
||
selection_manager = get_selection_manager()
|
||
selection_manager._sel_zone_local(data)
|
||
logger.debug(f"🎯 本地区域选择: {data}")
|
||
except Exception as e:
|
||
logger.debug(f"本地区域选择失败: {e}")
|
||
|
||
def _is_valid_zone(self, obj) -> bool:
|
||
"""检查是否是有效的区域对象"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
# 检查对象属性
|
||
uid = self._get_entity_attr(obj, "uid")
|
||
return uid is not None
|
||
else:
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.debug(f"区域有效性检查失败: {e}")
|
||
return False
|
||
|
||
def _get_entity_attr(self, entity: Any, attr: str, default: Any = None) -> Any:
|
||
"""获取实体属性"""
|
||
try:
|
||
if BLENDER_AVAILABLE and entity:
|
||
# 从Blender对象获取自定义属性
|
||
return entity.get(attr, default) if hasattr(entity, 'get') else default
|
||
elif isinstance(entity, dict):
|
||
return entity.get(attr, default)
|
||
else:
|
||
return default
|
||
|
||
except Exception as e:
|
||
logger.debug(f"获取实体属性失败: {e}")
|
||
return default
|
||
|
||
def _set_status_text(self, text: str):
|
||
"""设置状态文本"""
|
||
try:
|
||
StatusBarManager.set_status_text(text)
|
||
except Exception as e:
|
||
logger.debug(f"设置状态文本失败: {e}")
|
||
|
||
def _show_message(self, message: str):
|
||
"""显示消息"""
|
||
try:
|
||
MessageBoxManager.show_message(message, "SUWood", 'INFO')
|
||
except Exception as e:
|
||
logger.error(f"显示消息失败: {e}")
|
||
|
||
def _clear_selection(self):
|
||
"""清除选择"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
bpy.ops.object.select_all(action='DESELECT')
|
||
logger.debug("🧹 清除选择")
|
||
|
||
except Exception as e:
|
||
logger.debug(f"清除选择失败: {e}")
|
||
|
||
# 工具函数
|
||
|
||
|
||
def create_zone_div1_tool() -> SWZoneDiv1Tool:
|
||
"""创建区域分割工具"""
|
||
return SWZoneDiv1Tool()
|
||
|
||
|
||
def activate_zone_div1_tool():
|
||
"""激活区域分割工具"""
|
||
tool = SWZoneDiv1Tool()
|
||
tool.activate()
|
||
return tool
|
||
|
||
# 快捷键映射函数
|
||
|
||
|
||
def handle_zone_division_key(key: str, tool: SWZoneDiv1Tool):
|
||
"""处理区域分割快捷键"""
|
||
try:
|
||
if key in ["CTRL"]:
|
||
tool.on_key_down(key)
|
||
elif key in ["UP", "DOWN", "LEFT", "RIGHT"]:
|
||
tool.on_key_up(key)
|
||
else:
|
||
logger.debug(f"未处理的快捷键: {key}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"快捷键处理失败: {e}")
|
||
|
||
|
||
# 方向常量映射
|
||
DIRECTION_MAP = {
|
||
"UP_NORMAL": VSSpatialPos_T, # 上分割(普通模式)
|
||
"DOWN_NORMAL": VSSpatialPos_B, # 下分割(普通模式)
|
||
"LEFT": VSSpatialPos_L, # 左分割
|
||
"RIGHT": VSSpatialPos_R, # 右分割
|
||
"UP_BACK": VSSpatialPos_K, # 上分割(前后模式,实际是后)
|
||
"DOWN_BACK": VSSpatialPos_F, # 下分割(前后模式,实际是前)
|
||
}
|
||
|
||
# 注册Blender Operator
|
||
if BLENDER_AVAILABLE:
|
||
def register_zone_divide_operators():
|
||
"""注册区域分割相关的Blender Operator"""
|
||
try:
|
||
bpy.utils.register_class(SUWZoneDivideInputOperator)
|
||
logger.info("✅ 区域分割Operator注册成功")
|
||
except Exception as e:
|
||
logger.error(f"注册Operator失败: {e}")
|
||
|
||
def unregister_zone_divide_operators():
|
||
"""注销区域分割相关的Blender Operator"""
|
||
try:
|
||
bpy.utils.unregister_class(SUWZoneDivideInputOperator)
|
||
logger.info("✅ 区域分割Operator注销成功")
|
||
except Exception as e:
|
||
logger.error(f"注销Operator失败: {e}")
|
||
|
||
print("🎉 SWZoneDiv1Tool完整翻译完成!")
|
||
print("✅ 功能包括:")
|
||
print(" • 双模式分割系统")
|
||
print(" • 六方向分割支持")
|
||
print(" • 智能区域拾取")
|
||
print(" • 快捷键操作")
|
||
print(" • 分割参数输入")
|
||
print(" • 实时状态提示")
|
||
print(" • 自动选择管理")
|
||
print(" • Blender/存根双模式")
|
||
print(" • 完整的交互体验")
|
||
print(" • 完善的Blender UI集成")
|