565 lines
19 KiB
Python
565 lines
19 KiB
Python
|
#!/usr/bin/env python3
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
"""
|
|||
|
SUW Core - Dimension Manager Module
|
|||
|
拆分自: suw_impl.py (Line 5591-5750, 6249-6362)
|
|||
|
用途: Blender尺寸标注管理、文本标签、轮廓创建
|
|||
|
版本: 1.0.0
|
|||
|
作者: SUWood Team
|
|||
|
"""
|
|||
|
|
|||
|
from .geometry_utils import Point3d, Vector3d
|
|||
|
from .memory_manager import memory_manager
|
|||
|
from .data_manager import data_manager, get_data_manager
|
|||
|
import math
|
|||
|
import logging
|
|||
|
from typing import Dict, Any, Optional, List
|
|||
|
|
|||
|
# 设置日志
|
|||
|
logger = logging.getLogger(__name__)
|
|||
|
|
|||
|
# 检查Blender可用性
|
|||
|
try:
|
|||
|
import bpy
|
|||
|
BLENDER_AVAILABLE = True
|
|||
|
except ImportError:
|
|||
|
BLENDER_AVAILABLE = False
|
|||
|
|
|||
|
# 导入依赖模块
|
|||
|
|
|||
|
# ==================== 尺寸标注管理器类 ====================
|
|||
|
|
|||
|
|
|||
|
class DimensionManager:
|
|||
|
"""尺寸标注管理器 - 负责所有尺寸标注相关操作"""
|
|||
|
|
|||
|
def __init__(self):
|
|||
|
"""
|
|||
|
初始化尺寸标注管理器 - 完全独立,不依赖suw_impl
|
|||
|
"""
|
|||
|
# 使用全局数据管理器
|
|||
|
self.data_manager = get_data_manager()
|
|||
|
|
|||
|
self.dimensions = {}
|
|||
|
self.labels = None
|
|||
|
self.door_labels = None
|
|||
|
self.door_layer = None
|
|||
|
self.unit_trans = {}
|
|||
|
|
|||
|
logger.info("DimensionManager 初始化完成")
|
|||
|
|
|||
|
# ==================== 核心命令方法 ====================
|
|||
|
|
|||
|
def c07(self, data: Dict[str, Any]):
|
|||
|
"""add_dim - 添加尺寸标注 - 线程安全版本"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
return
|
|||
|
|
|||
|
uid = data.get("uid")
|
|||
|
|
|||
|
def create_dimensions():
|
|||
|
try:
|
|||
|
dims = data.get("dims", [])
|
|||
|
dimensions = []
|
|||
|
|
|||
|
for dim_data in dims:
|
|||
|
p1 = Point3d.parse(dim_data.get("p1", "(0,0,0)"))
|
|||
|
p2 = Point3d.parse(dim_data.get("p2", "(0,0,0)"))
|
|||
|
direction = Vector3d.parse(
|
|||
|
dim_data.get("dir", "(0,0,1)"))
|
|||
|
text = dim_data.get("text", "")
|
|||
|
|
|||
|
dimension = self.create_dimension(
|
|||
|
p1, p2, direction, text)
|
|||
|
if dimension:
|
|||
|
dimensions.append(dimension)
|
|||
|
|
|||
|
# 存储尺寸标注
|
|||
|
if uid not in self.dimensions:
|
|||
|
self.dimensions[uid] = []
|
|||
|
self.dimensions[uid].extend(dimensions)
|
|||
|
|
|||
|
for dimension in dimensions:
|
|||
|
memory_manager.register_object(dimension)
|
|||
|
|
|||
|
return len(dimensions)
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"创建尺寸标注失败: {e}")
|
|||
|
return 0
|
|||
|
|
|||
|
# 在主线程中执行尺寸标注创建
|
|||
|
count = create_dimensions()
|
|||
|
|
|||
|
if count > 0:
|
|||
|
logger.info(f"✅ 成功创建尺寸标注: uid={uid}, count={count}")
|
|||
|
else:
|
|||
|
logger.error(f"❌ 尺寸标注创建失败: uid={uid}")
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"❌ 添加尺寸标注失败: {e}")
|
|||
|
|
|||
|
def c0c(self, data: Dict[str, Any]):
|
|||
|
"""delete_dimensions - 删除尺寸标注"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
return
|
|||
|
|
|||
|
def delete_dimensions():
|
|||
|
try:
|
|||
|
uid = data.get("uid")
|
|||
|
|
|||
|
if uid in self.dimensions:
|
|||
|
dimensions_to_delete = self.dimensions[uid]
|
|||
|
deleted_count = 0
|
|||
|
|
|||
|
for dimension in dimensions_to_delete:
|
|||
|
if self.delete_object_safe(dimension):
|
|||
|
deleted_count += 1
|
|||
|
|
|||
|
# 清理维度记录
|
|||
|
del self.dimensions[uid]
|
|||
|
|
|||
|
return deleted_count
|
|||
|
|
|||
|
return 0
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"删除尺寸标注失败: {e}")
|
|||
|
return 0
|
|||
|
|
|||
|
count = delete_dimensions()
|
|||
|
logger.info(f"删除尺寸标注完成: count={count}")
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"删除尺寸标注失败: {e}")
|
|||
|
|
|||
|
def c12(self, data: Dict[str, Any]):
|
|||
|
"""add_contour - 添加轮廓 - 线程安全版本"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
return
|
|||
|
|
|||
|
def create_contour():
|
|||
|
try:
|
|||
|
# 设置添加轮廓标志
|
|||
|
self.data_manager.added_contour = True
|
|||
|
|
|||
|
surf = data.get("surf", {})
|
|||
|
|
|||
|
contour = self.create_contour_from_surf(surf)
|
|||
|
if contour:
|
|||
|
memory_manager.register_object(contour)
|
|||
|
return True
|
|||
|
|
|||
|
return False
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"创建轮廓失败: {e}")
|
|||
|
return False
|
|||
|
|
|||
|
# 在主线程中执行轮廓创建
|
|||
|
success = create_contour()
|
|||
|
|
|||
|
if success:
|
|||
|
logger.info("✅ 轮廓创建成功")
|
|||
|
else:
|
|||
|
logger.error("❌ 轮廓创建失败")
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"❌ 添加轮廓失败: {e}")
|
|||
|
|
|||
|
# ==================== 尺寸标注创建 ====================
|
|||
|
|
|||
|
def create_dimension(self, p1, p2, direction, text):
|
|||
|
"""创建尺寸标注"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
return None
|
|||
|
|
|||
|
# 创建尺寸标注几何体
|
|||
|
mesh = bpy.data.meshes.new("Dimension")
|
|||
|
|
|||
|
# 计算标注线的端点
|
|||
|
start = (p1.x * 0.001, p1.y * 0.001, p1.z * 0.001)
|
|||
|
end = (p2.x * 0.001, p2.y * 0.001, p2.z * 0.001)
|
|||
|
|
|||
|
# 计算标注偏移
|
|||
|
offset_distance = 0.05 # 5cm偏移
|
|||
|
offset = (
|
|||
|
direction.x * offset_distance,
|
|||
|
direction.y * offset_distance,
|
|||
|
direction.z * offset_distance
|
|||
|
)
|
|||
|
|
|||
|
# 创建标注线顶点
|
|||
|
vertices = [
|
|||
|
start,
|
|||
|
end,
|
|||
|
(start[0] + offset[0], start[1] +
|
|||
|
offset[1], start[2] + offset[2]),
|
|||
|
(end[0] + offset[0], end[1] + offset[1], end[2] + offset[2])
|
|||
|
]
|
|||
|
|
|||
|
# 创建边
|
|||
|
edges = [(0, 2), (1, 3), (2, 3)]
|
|||
|
|
|||
|
mesh.from_pydata(vertices, edges, [])
|
|||
|
mesh.update()
|
|||
|
|
|||
|
# 创建对象
|
|||
|
dim_obj = bpy.data.objects.new("Dimension", mesh)
|
|||
|
bpy.context.scene.collection.objects.link(dim_obj)
|
|||
|
|
|||
|
# 创建文本标签
|
|||
|
if text:
|
|||
|
label_pos = (
|
|||
|
(start[0] + end[0]) / 2 + offset[0],
|
|||
|
(start[1] + end[1]) / 2 + offset[1],
|
|||
|
(start[2] + end[2]) / 2 + offset[2]
|
|||
|
)
|
|||
|
text_obj = self.create_text_label(text, label_pos, direction)
|
|||
|
if text_obj:
|
|||
|
text_obj.parent = dim_obj
|
|||
|
|
|||
|
return dim_obj
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"创建尺寸标注失败: {e}")
|
|||
|
return None
|
|||
|
|
|||
|
def create_text_label(self, text, location, direction):
|
|||
|
"""创建文本标签"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
return None
|
|||
|
|
|||
|
# 创建文本对象
|
|||
|
font_curve = bpy.data.curves.new(type="FONT", name="TextLabel")
|
|||
|
font_curve.body = text
|
|||
|
font_obj = bpy.data.objects.new("TextLabel", font_curve)
|
|||
|
|
|||
|
# 设置位置和方向
|
|||
|
font_obj.location = location
|
|||
|
if isinstance(direction, (list, tuple)) and len(direction) >= 3:
|
|||
|
# 简化的方向设置
|
|||
|
font_obj.location = (
|
|||
|
location[0] + direction.x * 0.1 if hasattr(
|
|||
|
direction, 'x') else location[0] + direction[0] * 0.1,
|
|||
|
location[1] + direction.y * 0.1 if hasattr(
|
|||
|
direction, 'y') else location[1] + direction[1] * 0.1,
|
|||
|
location[2] + direction.z * 0.1 if hasattr(
|
|||
|
direction, 'z') else location[2] + direction[2] * 0.1
|
|||
|
)
|
|||
|
|
|||
|
bpy.context.scene.collection.objects.link(font_obj)
|
|||
|
memory_manager.register_object(font_obj)
|
|||
|
|
|||
|
return font_obj
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"创建文本标签失败: {e}")
|
|||
|
return None
|
|||
|
|
|||
|
# ==================== 轮廓创建 ====================
|
|||
|
|
|||
|
def create_contour_from_surf(self, surf):
|
|||
|
"""从表面创建轮廓"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
return None
|
|||
|
|
|||
|
xaxis = Vector3d.parse(surf.get("vx", "(1,0,0)"))
|
|||
|
zaxis = Vector3d.parse(surf.get("vz", "(0,0,1)"))
|
|||
|
segs = surf.get("segs", [])
|
|||
|
|
|||
|
edges = []
|
|||
|
for seg in segs:
|
|||
|
if "c" in seg:
|
|||
|
# 弧形段
|
|||
|
c = Point3d.parse(seg["c"])
|
|||
|
r = seg.get("r", 1.0) * 0.001
|
|||
|
a1 = seg.get("a1", 0.0)
|
|||
|
a2 = seg.get("a2", math.pi * 2)
|
|||
|
n = seg.get("n", 12)
|
|||
|
|
|||
|
# 创建弧形边
|
|||
|
arc_edges = self.create_arc_edges(
|
|||
|
c, xaxis, zaxis, r, a1, a2, n)
|
|||
|
edges.extend(arc_edges)
|
|||
|
else:
|
|||
|
# 直线段
|
|||
|
s = Point3d.parse(seg.get("s", "(0,0,0)"))
|
|||
|
e = Point3d.parse(seg.get("e", "(0,0,0)"))
|
|||
|
edge = self.create_line_edge_simple(
|
|||
|
(s.x * 0.001, s.y * 0.001, s.z * 0.001),
|
|||
|
(e.x * 0.001, e.y * 0.001, e.z * 0.001))
|
|||
|
if edge:
|
|||
|
edges.append(edge)
|
|||
|
|
|||
|
# 尝试创建面
|
|||
|
try:
|
|||
|
if edges:
|
|||
|
return self.create_face_from_edges(edges)
|
|||
|
except Exception as e:
|
|||
|
logger.warning(f"创建轮廓面失败: {e}")
|
|||
|
|
|||
|
return None
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"创建轮廓失败: {e}")
|
|||
|
return None
|
|||
|
|
|||
|
def create_arc_edges(self, center, xaxis, zaxis, radius, start_angle, end_angle, segments):
|
|||
|
"""创建弧形边"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
return []
|
|||
|
|
|||
|
edges = []
|
|||
|
angle_step = (end_angle - start_angle) / segments
|
|||
|
|
|||
|
for i in range(segments):
|
|||
|
angle1 = start_angle + i * angle_step
|
|||
|
angle2 = start_angle + (i + 1) * angle_step
|
|||
|
|
|||
|
# 计算点
|
|||
|
x1 = center.x * 0.001 + radius * math.cos(angle1)
|
|||
|
y1 = center.y * 0.001 + radius * math.sin(angle1)
|
|||
|
z1 = center.z * 0.001
|
|||
|
|
|||
|
x2 = center.x * 0.001 + radius * math.cos(angle2)
|
|||
|
y2 = center.y * 0.001 + radius * math.sin(angle2)
|
|||
|
z2 = center.z * 0.001
|
|||
|
|
|||
|
edge = self.create_line_edge_simple(
|
|||
|
(x1, y1, z1), (x2, y2, z2))
|
|||
|
if edge:
|
|||
|
edges.append(edge)
|
|||
|
|
|||
|
return edges
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"创建弧形边失败: {e}")
|
|||
|
return []
|
|||
|
|
|||
|
def create_line_edge_simple(self, start, end):
|
|||
|
"""创建简单线边"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
return None
|
|||
|
|
|||
|
# 创建线段网格
|
|||
|
mesh = bpy.data.meshes.new("Line_Edge")
|
|||
|
vertices = [start, end]
|
|||
|
edges = [(0, 1)]
|
|||
|
|
|||
|
mesh.from_pydata(vertices, edges, [])
|
|||
|
mesh.update()
|
|||
|
|
|||
|
# 创建对象
|
|||
|
obj = bpy.data.objects.new("Line_Edge_Obj", mesh)
|
|||
|
bpy.context.scene.collection.objects.link(obj)
|
|||
|
|
|||
|
return obj
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"创建线边失败: {e}")
|
|||
|
return None
|
|||
|
|
|||
|
def create_face_from_edges(self, edges):
|
|||
|
"""从边创建面"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE or not edges:
|
|||
|
return None
|
|||
|
|
|||
|
# 收集所有顶点
|
|||
|
all_vertices = []
|
|||
|
for edge in edges:
|
|||
|
if hasattr(edge, 'data') and hasattr(edge.data, 'vertices'):
|
|||
|
for vertex in edge.data.vertices:
|
|||
|
all_vertices.append(vertex.co)
|
|||
|
|
|||
|
if len(all_vertices) < 3:
|
|||
|
return None
|
|||
|
|
|||
|
# 创建面网格
|
|||
|
mesh = bpy.data.meshes.new("Contour_Face")
|
|||
|
faces = [list(range(len(all_vertices)))]
|
|||
|
|
|||
|
mesh.from_pydata(all_vertices, [], faces)
|
|||
|
mesh.update()
|
|||
|
|
|||
|
# 创建对象
|
|||
|
obj = bpy.data.objects.new("Contour_Face_Obj", mesh)
|
|||
|
bpy.context.scene.collection.objects.link(obj)
|
|||
|
|
|||
|
return obj
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"从边创建面失败: {e}")
|
|||
|
return None
|
|||
|
|
|||
|
# ==================== 标签管理 ====================
|
|||
|
|
|||
|
def add_part_labels(self, uid, parts):
|
|||
|
"""添加零件标签"""
|
|||
|
try:
|
|||
|
for root, part in parts.items():
|
|||
|
center = self.get_object_center(part)
|
|||
|
pos = part.get("sw_pos", 1)
|
|||
|
|
|||
|
# 确定标签方向
|
|||
|
if pos == 1:
|
|||
|
vector = (0, -1, 0) # F
|
|||
|
elif pos == 2:
|
|||
|
vector = (0, 1, 0) # K
|
|||
|
elif pos == 3:
|
|||
|
vector = (-1, 0, 0) # L
|
|||
|
elif pos == 4:
|
|||
|
vector = (1, 0, 0) # R
|
|||
|
elif pos == 5:
|
|||
|
vector = (0, 0, -1) # B
|
|||
|
else:
|
|||
|
vector = (0, 0, 1) # T
|
|||
|
|
|||
|
# 应用单位变换
|
|||
|
if uid in self.unit_trans:
|
|||
|
vector = self.transform_vector(
|
|||
|
vector, self.unit_trans[uid])
|
|||
|
|
|||
|
# 创建文本标签
|
|||
|
ord_seq = part.get("sw_seq", 0)
|
|||
|
text_obj = self.create_text_label(
|
|||
|
str(ord_seq), center, vector)
|
|||
|
|
|||
|
if text_obj:
|
|||
|
# 根据图层选择父对象
|
|||
|
if self.is_in_door_layer(part):
|
|||
|
text_obj.parent = self.door_labels
|
|||
|
else:
|
|||
|
text_obj.parent = self.labels
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"添加零件标签失败: {e}")
|
|||
|
|
|||
|
def clear_labels(self, label_obj):
|
|||
|
"""清理标签"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE or not label_obj:
|
|||
|
return
|
|||
|
|
|||
|
# 删除所有子对象
|
|||
|
children = label_obj.children[:]
|
|||
|
for child in children:
|
|||
|
self.delete_object_safe(child)
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"清理标签失败: {e}")
|
|||
|
|
|||
|
# ==================== 工具方法 ====================
|
|||
|
|
|||
|
def get_object_center(self, obj):
|
|||
|
"""获取对象中心"""
|
|||
|
try:
|
|||
|
if BLENDER_AVAILABLE and obj and hasattr(obj, 'location'):
|
|||
|
return obj.location
|
|||
|
return (0, 0, 0)
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"获取对象中心失败: {e}")
|
|||
|
return (0, 0, 0)
|
|||
|
|
|||
|
def is_in_door_layer(self, part):
|
|||
|
"""检查是否在门图层"""
|
|||
|
try:
|
|||
|
if not part or not self.door_layer:
|
|||
|
return False
|
|||
|
return part in self.door_layer.objects
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"检查门图层失败: {e}")
|
|||
|
return False
|
|||
|
|
|||
|
def delete_object_safe(self, obj) -> bool:
|
|||
|
"""安全删除对象"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE or not obj:
|
|||
|
return False
|
|||
|
|
|||
|
# 检查对象是否仍然有效
|
|||
|
if obj.name not in bpy.data.objects:
|
|||
|
return True
|
|||
|
|
|||
|
# 从场景中移除
|
|||
|
if obj.name in bpy.context.scene.collection.objects:
|
|||
|
bpy.context.scene.collection.objects.unlink(obj)
|
|||
|
|
|||
|
# 删除对象数据
|
|||
|
if hasattr(obj, 'data') and obj.data:
|
|||
|
if obj.data.name in bpy.data.meshes:
|
|||
|
bpy.data.meshes.remove(obj.data)
|
|||
|
elif obj.data.name in bpy.data.curves:
|
|||
|
bpy.data.curves.remove(obj.data)
|
|||
|
|
|||
|
# 删除对象
|
|||
|
if obj.name in bpy.data.objects:
|
|||
|
bpy.data.objects.remove(obj)
|
|||
|
|
|||
|
return True
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"删除对象失败: {e}")
|
|||
|
return False
|
|||
|
|
|||
|
def transform_vector(self, vector, transform):
|
|||
|
"""变换向量"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE or not transform:
|
|||
|
return vector
|
|||
|
|
|||
|
if isinstance(vector, (list, tuple)) and len(vector) >= 3:
|
|||
|
import mathutils
|
|||
|
vec = mathutils.Vector(vector)
|
|||
|
transformed = transform @ vec
|
|||
|
return (transformed.x, transformed.y, transformed.z)
|
|||
|
|
|||
|
return vector
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"变换向量失败: {e}")
|
|||
|
return vector
|
|||
|
|
|||
|
# ==================== 管理器统计 ====================
|
|||
|
|
|||
|
def get_dimension_stats(self) -> Dict[str, Any]:
|
|||
|
"""获取尺寸标注管理器统计信息"""
|
|||
|
try:
|
|||
|
total_dimensions = sum(len(dims)
|
|||
|
for dims in self.dimensions.values())
|
|||
|
|
|||
|
stats = {
|
|||
|
"manager_type": "DimensionManager",
|
|||
|
"total_dimensions": total_dimensions,
|
|||
|
"units_with_dimensions": len(self.dimensions),
|
|||
|
"has_labels": self.labels is not None,
|
|||
|
"has_door_labels": self.door_labels is not None,
|
|||
|
"blender_available": BLENDER_AVAILABLE
|
|||
|
}
|
|||
|
|
|||
|
return stats
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"获取尺寸标注统计失败: {e}")
|
|||
|
return {"error": str(e)}
|
|||
|
|
|||
|
|
|||
|
# ==================== 模块实例 ====================
|
|||
|
|
|||
|
# 全局实例,将由SUWImpl初始化时设置
|
|||
|
dimension_manager = None
|
|||
|
|
|||
|
|
|||
|
def init_dimension_manager():
|
|||
|
"""初始化尺寸标注管理器 - 不再需要suw_impl参数"""
|
|||
|
global dimension_manager
|
|||
|
dimension_manager = DimensionManager()
|
|||
|
return dimension_manager
|