suwoodblender/blenderpython/suw_impl - 副本 (2).py

2155 lines
76 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.

# 清理选择状态
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("测试完成")