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

2155 lines
76 KiB
Python
Raw Normal View History

2025-07-18 17:09:39 +08:00
# 清理选择状态
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("测试完成")