#!/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: logger.warning("Blender 不可用,跳过尺寸标注创建") return 0 uid = data.get("uid") dims = data.get("dims", []) logger.info(f" 开始创建尺寸标注: uid={uid}, 标注数={len(dims)}") # 【修复】直接在主线程中执行,不使用timer try: # 【按照Ruby逻辑】初始化尺寸标注存储 if uid not in self.dimensions: self.dimensions[uid] = [] dimensions = self.dimensions[uid] created_count = 0 # 【修复】添加批量处理,每处理几个对象就进行一次垃圾回收和依赖图更新 batch_size = 2 # 进一步减少批次大小,避免内存压力 current_batch = 0 # 【按照Ruby逻辑】处理每个尺寸标注 for i, dim in enumerate(dims): try: # 解析坐标和方向 p1 = Point3d.parse(dim.get("p1", "(0,0,0)")) p2 = Point3d.parse(dim.get("p2", "(0,0,0)")) d = Vector3d.parse(dim.get("d", "(0,0,1)")) # 方向向量 t = dim.get("t", "") # 文本内容 if not p1 or not p2 or not d: logger.warning( f"无效的尺寸标注数据: p1={p1}, p2={p2}, d={d}") continue # 【按照Ruby逻辑】应用单位变换 if uid in self.unit_trans: trans = self.unit_trans[uid] p1 = self._transform_point(p1, trans) p2 = self._transform_point(p2, trans) d = self._transform_vector(d, trans) # 【修复】使用更安全的创建方法,避免依赖图更新 entity = self._create_linear_dimension_minimal_safe( p1, p2, d, t) if entity: dimensions.append(entity) created_count += 1 current_batch += 1 # 注册到内存管理器 memory_manager.register_object(entity) logger.debug(f"✅ 创建尺寸标注成功: {entity.name}") # 【修复】每处理一批对象就进行清理和更新 if current_batch >= batch_size: try: # 强制垃圾回收 import gc gc.collect() # 【修复】延迟更新依赖图,避免频繁更新 # bpy.context.view_layer.update() # 重置批次计数 current_batch = 0 logger.debug( f"🔧 批次处理完成,已创建 {created_count} 个对象") except Exception as e: logger.warning(f"批次清理失败: {e}") except Exception as e: logger.error(f"创建单个尺寸标注失败: {e}") continue # 【修复】最终清理和更新 try: import gc gc.collect() # 【修复】延迟更新依赖图 # bpy.context.view_layer.update() except Exception as e: logger.warning(f"最终清理失败: {e}") logger.info(f" 尺寸标注创建完成: {created_count}/{len(dims)} 成功") return created_count except Exception as e: logger.error(f"❌ 创建尺寸标注失败: {e}") return 0 except Exception as e: logger.error(f"❌ 添加尺寸标注失败: {e}") return 0 def c0c(self, data: Dict[str, Any]): """delete_dimensions - 删除尺寸标注 - 按照Ruby逻辑""" try: if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过尺寸标注删除") return 0 uid = data.get("uid") logger.info(f" 删除尺寸标注: uid={uid}") deleted_count = 0 # 【按照Ruby逻辑】删除指定单元的尺寸标注 if uid in self.dimensions: dimensions = self.dimensions[uid] for dimension in dimensions: try: if self._delete_dimension_safe(dimension): deleted_count += 1 except Exception as e: logger.error(f"删除单个尺寸标注失败: {e}") continue # 清理记录 del self.dimensions[uid] logger.info(f"✅ 删除尺寸标注完成: {deleted_count} 个") else: logger.info(f"未找到单元 {uid} 的尺寸标注") return deleted_count except Exception as e: logger.error(f"❌ 删除尺寸标注失败: {e}") return 0 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 obj or not BLENDER_AVAILABLE: return True # 如果对象为空或Blender不可用,认为删除成功 # 【修复】更强的对象有效性检查 try: # 检查对象是否仍然存在于Blender数据中 if not hasattr(obj, 'name') or obj.name not in bpy.data.objects: logger.debug( f"对象 {obj.name if hasattr(obj, 'name') else 'unknown'} 已不在Blender数据中") return True # 如果对象已经不在数据中,认为删除成功 # 检查对象是否仍然有效(没有被删除) if not hasattr(obj, 'type') or not obj.type: logger.debug(f"对象已失效") return True # 对象已经失效,认为删除成功 except Exception as e: logger.debug(f"对象有效性检查失败: {e}") return True # 如果检查失败,认为对象已经不存在 # 删除子对象 try: if hasattr(obj, 'children') and obj.children: children_to_delete = list(obj.children) # 创建副本避免修改迭代对象 for child in children_to_delete: try: if child and hasattr(child, 'name') and child.name in bpy.data.objects: # 【修复】递归调用自身 if self.delete_object_safe(child): logger.debug(f"删除子对象成功: {child.name}") else: logger.debug(f"删除子对象失败: {child.name}") except Exception as e: logger.debug(f"删除子对象时出错: {e}") continue except Exception as e: logger.debug(f"处理子对象时出错: {e}") # 删除父对象 try: # 从场景中移除 if obj.name in bpy.context.scene.collection.objects: bpy.context.scene.collection.objects.unlink(obj) logger.debug(f"从场景中移除对象: {obj.name}") # 删除对象数据 if hasattr(obj, 'data') and obj.data: try: if obj.data.name in bpy.data.meshes: bpy.data.meshes.remove(obj.data) logger.debug(f"删除网格数据: {obj.data.name}") elif obj.data.name in bpy.data.curves: bpy.data.curves.remove(obj.data) logger.debug(f"删除曲线数据: {obj.data.name}") except Exception as e: logger.debug(f"删除对象数据时出错: {e}") # 删除对象 if obj.name in bpy.data.objects: bpy.data.objects.remove(obj) logger.debug(f"删除对象: {obj.name}") return True except Exception as e: logger.debug(f"删除父对象时出错: {e}") return True # 如果出错,认为删除成功 except Exception as e: logger.debug(f"删除对象时出错: {e}") return True # 如果出错,认为删除成功 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)} def _create_linear_dimension_minimal_safe(self, p1, p2, direction, text): """创建线性尺寸标注 - 最小化安全版本,只创建基本对象""" try: # 【修复】坐标已经通过Point3d.parse转换为内部单位,不需要再次转换 start_point = (p1.x, p1.y, p1.z) end_point = (p2.x, p2.y, p2.z) # 【调试】打印原始坐标 logger.info( f"🔍 原始坐标: p1=({p1.x*1000:.1f}, {p1.y*1000:.1f}, {p1.z*1000:.1f})mm, p2=({p2.x*1000:.1f}, {p2.y*1000:.1f}, {p2.z*1000:.1f})mm") logger.info( f"🔍 Blender坐标: start=({start_point[0]:.3f}, {start_point[1]:.3f}, {start_point[2]:.3f})m, end=({end_point[0]:.3f}, {end_point[1]:.3f}, {end_point[2]:.3f})m") # 计算标注偏移(垂直于方向向量) offset_distance = 0.05 # 5cm偏移 direction_normalized = self._normalize_vector( direction.x, direction.y, direction.z) # 【替换原有的偏移点计算】 offset_start = ( start_point[0] + direction_normalized[0] * offset_distance, start_point[1] + direction_normalized[1] * offset_distance, start_point[2] + direction_normalized[2] * offset_distance ) offset_end = ( end_point[0] + direction_normalized[0] * offset_distance, end_point[1] + direction_normalized[1] * offset_distance, end_point[2] + direction_normalized[2] * offset_distance ) # 【修复】使用时间戳确保唯一命名 import time timestamp = int(time.time() * 1000) % 100000 unique_id = f"Dimension_Linear_{timestamp}" # 创建标注线网格 mesh = bpy.data.meshes.new(f"Dimension_Mesh_{unique_id}") # 【修复】创建正确的顶点和边 vertices = [ start_point, # 0: 起点 end_point, # 1: 终点 offset_start, # 2: 偏移起点 offset_end # 3: 偏移终点 ] # 创建边:连接线、偏移线、水平线 edges = [ (0, 1), # 主标注线 (0, 2), # 起点到偏移起点的连接线 (1, 3), # 终点到偏移终点的连接线 (2, 3) # 偏移线 ] mesh.from_pydata(vertices, edges, []) mesh.update() # 【修复】创建对象时使用唯一名称 dim_obj = bpy.data.objects.new(unique_id, mesh) bpy.context.scene.collection.objects.link(dim_obj) # 【修复】最小化属性设置,避免触发依赖图更新 # 只设置最基本的属性,其他属性延迟设置 try: # 使用字典方式设置属性,避免触发依赖图更新 dim_obj["sw_typ"] = "dimension" dim_obj["sw_text"] = text dim_obj["sw_aligned"] = True # has_aligned_text = true dim_obj["sw_arrow_type"] = "none" # arrow_type = ARROW_NONE except Exception as e: logger.warning(f"设置标注属性失败: {e}") # 【修复】延迟创建文本标签,避免在批量创建时触发更新 if text and text.strip(): # 【修复】只创建非空文本 try: # 【修复】计算正确的文本位置(偏移线的中点) text_pos = ( (offset_start[0] + offset_end[0]) / 2, (offset_start[1] + offset_end[1]) / 2, (offset_start[2] + offset_end[2]) / 2 ) text_obj = self._create_dimension_text_minimal_safe( text, text_pos, direction_normalized) if text_obj: # 【修复】安全的父对象设置 - 延迟执行 try: text_obj.parent = dim_obj except Exception as e: logger.warning(f"设置文本父对象失败: {e}") # 【修复】安全的属性设置 - 使用字典方式 try: dim_obj["sw_text_obj"] = text_obj.name except Exception as e: logger.warning(f"设置文本对象引用失败: {e}") except Exception as e: logger.error(f"创建文本标签失败: {e}") # 【调试】打印标注信息 logger.info(f"🔍 创建尺寸标注: {dim_obj.name}") logger.info(f" - 起点: {start_point}") logger.info(f" - 终点: {end_point}") logger.info(f" - 偏移起点: {offset_start}") logger.info(f" - 偏移终点: {offset_end}") logger.info(f" - 方向: {direction_normalized}") logger.info(f" - 文本: {text}") return dim_obj except Exception as e: logger.error(f"创建线性尺寸标注失败: {e}") return None def _create_dimension_text_minimal_safe(self, text, location, line_direction): """创建尺寸标注文本 - 最小化安全版本,只创建基本对象""" try: # 【修复】检查是否在主线程中 if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过文本创建") return None # 【修复】使用时间戳确保唯一命名,避免组件ID冲突 import time timestamp = int(time.time() * 1000) % 100000 unique_id = f"Dimension_Text_{timestamp}" # 【修复】使用更安全的方法创建文本,避免依赖active_object # 直接创建文本曲线和对象 font_curve = bpy.data.curves.new( type="FONT", name=f"FontCurve_{unique_id}") font_curve.body = text # 根据场景大小自动计算文本缩放 scene_scale = self._calculate_scene_scale() # 限制在2cm到6cm之间 text_scale = max(0.08, min(0.1, scene_scale * 0.01)) # 设置文本大小 font_curve.size = text_scale font_curve.align_x = 'CENTER' font_curve.align_y = 'CENTER' # 【修复】创建对象时使用唯一名称 text_obj = bpy.data.objects.new(unique_id, font_curve) # 【修复】最小化属性设置,避免触发依赖图更新 try: # 【优化】根据线条方向设置文本位置和旋转 abs_x = abs(line_direction[0]) abs_y = abs(line_direction[1]) abs_z = abs(line_direction[2]) # 确定主要方向并调整位置和旋转 if abs_z > abs_x and abs_z > abs_y: # 主要是Z方向(垂直) adjusted_location = ( location[0], location[1], location[2] + text_scale * 2 # 向上偏移 ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 0) # 水平显示 except Exception as e: logger.warning(f"设置旋转失败: {e}") elif abs_x > abs_y: # 主要是X方向(水平) adjusted_location = ( location[0], location[1] + text_scale * 2, # 向Y轴正方向偏移 location[2] ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 1.5708) # 旋转90度 except Exception as e: logger.warning(f"设置旋转失败: {e}") else: # 主要是Y方向(深度) adjusted_location = ( location[0] + text_scale * 2, # 向X轴正方向偏移 location[1], location[2] ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 0) # 水平显示 except Exception as e: logger.warning(f"设置旋转失败: {e}") # 【修复】安全的位置设置 try: text_obj.location = adjusted_location except Exception as e: logger.warning(f"设置位置失败: {e}") except Exception as e: logger.warning(f"设置文本对象属性失败: {e}") # 【修复】安全的场景链接 try: bpy.context.scene.collection.objects.link(text_obj) except Exception as e: logger.error(f"链接文本对象到场景失败: {e}") # 【修复】清理已创建的对象 try: bpy.data.curves.remove(font_curve) except: pass return None # 【修复】安全的属性设置 - 使用字典方式 try: text_obj["sw_typ"] = "dimension_text" text_obj["sw_aligned"] = True except Exception as e: logger.warning(f"设置文本属性失败: {e}") logger.info(f"🔍 创建安全文本标签: {text_obj.name}") logger.info(f" - 位置: {adjusted_location}") logger.info(f" - 缩放: {text_scale}") logger.info(f" - 线条方向: {line_direction}") logger.info(f" - 文本: {text}") return text_obj except Exception as e: logger.error(f"创建安全尺寸标注文本失败: {e}") return None def _cross_product(self, v1, v2): """计算两个向量的叉积""" try: return ( v1[1] * v2[2] - v1[2] * v2[1], v1[2] * v2[0] - v1[0] * v2[2], v1[0] * v2[1] - v1[1] * v2[0] ) except Exception as e: logger.error(f"计算叉积失败: {e}") return (0, 0, 1) def _create_dimension_text(self, text, location, line_direction): """创建尺寸标注文本 - 修复线程安全问题""" try: # 【修复】检查是否在主线程中 if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过文本创建") return None # 【修复】使用更安全的方法创建文本,避免依赖active_object # 直接创建文本曲线和对象 font_curve = bpy.data.curves.new( type="FONT", name="Dimension_Text") font_curve.body = text # 根据场景大小自动计算文本缩放 scene_scale = self._calculate_scene_scale() # 限制在2cm到6cm之间 text_scale = max(0.02, min(0.06, scene_scale * 0.01)) # 设置文本大小 font_curve.size = text_scale font_curve.align_x = 'CENTER' font_curve.align_y = 'CENTER' # 创建文本对象 text_obj = bpy.data.objects.new("Dimension_Text", font_curve) # 【修复】安全的属性设置 - 添加异常处理 try: # 【优化】根据线条方向设置文本位置和旋转 abs_x = abs(line_direction[0]) abs_y = abs(line_direction[1]) abs_z = abs(line_direction[2]) # 确定主要方向并调整位置和旋转 if abs_z > abs_x and abs_z > abs_y: # 主要是Z方向(垂直) adjusted_location = ( location[0], location[1], location[2] + text_scale * 2 # 向上偏移 ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 0) # 水平显示 except Exception as e: logger.warning(f"设置旋转失败: {e}") elif abs_x > abs_y: # 主要是X方向(水平) adjusted_location = ( location[0], location[1] + text_scale * 2, # 向Y轴正方向偏移 location[2] ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 1.5708) # 旋转90度 except Exception as e: logger.warning(f"设置旋转失败: {e}") else: # 主要是Y方向(深度) adjusted_location = ( location[0] + text_scale * 2, # 向X轴正方向偏移 location[1], location[2] ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 0) # 水平显示 except Exception as e: logger.warning(f"设置旋转失败: {e}") # 【修复】安全的位置设置 try: text_obj.location = adjusted_location except Exception as e: logger.warning(f"设置位置失败: {e}") # 【优化】设置文本对象属性使其更可见 try: text_obj.show_in_front = True # 显示在前面 text_obj.hide_viewport = False # 确保在视口中可见 text_obj.hide_render = False # 确保在渲染中可见 except Exception as e: logger.warning(f"设置显示属性失败: {e}") except Exception as e: logger.warning(f"设置文本对象属性失败: {e}") # 【优化】添加文本材质使其更明显 try: self._add_text_material(text_obj) except Exception as e: logger.warning(f"添加文本材质失败: {e}") # 【修复】安全的场景链接 try: bpy.context.scene.collection.objects.link(text_obj) except Exception as e: logger.error(f"链接文本对象到场景失败: {e}") return None # 【修复】安全的属性设置 try: text_obj["sw_typ"] = "dimension_text" text_obj["sw_aligned"] = True except Exception as e: logger.warning(f"设置文本属性失败: {e}") logger.info(f"🔍 创建安全文本标签: {text_obj.name}") logger.info(f" - 位置: {adjusted_location}") logger.info(f" - 缩放: {text_scale}") logger.info(f" - 线条方向: {line_direction}") logger.info(f" - 文本: {text}") return text_obj except Exception as e: logger.error(f"创建安全尺寸标注文本失败: {e}") return None def _create_dimension_text_fallback(self, text, location, line_direction): """创建尺寸标注文本 - 传统回退方法""" try: # 创建文本曲线 font_curve = bpy.data.curves.new( type="FONT", name="Dimension_Text") font_curve.body = text # 根据场景大小自动计算文本缩放 scene_scale = self._calculate_scene_scale() text_scale = max(0.03, min(0.08, scene_scale * 0.015)) # 设置文本大小 font_curve.size = text_scale font_curve.align_x = 'CENTER' font_curve.align_y = 'CENTER' # 创建文本对象 text_obj = bpy.data.objects.new("Dimension_Text", font_curve) # 根据线条方向计算文本位置和旋转 abs_x = abs(line_direction[0]) abs_y = abs(line_direction[1]) abs_z = abs(line_direction[2]) # 确定主要方向并设置位置 if abs_z > abs_x and abs_z > abs_y: # 主要是Z方向(垂直) adjusted_location = ( location[0], location[1], location[2] + text_scale * 1.5 ) text_obj.rotation_euler = (0, 0, 0) elif abs_x > abs_y: # 主要是X方向(水平) adjusted_location = ( location[0], location[1] + text_scale * 1.5, location[2] ) text_obj.rotation_euler = (0, 0, 1.5708) else: # 主要是Y方向(深度) adjusted_location = ( location[0] + text_scale * 1.5, location[1], location[2] ) text_obj.rotation_euler = (0, 0, 0) text_obj.location = adjusted_location # 设置文本对象属性 text_obj.show_in_front = True text_obj.hide_viewport = False text_obj.hide_render = False # 添加文本材质 self._add_text_material(text_obj) bpy.context.scene.collection.objects.link(text_obj) # 设置文本属性 text_obj["sw_typ"] = "dimension_text" text_obj["sw_aligned"] = True logger.info(f"🔍 创建回退文本标签: {text_obj.name}") logger.info(f" - 位置: {adjusted_location}") logger.info(f" - 缩放: {text_scale}") logger.info(f" - 线条方向: {line_direction}") logger.info(f" - 文本: {text}") return text_obj except Exception as e: logger.error(f"创建回退尺寸标注文本失败: {e}") return None def _add_text_material(self, obj): """为文本添加可见材质""" try: # 创建黑色材质,使文本明显可见 mat_name = "Dimension_Text_Material" if mat_name in bpy.data.materials: material = bpy.data.materials[mat_name] else: material = bpy.data.materials.new(name=mat_name) material.use_nodes = True # 获取材质节点 nodes = material.node_tree.nodes principled_bsdf = nodes.get("Principled BSDF") if principled_bsdf: # 设置为黑色,不透明 principled_bsdf.inputs["Base Color"].default_value = ( 0.0, 0.0, 0.0, 1.0) # 黑色 principled_bsdf.inputs["Metallic"].default_value = 0.0 principled_bsdf.inputs["Roughness"].default_value = 0.8 principled_bsdf.inputs["Alpha"].default_value = 1.0 # 设置材质为不透明 material.blend_method = 'OPAQUE' # 应用材质到对象 if obj.data: if len(obj.data.materials) == 0: obj.data.materials.append(material) else: obj.data.materials[0] = material except Exception as e: logger.error(f"添加文本材质失败: {e}") def _calculate_scene_scale(self): """计算场景缩放比例""" try: # 获取场景中所有对象的位置范围 min_x = min_y = min_z = float('inf') max_x = max_y = max_z = float('-inf') for obj in bpy.context.scene.objects: if obj.type == 'MESH': for vertex in obj.bound_box: min_x = min(min_x, vertex[0]) min_y = min(min_y, vertex[1]) min_z = min(min_z, vertex[2]) max_x = max(max_x, vertex[0]) max_y = max(max_y, vertex[1]) max_z = max(max_z, vertex[2]) # 计算场景大小 scene_size = max(max_x - min_x, max_y - min_y, max_z - min_z) # 根据场景大小返回缩放比例 if scene_size < 0.1: # 小于10cm return 0.8 elif scene_size < 1.0: # 小于1m return 1.0 elif scene_size < 10.0: # 小于10m return 1.5 else: # 大于10m return 2.0 except Exception as e: logger.error(f"计算场景缩放失败: {e}") return 1.0 # 默认缩放 def _add_dimension_material(self, obj): """为尺寸标注添加可见材质""" try: # 创建红色材质,使标注线明显可见 mat_name = "Dimension_Material" if mat_name in bpy.data.materials: material = bpy.data.materials[mat_name] else: material = bpy.data.materials.new(name=mat_name) material.use_nodes = True # 获取材质节点 nodes = material.node_tree.nodes principled_bsdf = nodes.get("Principled BSDF") if principled_bsdf: # 设置为红色,不透明 principled_bsdf.inputs["Base Color"].default_value = ( 1.0, 0.0, 0.0, 1.0) # 红色 principled_bsdf.inputs["Metallic"].default_value = 0.0 principled_bsdf.inputs["Roughness"].default_value = 0.5 principled_bsdf.inputs["Alpha"].default_value = 1.0 # 设置材质为不透明 material.blend_method = 'OPAQUE' # 应用材质到对象 if obj.data: if len(obj.data.materials) == 0: obj.data.materials.append(material) else: obj.data.materials[0] = material except Exception as e: logger.error(f"添加尺寸标注材质失败: {e}") def _transform_point(self, point, transform): """变换点坐标 - 按照Ruby的transform!逻辑""" try: if not transform: return point # 简化的变换实现 # 这里应该根据实际的变换矩阵进行计算 # 暂时返回原始点 return point except Exception as e: logger.error(f"变换点坐标失败: {e}") return point def _transform_vector(self, vector, transform): """变换向量 - 按照Ruby的transform!逻辑""" try: if not transform: return vector # 简化的变换实现 # 这里应该根据实际的变换矩阵进行计算 # 暂时返回原始向量 return vector except Exception as e: logger.error(f"变换向量失败: {e}") return vector def _normalize_vector(self, x, y, z): """归一化向量""" try: length = math.sqrt(x*x + y*y + z*z) if length > 0: return (x/length, y/length, z/length) else: return (0, 0, 1) except Exception as e: logger.error(f"归一化向量失败: {e}") return (0, 0, 1) def _delete_dimension_safe(self, dimension): """安全删除尺寸标注对象 - 修复对象引用问题""" try: if not dimension: return True # 如果对象为空,认为删除成功 # 【修复】更强的对象有效性检查 try: # 检查对象是否仍然存在于Blender数据中 if not hasattr(dimension, 'name') or dimension.name not in bpy.data.objects: logger.debug(f"对象已不在Blender数据中") return True # 对象已经不存在,认为删除成功 # 检查对象是否仍然有效(没有被删除) if not hasattr(dimension, 'type') or not dimension.type: logger.debug(f"对象已失效") return True # 对象已经失效,认为删除成功 except Exception as e: logger.debug(f"对象有效性检查失败: {e}") return True # 如果检查失败,认为对象已经不存在 # 删除关联的文本对象 try: if "sw_text_obj" in dimension: text_obj_name = dimension["sw_text_obj"] if text_obj_name in bpy.data.objects: text_obj = bpy.data.objects[text_obj_name] # 【修复】调用正确的方法名 if self.delete_object_safe(text_obj): logger.debug(f"删除关联文本对象成功: {text_obj_name}") else: logger.debug(f"删除关联文本对象失败: {text_obj_name}") except Exception as e: logger.debug(f"删除关联文本对象时出错: {e}") # 删除主对象 try: # 【修复】调用正确的方法名 if self.delete_object_safe(dimension): logger.debug(f"删除主对象成功: {dimension.name}") return True else: logger.debug(f"删除主对象失败: {dimension.name}") return False except Exception as e: logger.debug(f"删除主对象时出错: {e}") return True # 如果出错,认为删除成功 except Exception as e: logger.debug(f"删除尺寸标注对象时出错: {e}") return True # 如果出错,认为删除成功 def _create_linear_dimension_safe(self, p1, p2, direction, text): """创建线性尺寸标注 - 完全线程安全版本,修复命名冲突,优化性能""" try: # 【修复】坐标已经通过Point3d.parse转换为内部单位,不需要再次转换 start_point = (p1.x, p1.y, p1.z) end_point = (p2.x, p2.y, p2.z) # 【调试】打印原始坐标 logger.info( f"🔍 原始坐标: p1=({p1.x*1000:.1f}, {p1.y*1000:.1f}, {p1.z*1000:.1f})mm, p2=({p2.x*1000:.1f}, {p2.y*1000:.1f}, {p2.z*1000:.1f})mm") logger.info( f"🔍 Blender坐标: start=({start_point[0]:.3f}, {start_point[1]:.3f}, {start_point[2]:.3f})m, end=({end_point[0]:.3f}, {end_point[1]:.3f}, {end_point[2]:.3f})m") # 计算标注偏移(垂直于方向向量) offset_distance = 0.05 # 5cm偏移 direction_normalized = self._normalize_vector( direction.x, direction.y, direction.z) # 【替换原有的偏移点计算】 offset_start = ( start_point[0] + direction_normalized[0] * offset_distance, start_point[1] + direction_normalized[1] * offset_distance, start_point[2] + direction_normalized[2] * offset_distance ) offset_end = ( end_point[0] + direction_normalized[0] * offset_distance, end_point[1] + direction_normalized[1] * offset_distance, end_point[2] + direction_normalized[2] * offset_distance ) # 【修复】使用时间戳确保唯一命名 import time timestamp = int(time.time() * 1000) % 100000 unique_id = f"Dimension_Linear_{timestamp}" # 创建标注线网格 mesh = bpy.data.meshes.new(f"Dimension_Mesh_{unique_id}") # 【修复】创建正确的顶点和边 vertices = [ start_point, # 0: 起点 end_point, # 1: 终点 offset_start, # 2: 偏移起点 offset_end # 3: 偏移终点 ] # 创建边:连接线、偏移线、水平线 edges = [ (0, 1), # 主标注线 (0, 2), # 起点到偏移起点的连接线 (1, 3), # 终点到偏移终点的连接线 (2, 3) # 偏移线 ] mesh.from_pydata(vertices, edges, []) mesh.update() # 【修复】创建对象时使用唯一名称 dim_obj = bpy.data.objects.new(unique_id, mesh) bpy.context.scene.collection.objects.link(dim_obj) # 【修复】设置对象属性使其可见 dim_obj.show_in_front = True # 显示在前面 dim_obj.hide_viewport = False # 确保在视口中可见 dim_obj.hide_render = False # 确保在渲染中可见 # 【修复】添加材质使标注线可见 self._add_dimension_material(dim_obj) # 【按照Ruby逻辑】设置标注属性 - 添加错误处理 try: dim_obj["sw_typ"] = "dimension" dim_obj["sw_text"] = text dim_obj["sw_aligned"] = True # has_aligned_text = true dim_obj["sw_arrow_type"] = "none" # arrow_type = ARROW_NONE except Exception as e: logger.warning(f"设置标注属性失败: {e}") # 创建文本标签 - 添加更强的错误处理 if text and text.strip(): # 【修复】只创建非空文本 try: # 【修复】计算正确的文本位置(偏移线的中点) text_pos = ( (offset_start[0] + offset_end[0]) / 2, (offset_start[1] + offset_end[1]) / 2, (offset_start[2] + offset_end[2]) / 2 ) text_obj = self._create_dimension_text_safe( text, text_pos, direction_normalized) if text_obj: # 【修复】安全的父对象设置 try: text_obj.parent = dim_obj except Exception as e: logger.warning(f"设置文本父对象失败: {e}") # 【修复】安全的属性设置 try: dim_obj["sw_text_obj"] = text_obj.name except Exception as e: logger.warning(f"设置文本对象引用失败: {e}") except Exception as e: logger.error(f"创建文本标签失败: {e}") # 【调试】打印标注信息 logger.info(f"🔍 创建尺寸标注: {dim_obj.name}") logger.info(f" - 起点: {start_point}") logger.info(f" - 终点: {end_point}") logger.info(f" - 偏移起点: {offset_start}") logger.info(f" - 偏移终点: {offset_end}") logger.info(f" - 方向: {direction_normalized}") logger.info(f" - 文本: {text}") return dim_obj except Exception as e: logger.error(f"创建线性尺寸标注失败: {e}") return None def _create_dimension_text_safe(self, text, location, line_direction): """创建尺寸标注文本 - 完全线程安全版本,修复命名冲突,优化性能""" try: # 【修复】检查是否在主线程中 if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过文本创建") return None # 【修复】使用时间戳确保唯一命名,避免组件ID冲突 import time timestamp = int(time.time() * 1000) % 100000 unique_id = f"Dimension_Text_{timestamp}" # 【修复】使用更安全的方法创建文本,避免依赖active_object # 直接创建文本曲线和对象 font_curve = bpy.data.curves.new( type="FONT", name=f"FontCurve_{unique_id}") font_curve.body = text # 根据场景大小自动计算文本缩放 scene_scale = self._calculate_scene_scale() # 限制在2cm到6cm之间 text_scale = max(0.08, min(0.1, scene_scale * 0.01)) # 设置文本大小 font_curve.size = text_scale font_curve.align_x = 'CENTER' font_curve.align_y = 'CENTER' # 【修复】创建对象时使用唯一名称 text_obj = bpy.data.objects.new(unique_id, font_curve) # 【修复】安全的属性设置 - 添加异常处理 try: # 【优化】根据线条方向设置文本位置和旋转 abs_x = abs(line_direction[0]) abs_y = abs(line_direction[1]) abs_z = abs(line_direction[2]) # 确定主要方向并调整位置和旋转 if abs_z > abs_x and abs_z > abs_y: # 主要是Z方向(垂直) adjusted_location = ( location[0], location[1], location[2] + text_scale * 2 # 向上偏移 ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 0) # 水平显示 except Exception as e: logger.warning(f"设置旋转失败: {e}") elif abs_x > abs_y: # 主要是X方向(水平) adjusted_location = ( location[0], location[1] + text_scale * 2, # 向Y轴正方向偏移 location[2] ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 1.5708) # 旋转90度 except Exception as e: logger.warning(f"设置旋转失败: {e}") else: # 主要是Y方向(深度) adjusted_location = ( location[0] + text_scale * 2, # 向X轴正方向偏移 location[1], location[2] ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 0) # 水平显示 except Exception as e: logger.warning(f"设置旋转失败: {e}") # 【修复】安全的位置设置 try: text_obj.location = adjusted_location except Exception as e: logger.warning(f"设置位置失败: {e}") # 【优化】设置文本对象属性使其更可见 try: text_obj.show_in_front = True # 显示在前面 text_obj.hide_viewport = False # 确保在视口中可见 text_obj.hide_render = False # 确保在渲染中可见 except Exception as e: logger.warning(f"设置显示属性失败: {e}") except Exception as e: logger.warning(f"设置文本对象属性失败: {e}") # 【修复】安全的场景链接 try: bpy.context.scene.collection.objects.link(text_obj) except Exception as e: logger.error(f"链接文本对象到场景失败: {e}") # 【修复】清理已创建的对象 try: bpy.data.curves.remove(font_curve) except: pass return None # 【修复】安全的属性设置 try: text_obj["sw_typ"] = "dimension_text" text_obj["sw_aligned"] = True except Exception as e: logger.warning(f"设置文本属性失败: {e}") # 【修复】移除强制更新,改为在批次处理时统一更新 # try: # text_obj.update_tag() # bpy.context.view_layer.update() # except: # pass logger.info(f"🔍 创建安全文本标签: {text_obj.name}") logger.info(f" - 位置: {adjusted_location}") logger.info(f" - 缩放: {text_scale}") logger.info(f" - 线条方向: {line_direction}") logger.info(f" - 文本: {text}") return text_obj except Exception as e: logger.error(f"创建安全尺寸标注文本失败: {e}") return None def _create_linear_dimension_ultra_safe(self, p1, p2, direction, text): """创建线性尺寸标注 - 超安全版本,避免所有依赖图更新""" try: # 【修复】坐标已经通过Point3d.parse转换为内部单位,不需要再次转换 start_point = (p1.x, p1.y, p1.z) end_point = (p2.x, p2.y, p2.z) # 【调试】打印原始坐标 logger.info( f"🔍 原始坐标: p1=({p1.x*1000:.1f}, {p1.y*1000:.1f}, {p1.z*1000:.1f})mm, p2=({p2.x*1000:.1f}, {p2.y*1000:.1f}, {p2.z*1000:.1f})mm") logger.info( f"🔍 Blender坐标: start=({start_point[0]:.3f}, {start_point[1]:.3f}, {start_point[2]:.3f})m, end=({end_point[0]:.3f}, {end_point[1]:.3f}, {end_point[2]:.3f})m") # 计算标注偏移(垂直于方向向量) offset_distance = 0.05 # 5cm偏移 direction_normalized = self._normalize_vector( direction.x, direction.y, direction.z) # 【替换原有的偏移点计算】 offset_start = ( start_point[0] + direction_normalized[0] * offset_distance, start_point[1] + direction_normalized[1] * offset_distance, start_point[2] + direction_normalized[2] * offset_distance ) offset_end = ( end_point[0] + direction_normalized[0] * offset_distance, end_point[1] + direction_normalized[1] * offset_distance, end_point[2] + direction_normalized[2] * offset_distance ) # 【修复】使用时间戳确保唯一命名 import time timestamp = int(time.time() * 1000) % 100000 unique_id = f"Dimension_Linear_{timestamp}" # 创建标注线网格 mesh = bpy.data.meshes.new(f"Dimension_Mesh_{unique_id}") # 【修复】创建正确的顶点和边 vertices = [ start_point, # 0: 起点 end_point, # 1: 终点 offset_start, # 2: 偏移起点 offset_end # 3: 偏移终点 ] # 创建边:连接线、偏移线、水平线 edges = [ (0, 1), # 主标注线 (0, 2), # 起点到偏移起点的连接线 (1, 3), # 终点到偏移终点的连接线 (2, 3) # 偏移线 ] mesh.from_pydata(vertices, edges, []) mesh.update() # 【修复】创建对象时使用唯一名称 dim_obj = bpy.data.objects.new(unique_id, mesh) bpy.context.scene.collection.objects.link(dim_obj) # 【修复】设置对象属性使其可见 - 避免触发依赖图更新 try: dim_obj.show_in_front = True # 显示在前面 dim_obj.hide_viewport = False # 确保在视口中可见 dim_obj.hide_render = False # 确保在渲染中可见 except Exception as e: logger.warning(f"设置显示属性失败: {e}") # 【移除】不再添加材质 # self._add_dimension_material_safe(dim_obj) # 【修复】设置标注属性 - 使用更安全的方法 try: # 使用字典方式设置属性,避免触发依赖图更新 dim_obj["sw_typ"] = "dimension" dim_obj["sw_text"] = text dim_obj["sw_aligned"] = True # has_aligned_text = true dim_obj["sw_arrow_type"] = "none" # arrow_type = ARROW_NONE except Exception as e: logger.warning(f"设置标注属性失败: {e}") # 创建文本标签 - 添加更强的错误处理 if text and text.strip(): # 【修复】只创建非空文本 try: # 【修复】计算正确的文本位置(偏移线的中点) text_pos = ( (offset_start[0] + offset_end[0]) / 2, (offset_start[1] + offset_end[1]) / 2, (offset_start[2] + offset_end[2]) / 2 ) text_obj = self._create_dimension_text_ultra_safe( text, text_pos, direction_normalized) if text_obj: # 【修复】安全的父对象设置 - 延迟执行 try: text_obj.parent = dim_obj except Exception as e: logger.warning(f"设置文本父对象失败: {e}") # 【修复】安全的属性设置 - 使用字典方式 try: dim_obj["sw_text_obj"] = text_obj.name except Exception as e: logger.warning(f"设置文本对象引用失败: {e}") except Exception as e: logger.error(f"创建文本标签失败: {e}") # 【调试】打印标注信息 logger.info(f"🔍 创建尺寸标注: {dim_obj.name}") logger.info(f" - 起点: {start_point}") logger.info(f" - 终点: {end_point}") logger.info(f" - 偏移起点: {offset_start}") logger.info(f" - 偏移终点: {offset_end}") logger.info(f" - 方向: {direction_normalized}") logger.info(f" - 文本: {text}") return dim_obj except Exception as e: logger.error(f"创建线性尺寸标注失败: {e}") return None def _create_dimension_text_ultra_safe(self, text, location, line_direction): """创建尺寸标注文本 - 超安全版本,避免所有依赖图更新""" try: # 【修复】检查是否在主线程中 if not BLENDER_AVAILABLE: logger.warning("Blender 不可用,跳过文本创建") return None # 【修复】使用时间戳确保唯一命名,避免组件ID冲突 import time timestamp = int(time.time() * 1000) % 100000 unique_id = f"Dimension_Text_{timestamp}" # 【修复】使用更安全的方法创建文本,避免依赖active_object # 直接创建文本曲线和对象 font_curve = bpy.data.curves.new( type="FONT", name=f"FontCurve_{unique_id}") font_curve.body = text # 根据场景大小自动计算文本缩放 scene_scale = self._calculate_scene_scale() # 限制在2cm到6cm之间 text_scale = max(0.08, min(0.1, scene_scale * 0.01)) # 设置文本大小 font_curve.size = text_scale font_curve.align_x = 'CENTER' font_curve.align_y = 'CENTER' # 【修复】创建对象时使用唯一名称 text_obj = bpy.data.objects.new(unique_id, font_curve) # 【修复】安全的属性设置 - 添加异常处理 try: # 【优化】根据线条方向设置文本位置和旋转 abs_x = abs(line_direction[0]) abs_y = abs(line_direction[1]) abs_z = abs(line_direction[2]) # 确定主要方向并调整位置和旋转 if abs_z > abs_x and abs_z > abs_y: # 主要是Z方向(垂直) adjusted_location = ( location[0], location[1], location[2] + text_scale * 2 # 向上偏移 ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 0) # 水平显示 except Exception as e: logger.warning(f"设置旋转失败: {e}") elif abs_x > abs_y: # 主要是X方向(水平) adjusted_location = ( location[0], location[1] + text_scale * 2, # 向Y轴正方向偏移 location[2] ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 1.5708) # 旋转90度 except Exception as e: logger.warning(f"设置旋转失败: {e}") else: # 主要是Y方向(深度) adjusted_location = ( location[0] + text_scale * 2, # 向X轴正方向偏移 location[1], location[2] ) # 【修复】安全的旋转设置 try: text_obj.rotation_euler = (0, 0, 0) # 水平显示 except Exception as e: logger.warning(f"设置旋转失败: {e}") # 【修复】安全的位置设置 try: text_obj.location = adjusted_location except Exception as e: logger.warning(f"设置位置失败: {e}") # 【优化】设置文本对象属性使其更可见 try: text_obj.show_in_front = True # 显示在前面 text_obj.hide_viewport = False # 确保在视口中可见 text_obj.hide_render = False # 确保在渲染中可见 except Exception as e: logger.warning(f"设置显示属性失败: {e}") except Exception as e: logger.warning(f"设置文本对象属性失败: {e}") # 【移除】不再添加文本材质 # self._add_text_material_safe(text_obj) # 【修复】安全的场景链接 try: bpy.context.scene.collection.objects.link(text_obj) except Exception as e: logger.error(f"链接文本对象到场景失败: {e}") # 【修复】清理已创建的对象 try: bpy.data.curves.remove(font_curve) except: pass return None # 【修复】安全的属性设置 - 使用字典方式 try: text_obj["sw_typ"] = "dimension_text" text_obj["sw_aligned"] = True except Exception as e: logger.warning(f"设置文本属性失败: {e}") logger.info(f"🔍 创建安全文本标签: {text_obj.name}") logger.info(f" - 位置: {adjusted_location}") logger.info(f" - 缩放: {text_scale}") logger.info(f" - 线条方向: {line_direction}") logger.info(f" - 文本: {text}") return text_obj except Exception as e: logger.error(f"创建安全尺寸标注文本失败: {e}") return None # ==================== 模块实例 ==================== # 全局实例,将由SUWImpl初始化时设置 dimension_manager = None def init_dimension_manager(): """初始化尺寸标注管理器 - 不再需要suw_impl参数""" global dimension_manager dimension_manager = DimensionManager() return dimension_manager def get_dimension_manager(): """获取尺寸标注管理器实例""" global dimension_manager if dimension_manager is None: dimension_manager = init_dimension_manager() return dimension_manager