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