suwoodblender/blenderpython/suw_core/dimension_manager.py

565 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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