blenderpython/suw_core/dimension_manager.py

1827 lines
73 KiB
Python
Raw Permalink 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:
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