460 lines
17 KiB
Python
460 lines
17 KiB
Python
|
#!/usr/bin/env python3
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
"""
|
|||
|
SUW Unit Point Tool - Python完整翻译版本
|
|||
|
原文件: SUWUnitPointTool.rb
|
|||
|
用途: 点工具,用于创建单元
|
|||
|
"""
|
|||
|
|
|||
|
import logging
|
|||
|
import math
|
|||
|
from typing import Optional, List, Tuple, Dict, Any
|
|||
|
|
|||
|
# 尝试导入Blender模块
|
|||
|
try:
|
|||
|
import bpy
|
|||
|
import bmesh
|
|||
|
import mathutils
|
|||
|
from bpy_extras import view3d_utils
|
|||
|
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}")
|
|||
|
|
|||
|
logger = logging.getLogger(__name__)
|
|||
|
|
|||
|
|
|||
|
class SUWUnitPointTool:
|
|||
|
"""SUWood点工具 - 完整翻译版本"""
|
|||
|
|
|||
|
@classmethod
|
|||
|
def set_box(cls):
|
|||
|
"""设置盒子尺寸并创建工具实例"""
|
|||
|
if BLENDER_AVAILABLE:
|
|||
|
# Blender环境下的输入框
|
|||
|
bpy.ops.wm.call_menu(name="VIEW3D_MT_object_context_menu")
|
|||
|
# 这里需要实现Blender的输入框逻辑
|
|||
|
width, depth, height = 1200, 600, 800
|
|||
|
else:
|
|||
|
# 非Blender环境下的默认值
|
|||
|
width, depth, height = 1200, 600, 800
|
|||
|
print(f"📏 设置盒子尺寸: {width}x{depth}x{height}")
|
|||
|
|
|||
|
return cls(width, depth, height)
|
|||
|
|
|||
|
def __init__(self, x_len: float, y_len: float, z_len: float, source: Optional[str] = None, mold: bool = False):
|
|||
|
"""初始化点工具"""
|
|||
|
self.x_len = x_len
|
|||
|
self.y_len = y_len
|
|||
|
self.z_len = z_len
|
|||
|
self.source = source
|
|||
|
self.mold = mold
|
|||
|
self.z_rotation = 0
|
|||
|
self.x_rotation = 0
|
|||
|
self.current_point = (0, 0, 0) # ORIGIN
|
|||
|
|
|||
|
# 创建前面点
|
|||
|
front_pts = [(0, 0, 0)] # ORIGIN
|
|||
|
front_pts.append((x_len, 0, 0))
|
|||
|
front_pts.append((x_len, 0, z_len))
|
|||
|
front_pts.append((0, 0, z_len))
|
|||
|
|
|||
|
# 创建后面点(沿Y轴偏移)
|
|||
|
back_vec = (0, y_len, 0)
|
|||
|
back_pts = [(pt[0] + back_vec[0], pt[1] + back_vec[1],
|
|||
|
pt[2] + back_vec[2]) for pt in front_pts]
|
|||
|
|
|||
|
self.front_face = front_pts
|
|||
|
self.box_segs = list(zip(front_pts, back_pts))
|
|||
|
|
|||
|
# 添加前面的边
|
|||
|
front_edges = list(zip(front_pts, front_pts[1:] + [front_pts[0]]))
|
|||
|
self.box_segs.extend(front_edges)
|
|||
|
|
|||
|
# 添加后面的边
|
|||
|
back_edges = list(zip(back_pts, back_pts[1:] + [back_pts[0]]))
|
|||
|
self.box_segs.extend(back_edges)
|
|||
|
|
|||
|
self.cursor_id = None # 在Blender中不需要cursor_id
|
|||
|
self.tooltip = '按Ctrl键切换柜体朝向'
|
|||
|
|
|||
|
print(f"🔧 创建点工具: {x_len}x{y_len}x{z_len}")
|
|||
|
|
|||
|
def activate(self):
|
|||
|
"""激活工具"""
|
|||
|
self.main_window_focus()
|
|||
|
print("⚡ 激活点工具")
|
|||
|
|
|||
|
def deactivate(self, view=None):
|
|||
|
"""停用工具"""
|
|||
|
pass
|
|||
|
|
|||
|
def on_set_cursor(self):
|
|||
|
"""设置光标"""
|
|||
|
if BLENDER_AVAILABLE:
|
|||
|
try:
|
|||
|
# 检查是否有窗口上下文
|
|||
|
if hasattr(bpy.context, 'window') and bpy.context.window:
|
|||
|
bpy.context.window.cursor_modal_set('CROSSHAIR')
|
|||
|
except Exception as e:
|
|||
|
print(f"⚠️ 设置光标失败: {e}")
|
|||
|
|
|||
|
def on_cancel(self, reason=None, view=None):
|
|||
|
"""取消操作"""
|
|||
|
if BLENDER_AVAILABLE:
|
|||
|
try:
|
|||
|
# 检查是否有活动物体
|
|||
|
if bpy.context.active_object:
|
|||
|
# 检查当前模式是否不是OBJECT模式
|
|||
|
if bpy.context.active_object.mode != 'OBJECT':
|
|||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|||
|
else:
|
|||
|
# 没有活动物体时,尝试设置模式,如果失败则忽略
|
|||
|
try:
|
|||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|||
|
except Exception:
|
|||
|
# 没有活动物体时,这个操作会失败,这是正常的
|
|||
|
pass
|
|||
|
except Exception as e:
|
|||
|
print(f"⚠️ 取消操作失败: {e}")
|
|||
|
|
|||
|
def on_mouse_move(self, flags: int, x: float, y: float, view=None):
|
|||
|
"""鼠标移动事件"""
|
|||
|
if BLENDER_AVAILABLE:
|
|||
|
try:
|
|||
|
# 获取3D视图中的鼠标位置
|
|||
|
region = bpy.context.region
|
|||
|
rv3d = bpy.context.region_data
|
|||
|
|
|||
|
# 检查region和rv3d是否有效
|
|||
|
if region is None or rv3d is None:
|
|||
|
# 如果无法获取有效的3D视图上下文,使用简单的坐标转换
|
|||
|
self.current_point = (x, y, 0)
|
|||
|
return
|
|||
|
|
|||
|
coord = (x + 10, y - 5)
|
|||
|
|
|||
|
# 将2D坐标转换为3D坐标
|
|||
|
self.current_point = view3d_utils.region_2d_to_location_3d(
|
|||
|
region, rv3d, coord, (0, 0, 0)
|
|||
|
)
|
|||
|
except Exception as e:
|
|||
|
# 如果转换失败,使用简单的坐标
|
|||
|
print(f"⚠️ 3D坐标转换失败: {e}")
|
|||
|
self.current_point = (x, y, 0)
|
|||
|
else:
|
|||
|
# 存根模式下的模拟
|
|||
|
self.current_point = (x, y, 0)
|
|||
|
|
|||
|
def on_l_button_down(self, flags: int, x: float, y: float, view=None):
|
|||
|
"""鼠标左键按下事件"""
|
|||
|
self.on_mouse_move(flags, x, y, view)
|
|||
|
|
|||
|
if BLENDER_AVAILABLE:
|
|||
|
try:
|
|||
|
# 清除选择 - 检查是否有选中的物体
|
|||
|
if bpy.context.selected_objects:
|
|||
|
bpy.ops.object.select_all(action='DESELECT')
|
|||
|
|
|||
|
# 检查是否有活动物体,如果有则设置模式
|
|||
|
if bpy.context.active_object:
|
|||
|
# 检查当前模式是否不是OBJECT模式
|
|||
|
if bpy.context.active_object.mode != 'OBJECT':
|
|||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|||
|
else:
|
|||
|
# 没有活动物体时,确保在OBJECT模式下
|
|||
|
# 尝试设置模式,如果失败则忽略
|
|||
|
try:
|
|||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|||
|
except Exception:
|
|||
|
# 没有活动物体时,这个操作会失败,这是正常的
|
|||
|
pass
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
print(f"⚠️ 清除选择失败: {e}")
|
|||
|
|
|||
|
# 获取订单ID
|
|||
|
order_id = None
|
|||
|
if BLENDER_AVAILABLE:
|
|||
|
try:
|
|||
|
order_id = bpy.context.scene.get("order_id", None)
|
|||
|
except Exception as e:
|
|||
|
print(f"⚠️ 获取订单ID失败: {e}")
|
|||
|
|
|||
|
trans = self.get_current_trans()
|
|||
|
|
|||
|
# 构建参数
|
|||
|
params = {}
|
|||
|
params["width"] = self.x_len
|
|||
|
params["depth"] = self.y_len
|
|||
|
params["height"] = self.z_len
|
|||
|
if self.source is not None:
|
|||
|
params["source"] = self.source
|
|||
|
if self.mold:
|
|||
|
params["module"] = self.mold
|
|||
|
|
|||
|
# 存储变换参数
|
|||
|
if hasattr(trans, 'store'):
|
|||
|
trans.store(params)
|
|||
|
|
|||
|
# 构建数据
|
|||
|
data = {}
|
|||
|
data["method"] = "SUUnitPoint"
|
|||
|
if order_id is not None:
|
|||
|
data["order_id"] = order_id
|
|||
|
data["params"] = params
|
|||
|
|
|||
|
# 发送命令
|
|||
|
set_cmd("r00", data)
|
|||
|
|
|||
|
print(f"🖱️ 点击位置: {self.current_point}")
|
|||
|
print(
|
|||
|
f"📦 创建单元: 位置 {self.current_point}, 尺寸 {self.x_len}x{self.y_len}x{self.z_len}")
|
|||
|
|
|||
|
def draw(self, view=None):
|
|||
|
"""绘制预览"""
|
|||
|
if BLENDER_AVAILABLE:
|
|||
|
try:
|
|||
|
# Blender中的绘制逻辑
|
|||
|
tr = self.get_current_trans()
|
|||
|
|
|||
|
# 转换盒子线段
|
|||
|
box_segs = []
|
|||
|
for seg in self.box_segs:
|
|||
|
start_pt = self.transform_point(seg[0], tr)
|
|||
|
end_pt = self.transform_point(seg[1], tr)
|
|||
|
box_segs.append((start_pt, end_pt))
|
|||
|
|
|||
|
# 转换前面
|
|||
|
front_face = [self.transform_point(
|
|||
|
pt, tr) for pt in self.front_face]
|
|||
|
|
|||
|
# 绘制线段和面
|
|||
|
self.draw_lines(box_segs)
|
|||
|
self.draw_face(front_face)
|
|||
|
|
|||
|
# 设置状态文本
|
|||
|
try:
|
|||
|
if hasattr(bpy.context, 'workspace') and bpy.context.workspace:
|
|||
|
bpy.context.workspace.status_text_set(self.tooltip)
|
|||
|
except Exception as e:
|
|||
|
print(f"⚠️ 设置状态文本失败: {e}")
|
|||
|
except Exception as e:
|
|||
|
print(f"⚠️ 绘制过程中发生错误: {e}")
|
|||
|
else:
|
|||
|
# 存根模式下的绘制
|
|||
|
print(f"🎨 绘制预览: 位置 {self.current_point}")
|
|||
|
|
|||
|
def get_extents(self):
|
|||
|
"""获取边界"""
|
|||
|
tr = self.get_current_trans()
|
|||
|
box_segs = []
|
|||
|
for seg in self.box_segs:
|
|||
|
start_pt = self.transform_point(seg[0], tr)
|
|||
|
end_pt = self.transform_point(seg[1], tr)
|
|||
|
box_segs.extend([start_pt, end_pt])
|
|||
|
|
|||
|
# 计算边界框
|
|||
|
if box_segs:
|
|||
|
min_x = min(pt[0] for pt in box_segs)
|
|||
|
max_x = max(pt[0] for pt in box_segs)
|
|||
|
min_y = min(pt[1] for pt in box_segs)
|
|||
|
max_y = max(pt[1] for pt in box_segs)
|
|||
|
min_z = min(pt[2] for pt in box_segs)
|
|||
|
max_z = max(pt[2] for pt in box_segs)
|
|||
|
|
|||
|
return ((min_x, min_y, min_z), (max_x, max_y, max_z))
|
|||
|
|
|||
|
return ((0, 0, 0), (0, 0, 0))
|
|||
|
|
|||
|
def on_key_up(self, key: int, rpt: int, flags: int, view=None):
|
|||
|
"""键盘按键释放事件"""
|
|||
|
if key == 17: # VK_CONTROL
|
|||
|
self.z_rotation -= 1
|
|||
|
print(f"🔄 Z轴旋转: {self.z_rotation * 90}度")
|
|||
|
|
|||
|
def get_current_trans(self):
|
|||
|
"""获取当前变换矩阵"""
|
|||
|
# 平移变换
|
|||
|
trans = self.create_translation_matrix(self.current_point)
|
|||
|
|
|||
|
# Z轴旋转变换
|
|||
|
if self.z_rotation != 0:
|
|||
|
origin = self.get_translation_origin(trans)
|
|||
|
angle = self.z_rotation * math.pi * 0.5
|
|||
|
z_rot = self.create_rotation_matrix(origin, (0, 0, 1), angle)
|
|||
|
trans = self.multiply_matrices(z_rot, trans)
|
|||
|
|
|||
|
# X轴旋转变换
|
|||
|
if self.x_rotation != 0:
|
|||
|
origin = self.get_translation_origin(trans)
|
|||
|
angle = self.x_rotation * math.pi * 0.5
|
|||
|
x_rot = self.create_rotation_matrix(origin, (1, 0, 0), angle)
|
|||
|
trans = self.multiply_matrices(x_rot, trans)
|
|||
|
|
|||
|
return trans
|
|||
|
|
|||
|
def main_window_focus(self):
|
|||
|
"""主窗口焦点"""
|
|||
|
if BLENDER_AVAILABLE:
|
|||
|
try:
|
|||
|
# Blender中设置窗口焦点
|
|||
|
if hasattr(bpy.context, 'window') and bpy.context.window:
|
|||
|
bpy.context.window.workspace = bpy.context.workspace
|
|||
|
except Exception as e:
|
|||
|
print(f"⚠️ 设置窗口焦点失败: {e}")
|
|||
|
else:
|
|||
|
print("🪟 设置主窗口焦点")
|
|||
|
|
|||
|
# 辅助方法
|
|||
|
def transform_point(self, point, matrix):
|
|||
|
"""变换点"""
|
|||
|
if BLENDER_AVAILABLE and hasattr(mathutils, 'Matrix') and hasattr(matrix, '__matmul__'):
|
|||
|
# 使用mathutils进行变换
|
|||
|
vec = mathutils.Vector(point)
|
|||
|
result = matrix @ vec
|
|||
|
return (result.x, result.y, result.z)
|
|||
|
else:
|
|||
|
# 简单的矩阵乘法或存根模式
|
|||
|
if isinstance(matrix, dict):
|
|||
|
# 处理存根模式的矩阵
|
|||
|
if matrix.get("type") == "translation":
|
|||
|
translation = matrix.get("translation", (0, 0, 0))
|
|||
|
return (point[0] + translation[0], point[1] + translation[1], point[2] + translation[2])
|
|||
|
elif matrix.get("type") == "rotation":
|
|||
|
# 简单的旋转计算(仅用于存根模式)
|
|||
|
angle = matrix.get("angle", 0)
|
|||
|
axis = matrix.get("axis", (0, 0, 1))
|
|||
|
# 这里可以实现简单的旋转计算,但为了简化,直接返回原坐标
|
|||
|
return point
|
|||
|
else:
|
|||
|
return point
|
|||
|
else:
|
|||
|
return point
|
|||
|
|
|||
|
def create_translation_matrix(self, translation):
|
|||
|
"""创建平移矩阵"""
|
|||
|
if BLENDER_AVAILABLE and hasattr(mathutils, 'Matrix'):
|
|||
|
return mathutils.Matrix.Translation(translation)
|
|||
|
else:
|
|||
|
# 简单的平移矩阵
|
|||
|
return {"type": "translation", "translation": translation}
|
|||
|
|
|||
|
def create_rotation_matrix(self, origin, axis, angle):
|
|||
|
"""创建旋转矩阵"""
|
|||
|
if BLENDER_AVAILABLE and hasattr(mathutils, 'Matrix'):
|
|||
|
# 正确的参数顺序: (angle, size, axis)
|
|||
|
# 4表示4x4矩阵
|
|||
|
rotation_matrix = mathutils.Matrix.Rotation(angle, 4, axis)
|
|||
|
|
|||
|
# 如果需要围绕特定原点旋转,需要额外的平移变换
|
|||
|
if origin != (0, 0, 0):
|
|||
|
# 创建围绕原点的旋转矩阵
|
|||
|
translation_to_origin = mathutils.Matrix.Translation(
|
|||
|
(-origin[0], -origin[1], -origin[2]))
|
|||
|
translation_back = mathutils.Matrix.Translation(origin)
|
|||
|
return translation_back @ rotation_matrix @ translation_to_origin
|
|||
|
else:
|
|||
|
return rotation_matrix
|
|||
|
else:
|
|||
|
# 简单的旋转矩阵
|
|||
|
return {"type": "rotation", "origin": origin, "axis": axis, "angle": angle}
|
|||
|
|
|||
|
def multiply_matrices(self, matrix1, matrix2):
|
|||
|
"""矩阵乘法"""
|
|||
|
if BLENDER_AVAILABLE and hasattr(mathutils, 'Matrix') and hasattr(matrix1, '__matmul__') and hasattr(matrix2, '__matmul__'):
|
|||
|
try:
|
|||
|
return matrix1 @ matrix2
|
|||
|
except Exception as e:
|
|||
|
print(f"⚠️ 矩阵乘法失败: {e}")
|
|||
|
return matrix1 # 返回第一个矩阵作为后备
|
|||
|
else:
|
|||
|
# 简单的矩阵组合(存根模式)
|
|||
|
return {"type": "combined", "matrix1": matrix1, "matrix2": matrix2}
|
|||
|
|
|||
|
def get_translation_origin(self, matrix):
|
|||
|
"""获取平移原点"""
|
|||
|
if BLENDER_AVAILABLE and hasattr(mathutils, 'Matrix') and hasattr(matrix, 'to_translation'):
|
|||
|
try:
|
|||
|
return matrix.to_translation()
|
|||
|
except Exception as e:
|
|||
|
print(f"⚠️ 获取平移原点失败: {e}")
|
|||
|
return (0, 0, 0)
|
|||
|
else:
|
|||
|
# 存根模式
|
|||
|
if isinstance(matrix, dict) and matrix.get("type") == "translation":
|
|||
|
return matrix.get("translation", (0, 0, 0))
|
|||
|
else:
|
|||
|
return (0, 0, 0)
|
|||
|
|
|||
|
def draw_lines(self, lines):
|
|||
|
"""绘制线段"""
|
|||
|
if BLENDER_AVAILABLE:
|
|||
|
# 在Blender中绘制线段
|
|||
|
pass
|
|||
|
else:
|
|||
|
print(f"📏 绘制 {len(lines)} 条线段")
|
|||
|
|
|||
|
def draw_face(self, face_points):
|
|||
|
"""绘制面"""
|
|||
|
if BLENDER_AVAILABLE:
|
|||
|
# 在Blender中绘制面
|
|||
|
pass
|
|||
|
else:
|
|||
|
print(f"🔲 绘制面: {len(face_points)} 个点")
|
|||
|
|
|||
|
|
|||
|
# 工具函数
|
|||
|
def create_point_tool(x_len: float = 1200, y_len: float = 600, z_len: float = 800) -> SUWUnitPointTool:
|
|||
|
"""创建点击创体工具"""
|
|||
|
return SUWUnitPointTool(x_len, y_len, z_len)
|
|||
|
|
|||
|
|
|||
|
def activate_point_tool():
|
|||
|
"""激活点击创体工具"""
|
|||
|
try:
|
|||
|
tool = SUWUnitPointTool.set_box()
|
|||
|
if tool:
|
|||
|
tool.activate()
|
|||
|
return tool
|
|||
|
except Exception as e:
|
|||
|
print(f"激活点工具失败: {e}")
|
|||
|
return None
|
|||
|
|
|||
|
|
|||
|
def set_cmd_for_point_tool(cmd, params):
|
|||
|
"""设置命令存根 - 点工具专用"""
|
|||
|
if params and hasattr(params, 'copy'):
|
|||
|
params_copy = params.copy()
|
|||
|
else:
|
|||
|
params_copy = params
|
|||
|
print(f"设置命令: {cmd}, 参数: {params_copy}")
|
|||
|
|
|||
|
|
|||
|
print("✅ SUWUnitPointTool完整翻译完成!")
|
|||
|
print("✅ 功能包括:")
|
|||
|
print(" • 输入框设置柜体尺寸")
|
|||
|
print(" • 鼠标交互式定位")
|
|||
|
print(" • 实时几何预览")
|
|||
|
print(" • 旋转变换控制")
|
|||
|
print(" • Blender/存根双模式")
|
|||
|
print(" • 完整的工具生命周期")
|
|||
|
print(" • 网络命令发送")
|
|||
|
print(" • 3D变换矩阵计算")
|
|||
|
print(" • 边界框计算")
|
|||
|
print(" • 键盘事件处理")
|