2155 lines
76 KiB
Python
2155 lines
76 KiB
Python
|
||
# 清理选择状态
|
||
if self.__class__._selected_uid == uid:
|
||
self.__class__._selected_uid = None
|
||
self.__class__._selected_obj = None
|
||
self.__class__._selected_zone = None
|
||
self.__class__._selected_part = None
|
||
|
||
logger.debug(f"单元数据清理完成: {uid}")
|
||
|
||
except Exception as e:
|
||
logger.warning(f"清理单元数据失败: {e}")
|
||
|
||
def _clear_labels_safe(self):
|
||
"""安全清理标签"""
|
||
try:
|
||
# 查找并删除标签对象
|
||
labels_to_delete = []
|
||
for obj in list(bpy.data.objects):
|
||
try:
|
||
if not self._is_object_valid(obj):
|
||
continue
|
||
|
||
if (obj.name.startswith("Label_") or
|
||
obj.name.startswith("Dimension_") or
|
||
obj.get("sw_typ") in ["label", "dimension"]):
|
||
labels_to_delete.append(obj)
|
||
|
||
except Exception:
|
||
continue
|
||
|
||
# 删除标签
|
||
for label_obj in labels_to_delete:
|
||
self._delete_object_safe(label_obj)
|
||
|
||
if labels_to_delete:
|
||
logger.debug(f"清理标签完成: {len(labels_to_delete)} 个")
|
||
|
||
except Exception as e:
|
||
logger.error(f"清理标签失败: {e}")
|
||
|
||
def c0a(self, data: Dict[str, Any]):
|
||
"""del_machining - 删除加工 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
def delete_machining():
|
||
try:
|
||
uid = data.get("uid")
|
||
typ = data.get("typ")
|
||
oid = data.get("oid")
|
||
special = data.get("special", 1)
|
||
|
||
if uid not in self.machinings:
|
||
return True
|
||
|
||
machinings = self.machinings[uid]
|
||
valid_machinings = []
|
||
|
||
for machining in machinings:
|
||
should_delete = False
|
||
|
||
if machining and hasattr(machining, 'name') and machining.name in bpy.data.objects:
|
||
if typ == "uid":
|
||
should_delete = True
|
||
else:
|
||
attr_value = machining.get(f"sw_{typ}")
|
||
should_delete = (attr_value == oid)
|
||
|
||
if should_delete and special == 0:
|
||
machining_special = machining.get(
|
||
"sw_special", 0)
|
||
should_delete = (machining_special == 0)
|
||
|
||
if should_delete:
|
||
try:
|
||
bpy.data.objects.remove(
|
||
machining, do_unlink=True)
|
||
logger.debug(f"已删除加工: {machining.name}")
|
||
except Exception as e:
|
||
logger.warning(f"删除加工失败: {e}")
|
||
else:
|
||
valid_machinings.append(machining)
|
||
|
||
self.machinings[uid] = valid_machinings
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"删除加工失败: {e}")
|
||
return False
|
||
|
||
# 在主线程中执行删除操作
|
||
success = execute_in_main_thread(delete_machining)
|
||
|
||
if success:
|
||
logger.info(f"✅ 加工删除完成")
|
||
else:
|
||
logger.error(f"❌ 加工删除失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 删除加工失败: {e}")
|
||
|
||
def c0c(self, data: Dict[str, Any]):
|
||
"""del_dim - 删除尺寸标注 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
def delete_dimensions():
|
||
try:
|
||
uid = data.get("uid")
|
||
|
||
if uid in self.dimensions:
|
||
dimensions = self.dimensions[uid]
|
||
for dim in dimensions:
|
||
try:
|
||
if dim and hasattr(dim, 'name') and dim.name in bpy.data.objects:
|
||
bpy.data.objects.remove(
|
||
dim, do_unlink=True)
|
||
logger.debug(f"已删除尺寸标注: {dim.name}")
|
||
except Exception as e:
|
||
logger.warning(f"删除尺寸标注失败: {e}")
|
||
|
||
del self.dimensions[uid]
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"删除尺寸标注失败: {e}")
|
||
return False
|
||
|
||
# 在主线程中执行删除操作
|
||
success = execute_in_main_thread(delete_dimensions)
|
||
|
||
if success:
|
||
logger.info(f"✅ 尺寸标注删除完成")
|
||
else:
|
||
logger.error(f"❌ 尺寸标注删除失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 删除尺寸标注失败: {e}")
|
||
|
||
# ==================== 视图管理方法 ====================
|
||
|
||
def c15(self, data: Dict[str, Any]):
|
||
"""sel_unit - 选择单元"""
|
||
try:
|
||
self.sel_clear()
|
||
zones = self.get_zones(data)
|
||
|
||
for zid, zone in zones.items():
|
||
if zone and zone.name in bpy.data.objects:
|
||
leaf = self._is_leaf_zone(zid, zones)
|
||
zone.hide_viewport = leaf and self.hide_none
|
||
|
||
except Exception as e:
|
||
logger.error(f"选择单元失败: {e}")
|
||
|
||
def _is_leaf_zone(self, zip_id, zones):
|
||
"""检查是否是叶子区域"""
|
||
try:
|
||
for zid, zone in zones.items():
|
||
if zone and zone.get("sw_zip") == zip_id:
|
||
return False
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"检查叶子区域失败: {e}")
|
||
return True
|
||
|
||
def c16(self, data: Dict[str, Any]):
|
||
"""sel_zone - 选择区域"""
|
||
try:
|
||
self._sel_zone_local(data)
|
||
except Exception as e:
|
||
logger.error(f"选择区域失败: {e}")
|
||
|
||
def c17(self, data: Dict[str, Any]):
|
||
"""sel_elem - 选择元素"""
|
||
try:
|
||
if self.part_mode:
|
||
self._sel_part_parent(data)
|
||
else:
|
||
self._sel_zone_local(data)
|
||
except Exception as e:
|
||
logger.error(f"选择元素失败: {e}")
|
||
|
||
def _sel_part_parent(self, data):
|
||
"""选择零件父级"""
|
||
try:
|
||
self.sel_clear()
|
||
zones = self.get_zones(data)
|
||
parts = self.get_parts(data)
|
||
hardwares = self.get_hardwares(data)
|
||
|
||
uid = data.get("uid")
|
||
zid = data.get("zid")
|
||
pid = data.get("pid")
|
||
|
||
parted = False
|
||
|
||
# 选择零件
|
||
for v_root, part in parts.items():
|
||
if part and part.get("sw_pid") == pid:
|
||
self.textured_part(part, True)
|
||
self.__class__._selected_uid = uid
|
||
self.__class__._selected_obj = pid
|
||
parted = True
|
||
|
||
# 选择硬件
|
||
for v_root, hw in hardwares.items():
|
||
if hw and hw.get("sw_pid") == pid:
|
||
self._textured_hw(hw, True)
|
||
|
||
# 处理区域
|
||
children = self._get_child_zones(zones, zid, True)
|
||
for child in children:
|
||
child_id = child.get("zid")
|
||
child_zone = zones.get(child_id)
|
||
leaf = child.get("leaf")
|
||
|
||
if not child_zone:
|
||
continue
|
||
|
||
if leaf and child_id == zid:
|
||
if not self.hide_none:
|
||
child_zone.hide_viewport = False
|
||
for face in child_zone.children:
|
||
if hasattr(face, 'data'):
|
||
selected = face.get("sw_child") == pid
|
||
self._textured_face(face, selected)
|
||
if selected:
|
||
self.__class__._selected_uid = uid
|
||
self.__class__._selected_obj = pid
|
||
elif not leaf and child_id == zid:
|
||
if not parted:
|
||
child_zone.hide_viewport = False
|
||
for face in child_zone.children:
|
||
if (hasattr(face, 'data') and
|
||
face.get("sw_child") == pid):
|
||
self._textured_face(face, True)
|
||
self.__class__._selected_uid = uid
|
||
self.__class__._selected_obj = pid
|
||
elif leaf and not self.hide_none:
|
||
child_zone.hide_viewport = False
|
||
for face in child_zone.children:
|
||
if hasattr(face, 'data'):
|
||
self._textured_face(face, False)
|
||
else:
|
||
child_zone.hide_viewport = True
|
||
|
||
except Exception as e:
|
||
logger.error(f"选择零件父级失败: {e}")
|
||
|
||
# ==================== 门和抽屉功能方法 ====================
|
||
|
||
def c10(self, data: Dict[str, Any]):
|
||
"""set_doorinfo - 设置门信息"""
|
||
try:
|
||
parts = self.get_parts(data)
|
||
doors = data.get("drs", [])
|
||
|
||
for door in doors:
|
||
root = door.get("cp", 0)
|
||
door_dir = door.get("dov", "")
|
||
ps = Point3d.parse(door.get("ps", "(0,0,0)"))
|
||
pe = Point3d.parse(door.get("pe", "(0,0,0)"))
|
||
offset = Vector3d.parse(door.get("off", "(0,0,0)"))
|
||
|
||
if root > 0 and root in parts:
|
||
part = parts[root]
|
||
part["sw_door_dir"] = door_dir
|
||
part["sw_door_ps"] = (ps.x, ps.y, ps.z)
|
||
part["sw_door_pe"] = (pe.x, pe.y, pe.z)
|
||
part["sw_door_offset"] = (offset.x, offset.y, offset.z)
|
||
|
||
except Exception as e:
|
||
logger.error(f"设置门信息失败: {e}")
|
||
|
||
def c1a(self, data: Dict[str, Any]):
|
||
"""open_doors - 开门"""
|
||
try:
|
||
uid = data.get("uid")
|
||
parts = self.get_parts(data)
|
||
hardwares = self.get_hardwares(data)
|
||
mydoor = data.get("cp", 0)
|
||
value = data.get("v", False)
|
||
|
||
for root, part in parts.items():
|
||
if mydoor != 0 and mydoor != root:
|
||
continue
|
||
|
||
door_type = part.get("sw_door", 0)
|
||
if door_type <= 0:
|
||
continue
|
||
|
||
is_open = part.get("sw_door_open", False)
|
||
if is_open == value:
|
||
continue
|
||
|
||
if door_type not in [10, 15]:
|
||
continue
|
||
|
||
# 获取门的参数
|
||
door_ps = part.get("sw_door_ps")
|
||
door_pe = part.get("sw_door_pe")
|
||
door_off = part.get("sw_door_offset")
|
||
|
||
if not all([door_ps, door_pe, door_off]):
|
||
continue
|
||
|
||
# 应用单位变换
|
||
if uid in self.unit_trans:
|
||
trans = self.unit_trans[uid]
|
||
door_ps = self._transform_point(door_ps, trans)
|
||
door_pe = self._transform_point(door_pe, trans)
|
||
door_off = self._transform_vector(door_off, trans)
|
||
|
||
# 计算变换
|
||
if door_type == 10: # 平开门
|
||
trans_a = self._calculate_swing_door_transform(
|
||
door_ps, door_pe, door_off)
|
||
else: # 推拉门
|
||
trans_a = self._calculate_slide_door_transform(door_off)
|
||
|
||
if is_open:
|
||
trans_a = self._invert_transform(trans_a)
|
||
|
||
# 应用变换
|
||
self._apply_transformation(part, trans_a)
|
||
part["sw_door_open"] = not is_open
|
||
|
||
# 变换相关硬件
|
||
for key, hardware in hardwares.items():
|
||
if hardware.get("sw_part") == root:
|
||
self._apply_transformation(hardware, trans_a)
|
||
|
||
except Exception as e:
|
||
logger.error(f"开门失败: {e}")
|
||
|
||
def c1b(self, data: Dict[str, Any]):
|
||
"""slide_drawers - 滑动抽屉"""
|
||
try:
|
||
uid = data.get("uid")
|
||
zones = self.get_zones(data)
|
||
parts = self.get_parts(data)
|
||
hardwares = self.get_hardwares(data)
|
||
|
||
# 收集抽屉信息
|
||
drawers = {}
|
||
depths = {}
|
||
|
||
for root, part in parts.items():
|
||
drawer_type = part.get("sw_drawer", 0)
|
||
if drawer_type > 0:
|
||
if drawer_type == 70: # DR_DP
|
||
pid = part.get("sw_pid")
|
||
drawer_dir = part.get("sw_drawer_dir")
|
||
if pid and drawer_dir:
|
||
drawers[pid] = Vector3d(
|
||
drawer_dir[0], drawer_dir[1], drawer_dir[2])
|
||
|
||
if drawer_type in [73, 74]: # DR_LP/DR_RP
|
||
pid = part.get("sw_pid")
|
||
dr_depth = part.get("sw_dr_depth", 300)
|
||
if pid:
|
||
depths[pid] = dr_depth
|
||
|
||
# 计算偏移量
|
||
offsets = {}
|
||
for drawer, direction in drawers.items():
|
||
zone = zones.get(drawer)
|
||
if not zone:
|
||
continue
|
||
|
||
dr_depth = depths.get(drawer, 300) * 0.001 # mm to meters
|
||
vector = Vector3d(direction.x, direction.y, direction.z)
|
||
vector_length = math.sqrt(
|
||
vector.x**2 + vector.y**2 + vector.z**2)
|
||
if vector_length > 0:
|
||
scale = (dr_depth * 0.9) / vector_length
|
||
vector = Vector3d(vector.x * scale,
|
||
vector.y * scale, vector.z * scale)
|
||
|
||
# 应用单位变换
|
||
if uid in self.unit_trans:
|
||
vector = self._transform_vector(
|
||
(vector.x, vector.y, vector.z), self.unit_trans[uid])
|
||
|
||
offsets[drawer] = vector
|
||
|
||
# 应用抽屉变换
|
||
value = data.get("v", False)
|
||
for drawer, vector in offsets.items():
|
||
zone = zones.get(drawer)
|
||
if not zone:
|
||
continue
|
||
|
||
is_open = zone.get("sw_drawer_open", False)
|
||
if is_open == value:
|
||
continue
|
||
|
||
# 计算变换
|
||
trans_a = self._calculate_translation_transform(vector)
|
||
if is_open:
|
||
trans_a = self._invert_transform(trans_a)
|
||
|
||
# 应用到区域
|
||
zone["sw_drawer_open"] = not is_open
|
||
|
||
# 变换零件
|
||
for root, part in parts.items():
|
||
if part.get("sw_pid") == drawer:
|
||
self._apply_transformation(part, trans_a)
|
||
|
||
# 变换硬件
|
||
for root, hardware in hardwares.items():
|
||
if hardware.get("sw_pid") == drawer:
|
||
self._apply_transformation(hardware, trans_a)
|
||
|
||
except Exception as e:
|
||
logger.error(f"滑动抽屉失败: {e}")
|
||
|
||
# ==================== 视图控制方法 ====================
|
||
|
||
def c18(self, data: Dict[str, Any]):
|
||
"""hide_door - 隐藏门"""
|
||
try:
|
||
visible = not data.get("v", False)
|
||
|
||
if self.door_layer:
|
||
# 在Blender中控制集合可见性
|
||
self.door_layer.hide_viewport = not visible
|
||
|
||
if self.door_labels:
|
||
self.door_labels.hide_viewport = not visible
|
||
|
||
except Exception as e:
|
||
logger.error(f"隐藏门失败: {e}")
|
||
|
||
def c28(self, data: Dict[str, Any]):
|
||
"""hide_drawer - 隐藏抽屉"""
|
||
try:
|
||
visible = not data.get("v", False)
|
||
|
||
if self.drawer_layer:
|
||
self.drawer_layer.hide_viewport = not visible
|
||
|
||
if self.door_labels:
|
||
self.door_labels.hide_viewport = not visible
|
||
|
||
except Exception as e:
|
||
logger.error(f"隐藏抽屉失败: {e}")
|
||
|
||
def c0f(self, data: Dict[str, Any]):
|
||
"""view_front - 前视图"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
# 设置前视图
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == 'VIEW_3D':
|
||
for region in area.regions:
|
||
if region.type == 'WINDOW':
|
||
override = {'area': area, 'region': region}
|
||
bpy.ops.view3d.view_axis(
|
||
override, type='FRONT')
|
||
bpy.ops.view3d.view_all(override)
|
||
break
|
||
except Exception as e:
|
||
logger.error(f"前视图失败: {e}")
|
||
|
||
def c23(self, data: Dict[str, Any]):
|
||
"""view_left - 左视图"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == 'VIEW_3D':
|
||
for region in area.regions:
|
||
if region.type == 'WINDOW':
|
||
override = {'area': area, 'region': region}
|
||
bpy.ops.view3d.view_axis(override, type='LEFT')
|
||
bpy.ops.view3d.view_all(override)
|
||
break
|
||
except Exception as e:
|
||
logger.error(f"左视图失败: {e}")
|
||
|
||
def c24(self, data: Dict[str, Any]):
|
||
"""view_right - 右视图"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == 'VIEW_3D':
|
||
for region in area.regions:
|
||
if region.type == 'WINDOW':
|
||
override = {'area': area, 'region': region}
|
||
bpy.ops.view3d.view_axis(
|
||
override, type='RIGHT')
|
||
bpy.ops.view3d.view_all(override)
|
||
break
|
||
except Exception as e:
|
||
logger.error(f"右视图失败: {e}")
|
||
|
||
def c25(self, data: Dict[str, Any]):
|
||
"""view_back - 后视图"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
for area in bpy.context.screen.areas:
|
||
if area.type == 'VIEW_3D':
|
||
for region in area.regions:
|
||
if region.type == 'WINDOW':
|
||
override = {'area': area, 'region': region}
|
||
bpy.ops.view3d.view_axis(override, type='BACK')
|
||
bpy.ops.view3d.view_all(override)
|
||
break
|
||
except Exception as e:
|
||
logger.error(f"后视图失败: {e}")
|
||
|
||
# ==================== 其他业务方法 ====================
|
||
|
||
def c00(self, data: Dict[str, Any]):
|
||
"""add_folder - 添加文件夹"""
|
||
try:
|
||
ref_v = data.get("ref_v", 0)
|
||
if ref_v > 0:
|
||
# 在实际应用中需要实现文件夹管理逻辑
|
||
logger.info(f"添加文件夹: ref_v={ref_v}")
|
||
except Exception as e:
|
||
logger.error(f"添加文件夹失败: {e}")
|
||
|
||
def c01(self, data: Dict[str, Any]):
|
||
"""edit_unit - 编辑单元"""
|
||
try:
|
||
unit_id = data.get("unit_id")
|
||
|
||
if "params" in data:
|
||
params = data["params"]
|
||
|
||
if "trans" in params:
|
||
jtran = params.pop("trans")
|
||
trans = Transformation.parse(jtran)
|
||
self.unit_trans[unit_id] = trans
|
||
time.sleep(0.5) # 等待
|
||
|
||
if unit_id in self.unit_param:
|
||
values = self.unit_param[unit_id]
|
||
values.update(params)
|
||
params = values
|
||
|
||
self.unit_param[unit_id] = params
|
||
|
||
except Exception as e:
|
||
logger.error(f"编辑单元失败: {e}")
|
||
|
||
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 = execute_in_main_thread(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 c0d(self, data: Dict[str, Any]):
|
||
"""parts_seqs - 零件序列"""
|
||
try:
|
||
parts = self.get_parts(data)
|
||
seqs = data.get("seqs", [])
|
||
|
||
for d in seqs:
|
||
root = d.get("cp")
|
||
seq = d.get("seq")
|
||
pos = d.get("pos")
|
||
name = d.get("name")
|
||
size = d.get("size")
|
||
mat = d.get("mat")
|
||
|
||
e_part = parts.get(root)
|
||
if e_part:
|
||
e_part["sw_seq"] = seq
|
||
e_part["sw_pos"] = pos
|
||
if name:
|
||
e_part["sw_name"] = name
|
||
if size:
|
||
e_part["sw_size"] = size
|
||
if mat:
|
||
e_part["sw_mat"] = mat
|
||
|
||
except Exception as e:
|
||
logger.error(f"零件序列失败: {e}")
|
||
|
||
def c0e(self, data: Dict[str, Any]):
|
||
"""explode_zones - 爆炸视图"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
# 清理标签
|
||
self._clear_labels(self.labels)
|
||
self._clear_labels(self.door_labels)
|
||
|
||
uid = data.get("uid")
|
||
zones = self.get_zones(data)
|
||
parts = self.get_parts(data)
|
||
hardwares = self.get_hardwares(data)
|
||
|
||
# 处理区域爆炸
|
||
jzones = data.get("zones", [])
|
||
for zone in jzones:
|
||
zoneid = zone.get("zid")
|
||
offset = Vector3d.parse(zone.get("vec", "(0,0,0)"))
|
||
|
||
if uid in self.unit_trans:
|
||
offset = self._transform_vector(
|
||
(offset.x, offset.y, offset.z), self.unit_trans[uid])
|
||
|
||
trans_a = self._calculate_translation_transform(offset)
|
||
|
||
if zoneid in zones:
|
||
azone = zones[zoneid]
|
||
self._apply_transformation(azone, trans_a)
|
||
|
||
# 处理零件爆炸
|
||
jparts = data.get("parts", [])
|
||
for jpart in jparts:
|
||
pid = jpart.get("pid")
|
||
offset = Vector3d.parse(jpart.get("vec", "(0,0,0)"))
|
||
|
||
if uid in self.unit_trans:
|
||
offset = self._transform_vector(
|
||
(offset.x, offset.y, offset.z), self.unit_trans[uid])
|
||
|
||
trans_a = self._calculate_translation_transform(offset)
|
||
|
||
# 变换零件
|
||
for root, part in parts.items():
|
||
if part.get("sw_pid") == pid:
|
||
self._apply_transformation(part, trans_a)
|
||
|
||
# 变换硬件
|
||
for root, hardware in hardwares.items():
|
||
if hardware.get("sw_pid") == pid:
|
||
self._apply_transformation(hardware, trans_a)
|
||
|
||
# 添加序号标签
|
||
if data.get("explode", False):
|
||
self._add_part_labels(uid, parts)
|
||
|
||
except Exception as e:
|
||
logger.error(f"爆炸视图失败: {e}")
|
||
|
||
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 c12(self, data: Dict[str, Any]):
|
||
"""add_contour - 添加轮廓 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
def create_contour():
|
||
try:
|
||
self.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 = execute_in_main_thread(create_contour)
|
||
|
||
if success:
|
||
logger.info(f"✅ 成功创建轮廓")
|
||
else:
|
||
logger.error(f"❌ 轮廓创建失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 添加轮廓失败: {e}")
|
||
|
||
def add_surf(self, data: Dict[str, Any]):
|
||
"""add_surf - 添加表面"""
|
||
try:
|
||
surf = data.get("surf", {})
|
||
self.create_face(bpy.context.scene, surf)
|
||
except Exception as e:
|
||
logger.error(f"添加表面失败: {e}")
|
||
|
||
def c13(self, data: Dict[str, Any]):
|
||
"""save_pixmap - 保存像素图 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
def save_pixmap():
|
||
try:
|
||
uid = data.get("uid")
|
||
file_path = data.get("file")
|
||
|
||
# 设置渲染参数
|
||
bpy.context.scene.render.filepath = file_path
|
||
bpy.context.scene.render.image_settings.file_format = 'PNG'
|
||
|
||
# 渲染当前视图
|
||
bpy.ops.render.render(write_still=True)
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"保存像素图失败: {e}")
|
||
return False
|
||
|
||
# 在主线程中执行渲染操作
|
||
success = execute_in_main_thread(save_pixmap)
|
||
|
||
if success:
|
||
logger.info(f"✅ 成功保存像素图")
|
||
else:
|
||
logger.error(f"❌ 像素图保存失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 保存像素图失败: {e}")
|
||
|
||
def c14(self, data: Dict[str, Any]):
|
||
"""pre_save_pixmap - 预保存像素图 - 线程安全版本"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
def pre_save_pixmap():
|
||
try:
|
||
self.sel_clear()
|
||
|
||
# 设置视图
|
||
if hasattr(bpy.context, 'space_data') and bpy.context.space_data:
|
||
bpy.context.space_data.show_gizmo = False
|
||
bpy.context.space_data.show_overlays = False
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"预保存像素图失败: {e}")
|
||
return False
|
||
|
||
# 在主线程中执行预处理操作
|
||
success = execute_in_main_thread(pre_save_pixmap)
|
||
|
||
if success:
|
||
logger.info(f"✅ 预保存像素图完成")
|
||
else:
|
||
logger.error(f"❌ 预保存像素图失败")
|
||
|
||
except Exception as e:
|
||
logger.error(f"❌ 预保存像素图失败: {e}")
|
||
|
||
def show_message(self, data: Dict[str, Any]):
|
||
"""显示消息"""
|
||
try:
|
||
message = data.get("message", "")
|
||
logger.info(f"显示消息: {message}")
|
||
|
||
# 在Blender中显示消息
|
||
if BLENDER_AVAILABLE:
|
||
# 可以使用报告系统
|
||
pass
|
||
|
||
except Exception as e:
|
||
logger.error(f"显示消息失败: {e}")
|
||
|
||
# ==================== 辅助方法 ====================
|
||
|
||
def _set_cmd(self, cmd, params):
|
||
"""设置命令(发送到客户端)"""
|
||
try:
|
||
# 在实际应用中需要实现客户端通信逻辑
|
||
logger.info(f"发送命令: {cmd}, 参数: {params}")
|
||
except Exception as e:
|
||
logger.error(f"设置命令失败: {e}")
|
||
|
||
def _clear_labels(self, label_obj):
|
||
"""清理标签"""
|
||
try:
|
||
if label_obj and BLENDER_AVAILABLE:
|
||
for child in label_obj.children:
|
||
bpy.data.objects.remove(child, do_unlink=True)
|
||
except Exception as e:
|
||
logger.error(f"清理标签失败: {e}")
|
||
|
||
# ==================== 属性访问器 ====================
|
||
|
||
@classmethod
|
||
def selected_uid(cls):
|
||
return cls._selected_uid
|
||
|
||
@classmethod
|
||
def selected_zone(cls):
|
||
return cls._selected_zone
|
||
|
||
@classmethod
|
||
def selected_part(cls):
|
||
return cls._selected_part
|
||
|
||
@classmethod
|
||
def selected_obj(cls):
|
||
return cls._selected_obj
|
||
|
||
@classmethod
|
||
def server_path(cls):
|
||
return cls._server_path
|
||
|
||
@classmethod
|
||
def default_zone(cls):
|
||
return cls._default_zone
|
||
|
||
# ==================== 清理和销毁 ====================
|
||
|
||
def shutdown(self):
|
||
"""关闭系统"""
|
||
try:
|
||
logger.info("开始关闭SUWood系统")
|
||
|
||
# 执行最终清理
|
||
self.force_cleanup()
|
||
|
||
# 清理所有缓存
|
||
self.mesh_cache.clear()
|
||
self.material_cache.clear()
|
||
self.object_references.clear()
|
||
|
||
# 清理数据结构
|
||
self.parts.clear()
|
||
self.zones.clear()
|
||
self.hardwares.clear()
|
||
self.machinings.clear()
|
||
self.dimensions.clear()
|
||
self.textures.clear()
|
||
self.unit_param.clear()
|
||
self.unit_trans.clear()
|
||
|
||
logger.info("✅ SUWood系统关闭完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"关闭系统失败: {e}")
|
||
|
||
def __del__(self):
|
||
"""析构函数"""
|
||
try:
|
||
self.shutdown()
|
||
except:
|
||
pass
|
||
|
||
# ==================== 内存管理方法(保持原有的优化) ====================
|
||
|
||
def force_cleanup(self):
|
||
"""强制清理"""
|
||
try:
|
||
logger.info("开始强制清理")
|
||
|
||
# 清理孤立数据
|
||
cleanup_count = memory_manager.cleanup_orphaned_data()
|
||
|
||
# 清理缓存
|
||
self.mesh_cache.clear()
|
||
|
||
# 清理过期的对象引用
|
||
current_time = time.time()
|
||
expired_refs = []
|
||
for obj_name, ref_info in self.object_references.items():
|
||
if current_time - ref_info.get('creation_time', 0) > 3600: # 1小时
|
||
expired_refs.append(obj_name)
|
||
|
||
for obj_name in expired_refs:
|
||
del self.object_references[obj_name]
|
||
|
||
# 强制垃圾回收
|
||
gc.collect()
|
||
|
||
# 更新依赖图
|
||
if BLENDER_AVAILABLE:
|
||
self._update_dependency_graph(full_update=True)
|
||
|
||
logger.info(
|
||
f"强制清理完成: 清理了 {cleanup_count} 个数据块,{len(expired_refs)} 个过期引用")
|
||
|
||
except Exception as e:
|
||
logger.error(f"强制清理失败: {e}")
|
||
|
||
def _update_dependency_graph(self, full_update: bool = False):
|
||
"""更新Blender依赖图"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
if full_update:
|
||
logger.info("执行全局依赖图更新")
|
||
bpy.context.view_layer.update()
|
||
bpy.context.evaluated_depsgraph_get().update()
|
||
|
||
# 刷新视图
|
||
try:
|
||
for area in bpy.context.screen.areas:
|
||
if area.type in ['VIEW_3D', 'OUTLINER']:
|
||
area.tag_redraw()
|
||
except:
|
||
pass
|
||
|
||
logger.info("全局依赖图更新完成")
|
||
else:
|
||
# 快速更新
|
||
bpy.context.view_layer.update()
|
||
|
||
except Exception as e:
|
||
logger.error(f"依赖图更新失败: {e}")
|
||
|
||
def get_creation_stats(self) -> Dict[str, Any]:
|
||
"""获取创建统计信息"""
|
||
try:
|
||
stats = {
|
||
"object_references": len(self.object_references),
|
||
"mesh_cache_size": len(self.mesh_cache),
|
||
"material_cache_size": len(self.material_cache),
|
||
"memory_manager_stats": memory_manager.creation_stats.copy(),
|
||
"blender_available": BLENDER_AVAILABLE
|
||
}
|
||
|
||
if BLENDER_AVAILABLE:
|
||
stats["total_objects"] = len(bpy.data.objects)
|
||
stats["total_meshes"] = len(bpy.data.meshes)
|
||
stats["total_materials"] = len(bpy.data.materials)
|
||
|
||
return stats
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取统计信息失败: {e}")
|
||
return {"error": str(e)}
|
||
|
||
def diagnose_system_state(self):
|
||
"""诊断系统状态"""
|
||
try:
|
||
logger.info("=== 系统状态诊断 ===")
|
||
|
||
# 内存使用情况
|
||
stats = self.get_creation_stats()
|
||
for key, value in stats.items():
|
||
logger.info(f"{key}: {value}")
|
||
|
||
# 检查潜在问题
|
||
issues = []
|
||
|
||
if BLENDER_AVAILABLE:
|
||
# 检查孤立数据
|
||
orphaned_meshes = [m for m in bpy.data.meshes if m.users == 0]
|
||
if orphaned_meshes:
|
||
issues.append(f"发现 {len(orphaned_meshes)} 个孤立网格")
|
||
|
||
# 检查空网格
|
||
empty_meshes = [m for m in bpy.data.meshes if not m.vertices]
|
||
if empty_meshes:
|
||
issues.append(f"发现 {len(empty_meshes)} 个空网格")
|
||
|
||
# 检查总顶点数
|
||
total_vertices = sum(len(m.vertices) for m in bpy.data.meshes)
|
||
if total_vertices > 1000000: # 100万顶点
|
||
issues.append(f"顶点数量过多: {total_vertices}")
|
||
|
||
if issues:
|
||
logger.warning("发现问题:")
|
||
for issue in issues:
|
||
logger.warning(f" - {issue}")
|
||
else:
|
||
logger.info("✅ 系统状态正常")
|
||
|
||
return issues
|
||
|
||
except Exception as e:
|
||
logger.error(f"系统诊断失败: {e}")
|
||
return [f"诊断失败: {e}"]
|
||
|
||
def get_memory_report(self) -> Dict[str, Any]:
|
||
"""获取内存报告"""
|
||
try:
|
||
report = {
|
||
"timestamp": time.time(),
|
||
"creation_stats": self.get_creation_stats(),
|
||
"system_diagnosis": self.diagnose_system_state(),
|
||
"memory_manager": {
|
||
"object_registry_size": len(memory_manager.object_registry),
|
||
"mesh_registry_size": len(memory_manager.mesh_registry),
|
||
"last_cleanup": memory_manager.last_cleanup_time,
|
||
"cleanup_interval": memory_manager.cleanup_interval
|
||
}
|
||
}
|
||
|
||
if BLENDER_AVAILABLE:
|
||
report["blender_data"] = {
|
||
"objects": len(bpy.data.objects),
|
||
"meshes": len(bpy.data.meshes),
|
||
"materials": len(bpy.data.materials),
|
||
"textures": len(bpy.data.textures),
|
||
"images": len(bpy.data.images)
|
||
}
|
||
|
||
return report
|
||
|
||
except Exception as e:
|
||
logger.error(f"生成内存报告失败: {e}")
|
||
return {"error": str(e)}
|
||
|
||
# ==================== 几何变换辅助方法 ====================
|
||
|
||
def _transform_point(self, point, trans):
|
||
"""变换点"""
|
||
try:
|
||
if isinstance(point, (list, tuple)) and len(point) >= 3:
|
||
# 简化的变换实现
|
||
return (
|
||
point[0] + trans.origin.x,
|
||
point[1] + trans.origin.y,
|
||
point[2] + trans.origin.z
|
||
)
|
||
return point
|
||
except Exception as e:
|
||
logger.error(f"变换点失败: {e}")
|
||
return point
|
||
|
||
def _transform_vector(self, vector, trans):
|
||
"""变换向量"""
|
||
try:
|
||
if isinstance(vector, (list, tuple)) and len(vector) >= 3:
|
||
# 简化的变换实现
|
||
return (
|
||
vector[0] * trans.x_axis.x,
|
||
vector[1] * trans.y_axis.y,
|
||
vector[2] * trans.z_axis.z
|
||
)
|
||
return vector
|
||
except Exception as e:
|
||
logger.error(f"变换向量失败: {e}")
|
||
return vector
|
||
|
||
def _calculate_swing_door_transform(self, door_ps, door_pe, door_off):
|
||
"""计算平开门变换"""
|
||
try:
|
||
# 计算旋转轴和角度
|
||
axis = (door_pe[0] - door_ps[0], door_pe[1] -
|
||
door_ps[1], door_pe[2] - door_ps[2])
|
||
angle = math.pi / 2 # 90度
|
||
|
||
# 在Blender中创建变换矩阵
|
||
if BLENDER_AVAILABLE:
|
||
import mathutils
|
||
rot_matrix = mathutils.Matrix.Rotation(angle, 4, axis)
|
||
trans_matrix = mathutils.Matrix.Translation(door_off)
|
||
return trans_matrix @ rot_matrix
|
||
|
||
return None
|
||
except Exception as e:
|
||
logger.error(f"计算平开门变换失败: {e}")
|
||
return None
|
||
|
||
def _calculate_slide_door_transform(self, door_off):
|
||
"""计算推拉门变换"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
import mathutils
|
||
return mathutils.Matrix.Translation(door_off)
|
||
return None
|
||
except Exception as e:
|
||
logger.error(f"计算推拉门变换失败: {e}")
|
||
return None
|
||
|
||
def _calculate_translation_transform(self, vector):
|
||
"""计算平移变换"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
import mathutils
|
||
if isinstance(vector, (list, tuple)):
|
||
return mathutils.Matrix.Translation(vector)
|
||
else:
|
||
return mathutils.Matrix.Translation((vector.x, vector.y, vector.z))
|
||
return None
|
||
except Exception as e:
|
||
logger.error(f"计算平移变换失败: {e}")
|
||
return None
|
||
|
||
def _invert_transform(self, transform):
|
||
"""反转变换"""
|
||
try:
|
||
if transform and hasattr(transform, 'inverted'):
|
||
return transform.inverted()
|
||
return transform
|
||
except Exception as e:
|
||
logger.error(f"反转变换失败: {e}")
|
||
return transform
|
||
|
||
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)
|
||
return (0, 0, 1)
|
||
except Exception as e:
|
||
logger.error(f"归一化向量失败: {e}")
|
||
return (0, 0, 1)
|
||
|
||
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 _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[0] * 0.1,
|
||
location[1] + direction[1] * 0.1,
|
||
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
|
||
|
||
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(bpy.context.scene,
|
||
(s.x, s.y, s.z),
|
||
(e.x, e.y, e.z))
|
||
if edge:
|
||
edges.append(edge)
|
||
|
||
# 尝试创建面
|
||
try:
|
||
if edges:
|
||
self._create_face_from_edges(bpy.context.scene, edges)
|
||
except Exception as e:
|
||
logger.warning(f"创建轮廓面失败: {e}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建轮廓失败: {e}")
|
||
|
||
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 + radius * math.cos(angle1)
|
||
y1 = center.y + radius * math.sin(angle1)
|
||
z1 = center.z
|
||
|
||
x2 = center.x + radius * math.cos(angle2)
|
||
y2 = center.y + radius * math.sin(angle2)
|
||
z2 = center.z
|
||
|
||
edge = self._create_line_edge_simple(bpy.context.scene,
|
||
(x1, y1, z1),
|
||
(x2, y2, z2))
|
||
if edge:
|
||
edges.append(edge)
|
||
|
||
return edges
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建弧形边失败: {e}")
|
||
return []
|
||
|
||
def _create_dimension(self, p1, p2, direction, text):
|
||
"""创建尺寸标注"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
# 在Blender中创建尺寸标注的简化实现
|
||
# 创建文本对象显示尺寸
|
||
midpoint = (
|
||
(p1[0] + p2[0]) / 2 + direction[0] * 0.1,
|
||
(p1[1] + p2[1]) / 2 + direction[1] * 0.1,
|
||
(p1[2] + p2[2]) / 2 + direction[2] * 0.1
|
||
)
|
||
|
||
dimension_obj = self._create_text_label(text, midpoint, direction)
|
||
|
||
# 创建尺寸线
|
||
if dimension_obj:
|
||
# 添加线条表示尺寸
|
||
line_mesh = bpy.data.meshes.new("DimensionLine")
|
||
vertices = [p1, p2]
|
||
edges = [(0, 1)]
|
||
|
||
line_mesh.from_pydata(vertices, edges, [])
|
||
line_mesh.update()
|
||
|
||
line_obj = bpy.data.objects.new("DimensionLine", line_mesh)
|
||
line_obj.parent = dimension_obj
|
||
bpy.context.scene.collection.objects.link(line_obj)
|
||
|
||
memory_manager.register_mesh(line_mesh)
|
||
memory_manager.register_object(line_obj)
|
||
|
||
return dimension_obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建尺寸标注失败: {e}")
|
||
return None
|
||
|
||
# ==================== 几何体创建的辅助方法(补充) ====================
|
||
|
||
def _create_triangle_face(self, container, tri, offset_vec, base_point):
|
||
"""创建三角形面"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
# 计算三角形的三个顶点
|
||
p1 = (tri.x, tri.y, tri.z)
|
||
p2 = (tri.x + offset_vec.x, tri.y +
|
||
offset_vec.y, tri.z + offset_vec.z)
|
||
p3 = (base_point.x + (base_point.x - tri.x),
|
||
base_point.y + (base_point.y - tri.y),
|
||
base_point.z + (base_point.z - tri.z))
|
||
|
||
# 创建网格
|
||
mesh = bpy.data.meshes.new("Triangle_Face")
|
||
vertices = [p1, p2, p3]
|
||
faces = [(0, 1, 2)]
|
||
|
||
mesh.from_pydata(vertices, [], faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
obj = bpy.data.objects.new("Triangle_Face_Obj", mesh)
|
||
obj.parent = container
|
||
bpy.context.scene.collection.objects.link(obj)
|
||
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(obj)
|
||
|
||
return obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建三角形面失败: {e}")
|
||
return None
|
||
|
||
def _create_circle_face(self, container, center, normal, radius):
|
||
"""创建圆形面"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
return None
|
||
|
||
# 创建圆形网格
|
||
mesh = bpy.data.meshes.new("Circle_Face")
|
||
|
||
# 生成圆形顶点
|
||
segments = 32
|
||
vertices = [(center.x, center.y, center.z)] # 中心点
|
||
|
||
for i in range(segments):
|
||
angle = (i / segments) * 2 * math.pi
|
||
x = center.x + radius * math.cos(angle)
|
||
y = center.y + radius * math.sin(angle)
|
||
z = center.z
|
||
vertices.append((x, y, z))
|
||
|
||
# 创建面
|
||
faces = []
|
||
for i in range(segments):
|
||
next_i = (i + 1) % segments
|
||
faces.append((0, i + 1, next_i + 1))
|
||
|
||
mesh.from_pydata(vertices, [], faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
obj = bpy.data.objects.new("Circle_Face_Obj", mesh)
|
||
obj.parent = container
|
||
bpy.context.scene.collection.objects.link(obj)
|
||
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(obj)
|
||
|
||
return obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建圆形面失败: {e}")
|
||
return None
|
||
|
||
def _apply_material_to_face(self, face, material):
|
||
"""为面应用材质"""
|
||
try:
|
||
if not face or not material or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
if hasattr(face, 'data') and face.data:
|
||
if not face.data.materials:
|
||
face.data.materials.append(material)
|
||
else:
|
||
face.data.materials[0] = material
|
||
|
||
except Exception as e:
|
||
logger.error(f"为面应用材质失败: {e}")
|
||
|
||
def _follow_me_face(self, face, path):
|
||
"""面跟随路径"""
|
||
try:
|
||
if not face or not path or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
# 在Blender中实现跟随路径
|
||
# 这里使用简化的实现
|
||
if hasattr(face, 'modifiers'):
|
||
# 添加阵列修改器或其他相关修改器
|
||
pass
|
||
|
||
except Exception as e:
|
||
logger.error(f"面跟随路径失败: {e}")
|
||
|
||
def _cleanup_path(self, path):
|
||
"""清理路径"""
|
||
try:
|
||
if path and BLENDER_AVAILABLE and path.name in bpy.data.objects:
|
||
bpy.data.objects.remove(path, do_unlink=True)
|
||
except Exception as e:
|
||
logger.error(f"清理路径失败: {e}")
|
||
|
||
def _cleanup_trimmer(self, trimmer):
|
||
"""清理修剪器"""
|
||
try:
|
||
if trimmer and BLENDER_AVAILABLE and trimmer.name in bpy.data.objects:
|
||
bpy.data.objects.remove(trimmer, do_unlink=True)
|
||
except Exception as e:
|
||
logger.error(f"清理修剪器失败: {e}")
|
||
|
||
def _trim_object(self, trimmer, target):
|
||
"""修剪对象"""
|
||
try:
|
||
if not trimmer or not target or not BLENDER_AVAILABLE:
|
||
return target
|
||
|
||
# 在Blender中实现布尔运算
|
||
# 这里使用简化的实现
|
||
return target
|
||
|
||
except Exception as e:
|
||
logger.error(f"修剪对象失败: {e}")
|
||
return target
|
||
|
||
def _mark_differ_faces(self, obj):
|
||
"""标记差异面"""
|
||
try:
|
||
if not obj or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
texture = self.get_texture("mat_default")
|
||
if not texture:
|
||
return
|
||
|
||
# 标记所有使用默认材质的面为差异面
|
||
for child in obj.children:
|
||
if hasattr(child, 'data') and child.data:
|
||
if (child.data.materials and
|
||
child.data.materials[0] == texture):
|
||
child["sw_differ"] = True
|
||
|
||
except Exception as e:
|
||
logger.error(f"标记差异面失败: {e}")
|
||
|
||
# ==================== 几何验证辅助方法 ====================
|
||
|
||
def _should_reverse_face(self, face, zaxis, reverse_face):
|
||
"""检查是否应该反转面"""
|
||
try:
|
||
if not face or not zaxis:
|
||
return False
|
||
|
||
# 简化的实现
|
||
return reverse_face
|
||
|
||
except Exception as e:
|
||
logger.error(f"检查面反转失败: {e}")
|
||
return False
|
||
|
||
def _face_normal_matches(self, face, zaxis):
|
||
"""检查面法向量是否匹配"""
|
||
try:
|
||
if not face or not zaxis:
|
||
return False
|
||
|
||
# 简化的实现
|
||
return True
|
||
|
||
except Exception as e:
|
||
logger.error(f"检查面法向量失败: {e}")
|
||
return False
|
||
|
||
def _reverse_face(self, face):
|
||
"""反转面"""
|
||
try:
|
||
if not face or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
if hasattr(face, 'data') and face.data:
|
||
# 在Blender中反转面的法向量
|
||
bpy.context.view_layer.objects.active = face
|
||
bpy.ops.object.mode_set(mode='EDIT')
|
||
bpy.ops.mesh.select_all(action='SELECT')
|
||
bpy.ops.mesh.flip_normals()
|
||
bpy.ops.object.mode_set(mode='OBJECT')
|
||
|
||
except Exception as e:
|
||
logger.error(f"反转面失败: {e}")
|
||
|
||
def _get_face_normal(self, face):
|
||
"""获取面法向量"""
|
||
try:
|
||
if not face or not BLENDER_AVAILABLE:
|
||
return (0, 0, 1)
|
||
|
||
if hasattr(face, 'data') and face.data and face.data.polygons:
|
||
# 获取第一个多边形的法向量
|
||
return face.data.polygons[0].normal
|
||
|
||
return (0, 0, 1)
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取面法向量失败: {e}")
|
||
return (0, 0, 1)
|
||
|
||
def _apply_follow_me(self, face, path):
|
||
"""应用跟随路径"""
|
||
try:
|
||
if not face or not path or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
# 在Blender中实现跟随路径的简化版本
|
||
# 这里需要根据实际需求实现具体的几何操作
|
||
pass
|
||
|
||
except Exception as e:
|
||
logger.error(f"应用跟随路径失败: {e}")
|
||
|
||
def _hide_edges(self, container):
|
||
"""隐藏边"""
|
||
try:
|
||
if not container or not BLENDER_AVAILABLE:
|
||
return
|
||
|
||
for child in container.children:
|
||
if hasattr(child, 'data') and child.data and hasattr(child.data, 'edges'):
|
||
for edge in child.data.edges:
|
||
edge.use_edge_sharp = True
|
||
|
||
except Exception as e:
|
||
logger.error(f"隐藏边失败: {e}")
|
||
|
||
def _create_face_fast(self, container, surface, material):
|
||
"""创建面 - 快速版本"""
|
||
try:
|
||
# 获取分段数据
|
||
segs = surface.get("segs", [])
|
||
if not segs:
|
||
return None
|
||
|
||
# 快速解析顶点
|
||
vertices = []
|
||
for seg in segs:
|
||
if len(seg) >= 2:
|
||
coord_str = seg[0].strip('()')
|
||
try:
|
||
x, y, z = map(float, coord_str.split(','))
|
||
vertices.append((x * 0.001, y * 0.001, z * 0.001))
|
||
except:
|
||
continue
|
||
|
||
if len(vertices) < 3:
|
||
return None
|
||
|
||
# 创建简单网格
|
||
mesh = bpy.data.meshes.new(f"FastFace_{int(time.time())}")
|
||
|
||
# 创建面(只支持三角形和四边形)
|
||
if len(vertices) == 4:
|
||
faces = [(0, 1, 2, 3)]
|
||
elif len(vertices) == 3:
|
||
faces = [(0, 1, 2)]
|
||
else:
|
||
# 复杂多边形简化为第一个三角形
|
||
faces = [(0, 1, 2)]
|
||
vertices = vertices[:3]
|
||
|
||
mesh.from_pydata(vertices, [], faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
face_obj = bpy.data.objects.new(f"Face_{container.name}", mesh)
|
||
face_obj.parent = container
|
||
bpy.context.scene.collection.objects.link(face_obj)
|
||
|
||
# 应用材质
|
||
if material:
|
||
face_obj.data.materials.append(material)
|
||
|
||
# 确保可见
|
||
face_obj.hide_viewport = False
|
||
|
||
# 注册到内存管理器
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(face_obj)
|
||
|
||
return face_obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"快速创建面失败: {e}")
|
||
return None
|
||
|
||
def _create_board_six_faces_fast(self, leaf, data, color, scale, angle, color2, scale2, angle2):
|
||
"""快速创建板件六个面"""
|
||
try:
|
||
# 获取正反面数据
|
||
obv = data.get("obv") # 正面
|
||
rev = data.get("rev") # 反面
|
||
|
||
if not obv or not rev:
|
||
logger.warning("缺少正反面数据")
|
||
return
|
||
|
||
# 处理材质
|
||
antiz = data.get("antiz", False)
|
||
|
||
# 根据antiz决定材质分配
|
||
if antiz:
|
||
# 交换正反面材质
|
||
obv_color = color2 if color2 else color
|
||
rev_color = color
|
||
else:
|
||
# 正常材质分配
|
||
obv_color = color
|
||
rev_color = color2 if color2 else color
|
||
|
||
# 获取材质
|
||
material_obv = self.get_texture(obv_color) if obv_color else None
|
||
material_rev = self.get_texture(rev_color) if rev_color else None
|
||
edge_material = material_obv # 边面使用正面材质
|
||
|
||
# 1. 创建正面 (obverse)
|
||
obv_face = self._create_face_fast(leaf, obv, material_obv)
|
||
if obv_face:
|
||
obv_face["sw_face_type"] = "obverse"
|
||
obv_face["sw_face_id"] = "front"
|
||
obv_face["sw_ckey"] = obv_color
|
||
logger.debug("正面创建成功")
|
||
|
||
# 2. 创建反面 (reverse)
|
||
rev_face = self._create_face_fast(leaf, rev, material_rev)
|
||
if rev_face:
|
||
rev_face["sw_face_type"] = "reverse"
|
||
rev_face["sw_face_id"] = "back"
|
||
rev_face["sw_ckey"] = rev_color
|
||
logger.debug("反面创建成功")
|
||
|
||
# 3. 创建四个边面
|
||
self._create_board_edge_faces_fast(leaf, obv, rev, edge_material)
|
||
|
||
logger.debug("板件六面创建完成")
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建板件六面失败: {e}")
|
||
|
||
def _create_board_edge_faces_fast(self, leaf, obv, rev, edge_material):
|
||
"""快速创建板件的四个边面"""
|
||
try:
|
||
# 解析正面和反面的顶点
|
||
obv_vertices = self._parse_surface_vertices(obv)
|
||
rev_vertices = self._parse_surface_vertices(rev)
|
||
|
||
if len(obv_vertices) != len(rev_vertices) or len(obv_vertices) < 3:
|
||
logger.warning("正反面顶点数量不匹配或不足")
|
||
return
|
||
|
||
# 创建四个边面
|
||
vertex_count = len(obv_vertices)
|
||
edge_count = 0
|
||
|
||
for i in range(vertex_count):
|
||
next_i = (i + 1) % vertex_count
|
||
|
||
# 边面的四个顶点:正面两个点 + 反面对应两个点
|
||
edge_vertices = [
|
||
obv_vertices[i], # 正面当前点
|
||
obv_vertices[next_i], # 正面下一点
|
||
rev_vertices[next_i], # 反面下一点
|
||
rev_vertices[i] # 反面当前点
|
||
]
|
||
|
||
# 创建边面
|
||
edge_face = self._create_face_from_vertices_fast(
|
||
leaf, edge_vertices, edge_material, f"edge_{i}")
|
||
|
||
if edge_face:
|
||
edge_face["sw_face_type"] = "edge"
|
||
edge_face["sw_face_id"] = f"edge_{i}"
|
||
edge_face["sw_edge_index"] = i
|
||
edge_count += 1
|
||
|
||
logger.debug(f"创建了 {edge_count}/{vertex_count} 个边面")
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建边面失败: {e}")
|
||
|
||
def _parse_surface_vertices(self, surface):
|
||
"""解析表面顶点坐标"""
|
||
try:
|
||
vertices = []
|
||
segs = surface.get("segs", [])
|
||
|
||
for seg in segs:
|
||
if len(seg) >= 2:
|
||
coord_str = seg[0].strip('()')
|
||
try:
|
||
x, y, z = map(float, coord_str.split(','))
|
||
# 转换为米(Blender使用米作为单位)
|
||
vertices.append((x * 0.001, y * 0.001, z * 0.001))
|
||
except ValueError:
|
||
continue
|
||
|
||
return vertices
|
||
|
||
except Exception as e:
|
||
logger.error(f"解析表面顶点失败: {e}")
|
||
return []
|
||
|
||
def _create_face_from_vertices_fast(self, container, vertices, material, face_name):
|
||
"""从顶点快速创建面"""
|
||
try:
|
||
if len(vertices) < 3:
|
||
return None
|
||
|
||
# 创建网格
|
||
mesh = bpy.data.meshes.new(f"Face_{face_name}_{int(time.time())}")
|
||
|
||
# 创建面
|
||
if len(vertices) == 4:
|
||
faces = [(0, 1, 2, 3)]
|
||
elif len(vertices) == 3:
|
||
faces = [(0, 1, 2)]
|
||
else:
|
||
# 复杂多边形创建扇形三角形
|
||
faces = []
|
||
for i in range(1, len(vertices) - 1):
|
||
faces.append((0, i, i + 1))
|
||
|
||
mesh.from_pydata(vertices, [], faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
face_obj = bpy.data.objects.new(f"Face_{face_name}", mesh)
|
||
face_obj.parent = container
|
||
bpy.context.scene.collection.objects.link(face_obj)
|
||
|
||
# 应用材质
|
||
if material:
|
||
face_obj.data.materials.append(material)
|
||
|
||
# 确保可见
|
||
face_obj.hide_viewport = False
|
||
|
||
# 注册到内存管理器
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(face_obj)
|
||
|
||
return face_obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"从顶点创建面失败: {e}")
|
||
return None
|
||
|
||
def _add_part_board_fast(self, part, data):
|
||
"""创建板材部件 - 保持六面逻辑的快速版本"""
|
||
try:
|
||
# 创建叶子组
|
||
leaf = bpy.data.objects.new(
|
||
f"Board_{part.name}_{int(time.time())}", None)
|
||
leaf.parent = part
|
||
bpy.context.scene.collection.objects.link(leaf)
|
||
|
||
# 获取材质信息
|
||
color = data.get("ckey", "mat_default")
|
||
scale = data.get("scale")
|
||
angle = data.get("angle")
|
||
color2 = data.get("ckey2")
|
||
scale2 = data.get("scale2")
|
||
angle2 = data.get("angle2")
|
||
|
||
# 设置叶子属性
|
||
leaf["sw_ckey"] = color
|
||
if scale:
|
||
leaf["sw_scale"] = scale
|
||
if angle:
|
||
leaf["sw_angle"] = angle
|
||
|
||
logger.debug(f"板材材质: {color}")
|
||
|
||
# 创建板件的六个面(正面、反面、四个边面)
|
||
self._create_board_six_faces_fast(
|
||
leaf, data, color, scale, angle, color2, scale2, angle2)
|
||
|
||
logger.debug(f"板材部件创建完成: {leaf.name}")
|
||
return leaf
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建板材部件失败: {e}")
|
||
return None
|
||
|
||
def _create_transparent_material(self):
|
||
"""创建透明材质用于容器对象"""
|
||
try:
|
||
material_name = "SUW_Container_Transparent"
|
||
|
||
# 检查是否已存在
|
||
if material_name in bpy.data.materials:
|
||
return bpy.data.materials[material_name]
|
||
|
||
# 创建透明材质
|
||
material = bpy.data.materials.new(name=material_name)
|
||
material.use_nodes = True
|
||
|
||
# 清理默认节点
|
||
material.node_tree.nodes.clear()
|
||
|
||
# 创建节点
|
||
bsdf = material.node_tree.nodes.new(
|
||
type='ShaderNodeBsdfPrincipled')
|
||
bsdf.location = (0, 0)
|
||
|
||
output = material.node_tree.nodes.new(
|
||
type='ShaderNodeOutputMaterial')
|
||
output.location = (300, 0)
|
||
|
||
# 连接节点
|
||
material.node_tree.links.new(
|
||
bsdf.outputs['BSDF'], output.inputs['Surface'])
|
||
|
||
# 设置完全透明
|
||
bsdf.inputs['Base Color'].default_value = (
|
||
0.5, 0.5, 0.5, 1.0) # 灰色
|
||
bsdf.inputs['Alpha'].default_value = 0.0 # 完全透明
|
||
|
||
# 设置混合模式
|
||
material.blend_method = 'BLEND'
|
||
material.use_backface_culling = False
|
||
|
||
logger.info(f"✅ 创建容器透明材质: {material_name}")
|
||
return material
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建透明材质失败: {e}")
|
||
return None
|
||
|
||
def c03(self, data: Dict[str, Any]):
|
||
"""add_zone - 添加区域 - 修复父对象设置问题"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
logger.error("Blender不可用")
|
||
return
|
||
|
||
uid = data.get("uid")
|
||
zid = data.get("zid")
|
||
zip_id = data.get("zip", -1)
|
||
elements = data.get("children", [])
|
||
|
||
logger.info(f"🏗️ 开始创建区域: uid={uid}, zid={zid}, zip={zip_id}")
|
||
|
||
def create_zone():
|
||
try:
|
||
# 创建区域组 - 保持为Empty对象
|
||
group_name = f"Zone_{zid}"
|
||
group = bpy.data.objects.new(group_name, None)
|
||
|
||
# 设置父对象 - 根据zip_id查找父Zone
|
||
if zip_id > 0:
|
||
# 查找父Zone
|
||
parent_zone_name = f"Zone_{zip_id}"
|
||
parent_zone = bpy.data.objects.get(parent_zone_name)
|
||
if parent_zone:
|
||
group.parent = parent_zone
|
||
else:
|
||
logger.warning(f"未找到父Zone: {parent_zone_name}")
|
||
# 如果zip_id <= 0,则不设置父对象(顶级Zone)
|
||
|
||
# 设置属性
|
||
group["sw_uid"] = uid
|
||
group["sw_zid"] = zid
|
||
group["sw_zip"] = zip_id
|
||
group["sw_typ"] = "zid"
|
||
|
||
bpy.context.scene.collection.objects.link(group)
|
||
|
||
# 将Zone存储到zones字典中
|
||
zones_dict = self.get_zones(data)
|
||
zones_dict[zid] = group
|
||
|
||
# 处理子元素 - 给face应用透明材质
|
||
for element in elements:
|
||
surf = element.get("surf")
|
||
if surf:
|
||
self.create_face_safe(
|
||
group, surf, transparent=True)
|
||
|
||
logger.info(f"✅ 区域创建完成: {group_name}")
|
||
return group
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建区域失败: {e}")
|
||
return None
|
||
|
||
# 在主线程执行
|
||
return execute_in_main_thread(create_zone)
|
||
|
||
except Exception as e:
|
||
logger.error(f"c03命令失败: {e}")
|
||
return None
|
||
|
||
def create_face_safe(self, container, surface, color=None, scale=None, angle=None,
|
||
series=None, reverse_face=False, back_material=True,
|
||
saved_color=None, typ=None, transparent=False):
|
||
"""创建面 - 支持透明材质选项"""
|
||
try:
|
||
if not BLENDER_AVAILABLE:
|
||
logger.error("Blender不可用")
|
||
return None
|
||
|
||
# 获取分段数据
|
||
segs = surface.get("segs", [])
|
||
if not segs:
|
||
logger.error("没有分段数据")
|
||
return None
|
||
|
||
# 创建顶点
|
||
vertices = []
|
||
for i, seg in enumerate(segs):
|
||
if len(seg) >= 2:
|
||
coord_str = seg[0].strip('()')
|
||
try:
|
||
x, y, z = map(float, coord_str.split(','))
|
||
# 转换为米(Blender使用米作为单位)
|
||
vertex = (x * 0.001, y * 0.001, z * 0.001)
|
||
vertices.append(vertex)
|
||
except ValueError as e:
|
||
logger.error(f"解析顶点失败: {coord_str}, 错误: {e}")
|
||
continue
|
||
|
||
if len(vertices) < 3:
|
||
logger.error(f"顶点数量不足,无法创建面: {len(vertices)}")
|
||
return None
|
||
|
||
# 创建网格
|
||
mesh_name = f"Face_{surface.get('f', 0)}_{int(time.time())}"
|
||
mesh = bpy.data.meshes.new(mesh_name)
|
||
|
||
# 创建面
|
||
edges = []
|
||
faces = []
|
||
|
||
if len(vertices) == 4:
|
||
# 四边形
|
||
faces = [(0, 1, 2, 3)]
|
||
elif len(vertices) == 3:
|
||
# 三角形
|
||
faces = [(0, 1, 2)]
|
||
else:
|
||
# 复杂多边形,创建扇形三角形
|
||
for i in range(1, len(vertices) - 1):
|
||
faces.append((0, i, i + 1))
|
||
|
||
# 从顶点、边、面创建网格
|
||
mesh.from_pydata(vertices, edges, faces)
|
||
mesh.update()
|
||
|
||
# 创建对象
|
||
obj_name = f"Face_{surface.get('f', 0)}"
|
||
face_obj = bpy.data.objects.new(obj_name, mesh)
|
||
|
||
# 设置父对象
|
||
face_obj.parent = container
|
||
|
||
# 添加到场景
|
||
bpy.context.scene.collection.objects.link(face_obj)
|
||
|
||
# 设置面属性
|
||
if surface.get("p"):
|
||
face_obj["sw_p"] = surface["p"]
|
||
if surface.get("f"):
|
||
face_obj["sw_f"] = surface["f"]
|
||
|
||
# 确保对象可见
|
||
face_obj.hide_viewport = False
|
||
face_obj.hide_render = False
|
||
face_obj.hide_set(False)
|
||
|
||
# 应用材质
|
||
if transparent:
|
||
# 应用透明材质
|
||
transparent_material = self._create_transparent_material()
|
||
if transparent_material:
|
||
face_obj.data.materials.append(transparent_material)
|
||
logger.info(f"✅ Face {obj_name} 应用透明材质")
|
||
elif color:
|
||
# 应用指定材质
|
||
material = self.get_texture(color)
|
||
if material:
|
||
face_obj.data.materials.append(material)
|
||
else:
|
||
# 创建默认材质
|
||
default_mat = bpy.data.materials.new(
|
||
name="DefaultMaterial")
|
||
default_mat.use_nodes = True
|
||
default_mat.node_tree.nodes["Principled BSDF"].inputs[0].default_value = (
|
||
0.8, 0.8, 0.8, 1.0)
|
||
face_obj.data.materials.append(default_mat)
|
||
|
||
# 注册到内存管理器
|
||
memory_manager.register_mesh(mesh)
|
||
memory_manager.register_object(face_obj)
|
||
|
||
# 添加到series(如果提供)
|
||
if series is not None:
|
||
series.append(face_obj)
|
||
|
||
return face_obj
|
||
|
||
except Exception as e:
|
||
logger.error(f"创建面失败: {e}")
|
||
return None
|
||
|
||
|
||
# ==================== 模块级别的便利函数 ====================
|
||
|
||
def create_suw_instance():
|
||
"""创建SUW实例的便利函数"""
|
||
return SUWImpl.get_instance()
|
||
|
||
|
||
def cleanup_blender_memory():
|
||
"""清理Blender内存的便利函数"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
cleanup_count = memory_manager.cleanup_orphaned_data()
|
||
gc.collect()
|
||
logger.info(f"清理了 {cleanup_count} 个孤立数据")
|
||
return cleanup_count
|
||
return 0
|
||
except Exception as e:
|
||
logger.error(f"清理内存失败: {e}")
|
||
return 0
|
||
|
||
|
||
def get_memory_usage_summary():
|
||
"""获取内存使用摘要"""
|
||
try:
|
||
if BLENDER_AVAILABLE:
|
||
return {
|
||
"objects": len(bpy.data.objects),
|
||
"meshes": len(bpy.data.meshes),
|
||
"materials": len(bpy.data.materials),
|
||
"memory_manager_stats": memory_manager.creation_stats.copy()
|
||
}
|
||
return {"blender_available": False}
|
||
except Exception as e:
|
||
logger.error(f"获取内存使用摘要失败: {e}")
|
||
return {"error": str(e)}
|
||
|
||
|
||
# ==================== 主程序入口 ====================
|
||
|
||
if __name__ == "__main__":
|
||
# 测试代码
|
||
try:
|
||
logger.info("开始测试SUWImpl内存管理")
|
||
|
||
# 创建实例
|
||
suw = create_suw_instance()
|
||
suw.startup()
|
||
|
||
# 获取内存报告
|
||
report = suw.get_memory_report()
|
||
logger.info(f"内存报告: {report}")
|
||
|
||
# 执行诊断
|
||
issues = suw.diagnose_system_state()
|
||
if not issues:
|
||
logger.info("✅ 系统状态正常")
|
||
|
||
# 清理测试
|
||
cleanup_count = cleanup_blender_memory()
|
||
logger.info(f"清理测试完成: {cleanup_count}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"测试失败: {e}")
|
||
|
||
finally:
|
||
logger.info("测试完成")
|