793 lines
32 KiB
Python
793 lines
32 KiB
Python
|
#!/usr/bin/env python3
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
"""
|
|||
|
SUW Core - Part Creator Module
|
|||
|
拆分自: suw_impl.py (Line 1302-1600)
|
|||
|
用途: Blender部件创建、板材管理、UV处理
|
|||
|
版本: 1.0.0
|
|||
|
作者: SUWood Team
|
|||
|
"""
|
|||
|
|
|||
|
from . import material_manager as mm_module
|
|||
|
from .material_manager import material_manager
|
|||
|
from .memory_manager import memory_manager, dependency_manager, safe_blender_operation
|
|||
|
from .data_manager import data_manager, get_data_manager
|
|||
|
import time
|
|||
|
import logging
|
|||
|
from typing import Dict, Any, Optional, List, Tuple
|
|||
|
|
|||
|
# 设置日志
|
|||
|
logger = logging.getLogger(__name__)
|
|||
|
|
|||
|
# 检查Blender可用性
|
|||
|
try:
|
|||
|
import bpy
|
|||
|
BLENDER_AVAILABLE = True
|
|||
|
except ImportError:
|
|||
|
BLENDER_AVAILABLE = False
|
|||
|
|
|||
|
# ==================== 部件创建器类 ====================
|
|||
|
|
|||
|
|
|||
|
class PartCreator:
|
|||
|
"""部件创建器 - 负责所有部件相关操作"""
|
|||
|
|
|||
|
def __init__(self):
|
|||
|
"""
|
|||
|
初始化部件创建器 - 完全独立,不依赖suw_impl
|
|||
|
"""
|
|||
|
# 使用全局数据管理器
|
|||
|
self.data_manager = get_data_manager()
|
|||
|
|
|||
|
# 【修复】初始化时间戳,避免AttributeError
|
|||
|
self._last_board_creation_time = 0
|
|||
|
|
|||
|
# 创建统计
|
|||
|
self.creation_stats = {
|
|||
|
"parts_created": 0,
|
|||
|
"boards_created": 0,
|
|||
|
"creation_errors": 0
|
|||
|
}
|
|||
|
|
|||
|
logger.info("PartCreator 初始化完成")
|
|||
|
|
|||
|
def get_parts(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|||
|
"""获取零件信息 - 保持原始方法名和参数"""
|
|||
|
return self.data_manager.get_parts(data)
|
|||
|
|
|||
|
def c04(self, data: Dict[str, Any]):
|
|||
|
"""c04 - 添加部件 - 修复版本,参考suw_impl.py的实现"""
|
|||
|
try:
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
logger.warning("Blender 不可用,跳过零件创建")
|
|||
|
return
|
|||
|
|
|||
|
uid = data.get("uid")
|
|||
|
root = data.get("cp")
|
|||
|
|
|||
|
if not uid or not root:
|
|||
|
logger.error("缺少必要参数: uid或cp")
|
|||
|
return
|
|||
|
|
|||
|
logger.info(f" 开始创建部件: uid={uid}, cp={root}")
|
|||
|
|
|||
|
# 【修复1】获取parts数据结构
|
|||
|
parts = self.get_parts(data)
|
|||
|
|
|||
|
# 【修复2】检查是否已存在
|
|||
|
if root in parts:
|
|||
|
existing_part = parts[root]
|
|||
|
if existing_part and self._is_object_valid(existing_part):
|
|||
|
logger.info(f"✅ 部件 {root} 已存在,跳过创建")
|
|||
|
return existing_part
|
|||
|
else:
|
|||
|
logger.warning(f"清理无效的部件引用: {root}")
|
|||
|
del parts[root]
|
|||
|
|
|||
|
# 【修复3】创建部件容器 - 修改命名格式为Part_{uid}_{cp}
|
|||
|
part_name = f"Part_{uid}_{root}"
|
|||
|
part = bpy.data.objects.new(part_name, None)
|
|||
|
bpy.context.scene.collection.objects.link(part)
|
|||
|
|
|||
|
logger.info(f"✅ 创建Part对象: {part_name}")
|
|||
|
|
|||
|
# 【修复4】设置部件基本属性
|
|||
|
part["sw_uid"] = uid
|
|||
|
part["sw_cp"] = root
|
|||
|
part["sw_typ"] = "part"
|
|||
|
part["sw_zid"] = data.get("zid")
|
|||
|
part["sw_pid"] = data.get("pid")
|
|||
|
|
|||
|
# 【新增】设置layer属性 - 参考Ruby版本的逻辑
|
|||
|
layer = data.get("layer", 0)
|
|||
|
part["sw_layer"] = layer
|
|||
|
if layer == 1:
|
|||
|
logger.info(f"✅ 部件 {part_name} 标记为门板图层 (layer=1)")
|
|||
|
elif layer == 2:
|
|||
|
logger.info(f"✅ 部件 {part_name} 标记为抽屉图层 (layer=2)")
|
|||
|
else:
|
|||
|
logger.info(f"✅ 部件 {part_name} 标记为普通图层 (layer=0)")
|
|||
|
|
|||
|
# 【新增】设置门板属性 - 参考Ruby版本的逻辑
|
|||
|
door_type = data.get("dor", 0)
|
|||
|
part["sw_door"] = door_type
|
|||
|
if door_type in [10, 15]:
|
|||
|
part["sw_door_width"] = data.get("dow", 0)
|
|||
|
part["sw_door_pos"] = data.get("dop", "F")
|
|||
|
logger.info(
|
|||
|
f"✅ 部件 {part_name} 设置门板属性: door_type={door_type}, width={data.get('dow', 0)}, pos={data.get('dop', 'F')}")
|
|||
|
|
|||
|
# 【新增】设置抽屉属性 - 参考Ruby版本的逻辑
|
|||
|
drawer_type = data.get("drw", 0)
|
|||
|
part["sw_drawer"] = drawer_type
|
|||
|
if drawer_type in [73, 74]: # DR_LP/DR_RP
|
|||
|
part["sw_dr_depth"] = data.get("drd", 0)
|
|||
|
logger.info(
|
|||
|
f"📦 部件 {part_name} 设置抽屉属性: drawer_type={drawer_type}, depth={data.get('drd', 0)}")
|
|||
|
elif drawer_type == 70: # DR_DP
|
|||
|
drv = data.get("drv")
|
|||
|
if drv:
|
|||
|
# 这里需要解析向量,暂时存储原始值
|
|||
|
part["sw_drawer_dir"] = drv
|
|||
|
logger.info(f"📦 部件 {part_name} 设置抽屉方向: {drv}")
|
|||
|
|
|||
|
# 【新增】设置Part对象的父对象为Zone对象
|
|||
|
zone_name = f"Zone_{uid}"
|
|||
|
zone_obj = bpy.data.objects.get(zone_name)
|
|||
|
if zone_obj:
|
|||
|
part.parent = zone_obj
|
|||
|
logger.info(f"✅ 设置Part对象 {part_name} 的父对象为Zone: {zone_name}")
|
|||
|
else:
|
|||
|
logger.warning(f"⚠️ 未找到Zone对象: {zone_name},Part对象将没有父对象")
|
|||
|
|
|||
|
# 【修复5】存储部件到数据结构 - 使用data_manager.add_part()方法
|
|||
|
self.data_manager.add_part(uid, root, part)
|
|||
|
logger.info(
|
|||
|
f"✅ 使用data_manager.add_part()存储部件数据: uid={uid}, cp={root}")
|
|||
|
|
|||
|
# 【修复6】处理finals数据
|
|||
|
finals = data.get("finals", [])
|
|||
|
logger.info(f" 处理 {len(finals)} 个板材数据")
|
|||
|
|
|||
|
created_boards = 0
|
|||
|
|
|||
|
for i, final_data in enumerate(finals):
|
|||
|
try:
|
|||
|
board = self.create_board_with_material_and_uv(
|
|||
|
part, final_data)
|
|||
|
if board:
|
|||
|
created_boards += 1
|
|||
|
logger.info(
|
|||
|
f"✅ 板材 {i+1}/{len(finals)} 创建成功: {board.name}")
|
|||
|
|
|||
|
# 【修复7】移除频繁的依赖图更新,避免评估过程中的错误
|
|||
|
# if i % 5 == 0:
|
|||
|
# bpy.context.view_layer.update()
|
|||
|
else:
|
|||
|
logger.warning(f"⚠️ 板材 {i+1}/{len(finals)} 创建失败")
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"❌ 创建板材 {i+1}/{len(finals)} 失败: {e}")
|
|||
|
# 【修复8】单个板材失败时的恢复 - 移除依赖图更新
|
|||
|
try:
|
|||
|
import gc
|
|||
|
gc.collect()
|
|||
|
# bpy.context.view_layer.update() # 移除这行
|
|||
|
except:
|
|||
|
pass
|
|||
|
|
|||
|
logger.info(f"📊 板材创建统计: {created_boards}/{len(finals)} 成功")
|
|||
|
|
|||
|
# 【修复9】最终清理 - 移除依赖图更新
|
|||
|
try:
|
|||
|
# bpy.context.view_layer.update() # 移除这行
|
|||
|
import gc
|
|||
|
gc.collect()
|
|||
|
except Exception as cleanup_error:
|
|||
|
logger.warning(f"最终清理失败: {cleanup_error}")
|
|||
|
|
|||
|
# 【修复10】验证创建结果
|
|||
|
if part.name in bpy.data.objects:
|
|||
|
logger.info(f"🎉 部件创建完全成功: {part_name}")
|
|||
|
return part
|
|||
|
else:
|
|||
|
logger.error(f"❌ 部件创建失败: {part_name} 不在bpy.data.objects中")
|
|||
|
return None
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"❌ c04命令执行失败: {e}")
|
|||
|
import traceback
|
|||
|
logger.error(traceback.format_exc())
|
|||
|
return None
|
|||
|
|
|||
|
def create_board_with_material_and_uv(self, part, data):
|
|||
|
"""创建板材并关联材质和启用UV - 完全修复版本,避免bpy.ops"""
|
|||
|
try:
|
|||
|
# 获取正反面数据
|
|||
|
obv = data.get("obv")
|
|||
|
rev = data.get("rev")
|
|||
|
|
|||
|
if not obv or not rev:
|
|||
|
logger.warning("缺少正反面数据,创建默认板材")
|
|||
|
return self.create_default_board_with_material(part, data)
|
|||
|
|
|||
|
# 解析顶点计算精确尺寸
|
|||
|
obv_vertices = self._parse_surface_vertices(obv)
|
|||
|
rev_vertices = self._parse_surface_vertices(rev)
|
|||
|
|
|||
|
if len(obv_vertices) >= 3 and len(rev_vertices) >= 3:
|
|||
|
# 计算板材的精确边界
|
|||
|
all_vertices = obv_vertices + rev_vertices
|
|||
|
|
|||
|
min_x = min(v[0] for v in all_vertices)
|
|||
|
max_x = max(v[0] for v in all_vertices)
|
|||
|
min_y = min(v[1] for v in all_vertices)
|
|||
|
max_y = max(v[1] for v in all_vertices)
|
|||
|
min_z = min(v[2] for v in all_vertices)
|
|||
|
max_z = max(v[2] for v in all_vertices)
|
|||
|
|
|||
|
# 计算中心点和精确尺寸
|
|||
|
center_x = (min_x + max_x) / 2
|
|||
|
center_y = (min_y + max_y) / 2
|
|||
|
center_z = (min_z + max_z) / 2
|
|||
|
|
|||
|
size_x = max(max_x - min_x, 0.001) # 确保最小尺寸
|
|||
|
size_y = max(max_y - min_y, 0.001)
|
|||
|
size_z = max(max_z - min_z, 0.001)
|
|||
|
|
|||
|
logger.info(
|
|||
|
f" 计算板材尺寸: {size_x:.3f}x{size_y:.3f}x{size_z:.3f}m, 中心: ({center_x:.3f},{center_y:.3f},{center_z:.3f})")
|
|||
|
|
|||
|
# 【修复1】完全避免bpy.ops,直接创建网格对象
|
|||
|
board = None
|
|||
|
try:
|
|||
|
# 创建网格数据
|
|||
|
mesh_data = bpy.data.meshes.new("Board_Mesh")
|
|||
|
|
|||
|
# 创建立方体的顶点和面
|
|||
|
vertices = [
|
|||
|
(-0.5, -0.5, -0.5), # 0
|
|||
|
(0.5, -0.5, -0.5), # 1
|
|||
|
(0.5, 0.5, -0.5), # 2
|
|||
|
(-0.5, 0.5, -0.5), # 3
|
|||
|
(-0.5, -0.5, 0.5), # 4
|
|||
|
(0.5, -0.5, 0.5), # 5
|
|||
|
(0.5, 0.5, 0.5), # 6
|
|||
|
(-0.5, 0.5, 0.5) # 7
|
|||
|
]
|
|||
|
|
|||
|
faces = [
|
|||
|
(0, 1, 2, 3), # 底面
|
|||
|
(4, 7, 6, 5), # 顶面
|
|||
|
(0, 4, 5, 1), # 前面
|
|||
|
(2, 6, 7, 3), # 后面
|
|||
|
(1, 5, 6, 2), # 右面
|
|||
|
(0, 3, 7, 4) # 左面
|
|||
|
]
|
|||
|
|
|||
|
# 创建网格
|
|||
|
mesh_data.from_pydata(vertices, [], faces)
|
|||
|
mesh_data.update()
|
|||
|
|
|||
|
# 创建对象
|
|||
|
board = bpy.data.objects.new("Board", mesh_data)
|
|||
|
|
|||
|
# 设置位置
|
|||
|
board.location = (center_x, center_y, center_z)
|
|||
|
|
|||
|
# 添加到场景
|
|||
|
bpy.context.scene.collection.objects.link(board)
|
|||
|
|
|||
|
logger.info("✅ 使用直接创建方式成功创建板材对象")
|
|||
|
|
|||
|
except Exception as create_error:
|
|||
|
logger.error(f"直接创建板材对象失败: {create_error}")
|
|||
|
return None
|
|||
|
|
|||
|
if not board:
|
|||
|
logger.error("无法创建板材对象")
|
|||
|
return None
|
|||
|
|
|||
|
# 【修复2】缩放到精确尺寸
|
|||
|
board.scale = (size_x, size_y, size_z)
|
|||
|
|
|||
|
# 【调试】添加缩放验证日志
|
|||
|
logger.info(f"🔧 板材缩放信息:")
|
|||
|
logger.info(
|
|||
|
f" 计算尺寸: {size_x:.6f} x {size_y:.6f} x {size_z:.6f} 米")
|
|||
|
logger.info(f" 应用缩放: {board.scale}")
|
|||
|
logger.info(
|
|||
|
f" 中心位置: ({center_x:.6f}, {center_y:.6f}, {center_z:.6f})")
|
|||
|
logger.info(f" 板材名称: {board.name}")
|
|||
|
|
|||
|
# 【修复3】设置属性和父子关系
|
|||
|
board.parent = part
|
|||
|
board.name = f"Board_{part.name}"
|
|||
|
board["sw_face_type"] = "board"
|
|||
|
board["sw_uid"] = part.get("sw_uid")
|
|||
|
board["sw_cp"] = part.get("sw_cp")
|
|||
|
board["sw_typ"] = "board"
|
|||
|
|
|||
|
logger.info(f"✅ 板材属性设置完成: {board.name}, 父对象: {part.name}")
|
|||
|
|
|||
|
# 【修复4】关联材质 - 使用修复后的材质管理器
|
|||
|
color = data.get("ckey", "mat_default")
|
|||
|
if color:
|
|||
|
try:
|
|||
|
# 导入材质管理器
|
|||
|
from suw_core.material_manager import MaterialManager
|
|||
|
material_manager = MaterialManager()
|
|||
|
material = material_manager.get_texture(color)
|
|||
|
|
|||
|
if material and board.data:
|
|||
|
# 清空现有材质
|
|||
|
board.data.materials.clear()
|
|||
|
# 添加新材质
|
|||
|
board.data.materials.append(material)
|
|||
|
logger.info(f"✅ 材质 {color} 已关联到板材 {board.name}")
|
|||
|
else:
|
|||
|
logger.warning(f"材质 {color} 未找到或板材数据无效")
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"关联材质失败: {e}")
|
|||
|
|
|||
|
# 【修复5】启用UV - 移除依赖图更新
|
|||
|
self.enable_uv_for_board(board)
|
|||
|
|
|||
|
return board
|
|||
|
else:
|
|||
|
logger.warning("顶点数据不足,创建默认板材")
|
|||
|
return self.create_default_board_with_material(part, data)
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"创建板材失败: {e}")
|
|||
|
return self.create_default_board_with_material(part, data)
|
|||
|
|
|||
|
def enable_uv_for_board(self, board):
|
|||
|
"""为板件启用UV - 保持原始方法名和参数"""
|
|||
|
try:
|
|||
|
if not board or not board.data:
|
|||
|
logger.warning("无效的板件对象,无法启用UV")
|
|||
|
return
|
|||
|
|
|||
|
# 确保网格数据存在
|
|||
|
mesh = board.data
|
|||
|
if not mesh:
|
|||
|
logger.warning("板件没有网格数据")
|
|||
|
return
|
|||
|
|
|||
|
# 创建UV贴图层(如果不存在)
|
|||
|
if not mesh.uv_layers:
|
|||
|
uv_layer = mesh.uv_layers.new(name="UVMap")
|
|||
|
else:
|
|||
|
uv_layer = mesh.uv_layers[0]
|
|||
|
|
|||
|
# 确保UV层是活动的
|
|||
|
mesh.uv_layers.active = uv_layer
|
|||
|
|
|||
|
# 更新网格数据 - 移除可能导致依赖图更新的操作
|
|||
|
# mesh.calc_loop_triangles() # 移除这行
|
|||
|
|
|||
|
# 为立方体创建基本UV坐标
|
|||
|
if len(mesh.polygons) == 6: # 标准立方体
|
|||
|
# 为每个面分配UV坐标
|
|||
|
for poly_idx, poly in enumerate(mesh.polygons):
|
|||
|
# 标准UV坐标 (0,0) (1,0) (1,1) (0,1)
|
|||
|
uv_coords = [(0.0, 0.0), (1.0, 0.0),
|
|||
|
(1.0, 1.0), (0.0, 1.0)]
|
|||
|
|
|||
|
for loop_idx, loop_index in enumerate(poly.loop_indices):
|
|||
|
if loop_idx < len(uv_coords):
|
|||
|
uv_layer.data[loop_index].uv = uv_coords[loop_idx]
|
|||
|
else:
|
|||
|
# 为非标准网格设置简单UV
|
|||
|
for loop in mesh.loops:
|
|||
|
uv_layer.data[loop.index].uv = (0.5, 0.5)
|
|||
|
|
|||
|
# 更新网格 - 移除可能导致依赖图更新的操作
|
|||
|
# mesh.update() # 移除这行
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"启用UV失败: {e}")
|
|||
|
|
|||
|
def create_default_board_with_material(self, part, data):
|
|||
|
"""创建默认板材 - 修复版本,使用更安全的对象创建方式"""
|
|||
|
try:
|
|||
|
# 【修复1】使用更安全的对象创建方式,避免bpy.ops上下文问题
|
|||
|
board = None
|
|||
|
|
|||
|
# 方法1:尝试使用bpy.ops(如果上下文可用)
|
|||
|
try:
|
|||
|
if hasattr(bpy.context, 'active_object'):
|
|||
|
bpy.ops.mesh.primitive_cube_add(
|
|||
|
size=1,
|
|||
|
location=(0, 0, 0)
|
|||
|
)
|
|||
|
board = bpy.context.active_object
|
|||
|
logger.info("✅ 使用bpy.ops成功创建立方体")
|
|||
|
else:
|
|||
|
raise Exception("bpy.context.active_object不可用")
|
|||
|
except Exception as ops_error:
|
|||
|
logger.warning(f"使用bpy.ops创建对象失败: {ops_error}")
|
|||
|
|
|||
|
# 方法2:回退方案 - 直接创建网格对象
|
|||
|
try:
|
|||
|
# 创建网格数据
|
|||
|
mesh_data = bpy.data.meshes.new("Board_Mesh")
|
|||
|
|
|||
|
# 创建立方体的顶点和面
|
|||
|
vertices = [
|
|||
|
(-0.5, -0.5, -0.5), # 0
|
|||
|
(0.5, -0.5, -0.5), # 1
|
|||
|
(0.5, 0.5, -0.5), # 2
|
|||
|
(-0.5, 0.5, -0.5), # 3
|
|||
|
(-0.5, -0.5, 0.5), # 4
|
|||
|
(0.5, -0.5, 0.5), # 5
|
|||
|
(0.5, 0.5, 0.5), # 6
|
|||
|
(-0.5, 0.5, 0.5) # 7
|
|||
|
]
|
|||
|
|
|||
|
faces = [
|
|||
|
(0, 1, 2, 3), # 底面
|
|||
|
(4, 7, 6, 5), # 顶面
|
|||
|
(0, 4, 5, 1), # 前面
|
|||
|
(2, 6, 7, 3), # 后面
|
|||
|
(1, 5, 6, 2), # 右面
|
|||
|
(0, 3, 7, 4) # 左面
|
|||
|
]
|
|||
|
|
|||
|
# 创建网格
|
|||
|
mesh_data.from_pydata(vertices, [], faces)
|
|||
|
mesh_data.update()
|
|||
|
|
|||
|
# 创建对象
|
|||
|
board = bpy.data.objects.new("Board_Default", mesh_data)
|
|||
|
|
|||
|
# 添加到场景
|
|||
|
bpy.context.collection.objects.link(board)
|
|||
|
|
|||
|
logger.info("✅ 使用直接创建方式成功创建立方体")
|
|||
|
|
|||
|
except Exception as direct_error:
|
|||
|
logger.error(f"直接创建对象也失败: {direct_error}")
|
|||
|
return None
|
|||
|
|
|||
|
if not board:
|
|||
|
logger.error("无法创建板材对象")
|
|||
|
return None
|
|||
|
|
|||
|
# 【修复2】设置属性和父子关系
|
|||
|
try:
|
|||
|
board.parent = part
|
|||
|
board.name = f"Board_{part.name}_default"
|
|||
|
board["sw_face_type"] = "board"
|
|||
|
|
|||
|
# 从part获取uid和cp信息
|
|||
|
uid = part.get("sw_uid")
|
|||
|
cp = part.get("sw_cp")
|
|||
|
board["sw_uid"] = uid
|
|||
|
board["sw_cp"] = cp
|
|||
|
board["sw_typ"] = "board"
|
|||
|
|
|||
|
logger.info(f"✅ 默认板材属性设置完成: {board.name}, 父对象: {part.name}")
|
|||
|
except Exception as attr_error:
|
|||
|
logger.error(f"设置板材属性失败: {attr_error}")
|
|||
|
|
|||
|
# 【修复3】关联默认材质 - 使用更安全的材质处理
|
|||
|
try:
|
|||
|
color = data.get("ckey", "mat_default")
|
|||
|
|
|||
|
# 使用更安全的材质管理器初始化方式
|
|||
|
if not mm_module.material_manager:
|
|||
|
mm_module.material_manager = mm_module.MaterialManager()
|
|||
|
|
|||
|
# 额外安全检查
|
|||
|
if mm_module.material_manager and hasattr(mm_module.material_manager, 'get_texture'):
|
|||
|
material = mm_module.material_manager.get_texture(color)
|
|||
|
else:
|
|||
|
logger.error("材质管理器未正确初始化")
|
|||
|
material = None
|
|||
|
|
|||
|
if material and board.data:
|
|||
|
board.data.materials.clear()
|
|||
|
board.data.materials.append(material)
|
|||
|
logger.info(f"✅ 材质 {color} 已关联到板材 {board.name}")
|
|||
|
else:
|
|||
|
logger.warning(f"材质 {color} 未找到或板材数据无效")
|
|||
|
|
|||
|
except Exception as material_error:
|
|||
|
logger.error(f"❌ 默认材质处理失败: {material_error}")
|
|||
|
|
|||
|
# 【修复4】启用UV
|
|||
|
try:
|
|||
|
self.enable_uv_for_board(board)
|
|||
|
except Exception as uv_error:
|
|||
|
logger.error(f"启用UV失败: {uv_error}")
|
|||
|
|
|||
|
return board
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"创建默认板材失败: {e}")
|
|||
|
return None
|
|||
|
|
|||
|
def parse_surface_vertices(self, surface):
|
|||
|
"""解析表面顶点 - 保持原始方法名和参数"""
|
|||
|
try:
|
|||
|
vertices = []
|
|||
|
if not surface:
|
|||
|
return vertices
|
|||
|
|
|||
|
segs = surface.get("segs", [])
|
|||
|
for seg in segs:
|
|||
|
if len(seg) >= 2:
|
|||
|
coord_str = seg[0].strip('()')
|
|||
|
try:
|
|||
|
# 解析坐标字符串
|
|||
|
coords = coord_str.split(',')
|
|||
|
if len(coords) >= 3:
|
|||
|
x = float(coords[0]) * 0.001 # 转换为米
|
|||
|
y = float(coords[1]) * 0.001
|
|||
|
z = float(coords[2]) * 0.001
|
|||
|
vertices.append((x, y, z))
|
|||
|
except ValueError as e:
|
|||
|
logger.warning(f"解析顶点坐标失败: {coord_str}, 错误: {e}")
|
|||
|
continue
|
|||
|
|
|||
|
logger.debug(f"解析得到 {len(vertices)} 个顶点")
|
|||
|
return vertices
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"解析表面顶点失败: {e}")
|
|||
|
return []
|
|||
|
|
|||
|
def _is_object_valid(self, obj) -> bool:
|
|||
|
"""检查对象是否有效 - 保持原始方法名和参数"""
|
|||
|
try:
|
|||
|
if not obj:
|
|||
|
return False
|
|||
|
|
|||
|
if not BLENDER_AVAILABLE:
|
|||
|
return True # 在非Blender环境中假设有效
|
|||
|
|
|||
|
# 检查对象是否仍在Blender数据中
|
|||
|
return obj.name in bpy.data.objects
|
|||
|
|
|||
|
except Exception:
|
|||
|
return False
|
|||
|
|
|||
|
def clear_part_children(self, part):
|
|||
|
"""清理部件子对象 - 保持原始方法名和参数"""
|
|||
|
try:
|
|||
|
if not part or not BLENDER_AVAILABLE:
|
|||
|
return
|
|||
|
|
|||
|
# 清理所有子对象
|
|||
|
children_to_remove = []
|
|||
|
for child in part.children:
|
|||
|
children_to_remove.append(child)
|
|||
|
|
|||
|
for child in children_to_remove:
|
|||
|
if child.name in bpy.data.objects:
|
|||
|
bpy.data.objects.remove(child, do_unlink=True)
|
|||
|
|
|||
|
logger.info(f"清理部件 {part.name} 的 {len(children_to_remove)} 个子对象")
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"清理部件子对象失败: {e}")
|
|||
|
|
|||
|
def get_creation_stats(self) -> Dict[str, Any]:
|
|||
|
"""获取创建统计信息"""
|
|||
|
return self.creation_stats.copy()
|
|||
|
|
|||
|
def get_part_creator_stats(self) -> Dict[str, Any]:
|
|||
|
"""获取部件创建器统计信息"""
|
|||
|
try:
|
|||
|
# 从data_manager获取parts数据
|
|||
|
parts_data = {}
|
|||
|
if hasattr(self.data_manager, 'parts'):
|
|||
|
parts_data = self.data_manager.parts
|
|||
|
|
|||
|
stats = {
|
|||
|
"manager_type": "PartCreator",
|
|||
|
"parts_by_uid": {uid: len(parts) for uid, parts in parts_data.items()},
|
|||
|
"total_parts": sum(len(parts) for parts in parts_data.values()),
|
|||
|
"creation_stats": self.creation_stats.copy(),
|
|||
|
"data_manager_attached": self.data_manager is not None,
|
|||
|
"blender_available": BLENDER_AVAILABLE
|
|||
|
}
|
|||
|
return stats
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"获取部件创建器统计失败: {e}")
|
|||
|
return {"error": str(e)}
|
|||
|
|
|||
|
def reset_creation_stats(self):
|
|||
|
"""重置创建统计信息"""
|
|||
|
self.creation_stats = {
|
|||
|
"parts_created": 0,
|
|||
|
"boards_created": 0,
|
|||
|
"creation_errors": 0
|
|||
|
}
|
|||
|
logger.info("创建统计信息已重置")
|
|||
|
|
|||
|
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 _calculate_board_dimensions(self, final_data: dict) -> Tuple[Optional['mathutils.Vector'], Optional['mathutils.Vector']]:
|
|||
|
"""
|
|||
|
[V2] 计算板材的精确尺寸和中心点。
|
|||
|
"""
|
|||
|
try:
|
|||
|
obv_vertices = self._parse_surface_vertices(final_data.get("obv"))
|
|||
|
rev_vertices = self._parse_surface_vertices(final_data.get("rev"))
|
|||
|
|
|||
|
if not obv_vertices or not rev_vertices:
|
|||
|
logger.warning("无法解析顶点数据,使用默认尺寸")
|
|||
|
return (None, None)
|
|||
|
|
|||
|
all_vertices = obv_vertices + rev_vertices
|
|||
|
|
|||
|
min_x = min(v[0] for v in all_vertices)
|
|||
|
max_x = max(v[0] for v in all_vertices)
|
|||
|
min_y = min(v[1] for v in all_vertices)
|
|||
|
max_y = max(v[1] for v in all_vertices)
|
|||
|
min_z = min(v[2] for v in all_vertices)
|
|||
|
max_z = max(v[2] for v in all_vertices)
|
|||
|
|
|||
|
center_x = (min_x + max_x) / 2
|
|||
|
center_y = (min_y + max_y) / 2
|
|||
|
center_z = (min_z + max_z) / 2
|
|||
|
|
|||
|
size_x = max(max_x - min_x, 0.001)
|
|||
|
size_y = max(max_y - min_y, 0.001)
|
|||
|
size_z = max(max_z - min_z, 0.001)
|
|||
|
|
|||
|
logger.info(
|
|||
|
f" 计算板材尺寸: {size_x:.3f}x{size_y:.3f}x{size_z:.3f}m, 中心: ({center_x:.3f},{center_y:.3f},{center_z:.3f})")
|
|||
|
|
|||
|
return (mathutils.Vector((center_x, center_y, center_z)), mathutils.Vector((size_x, size_y, size_z)))
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"❌ 计算板材尺寸失败: {e}")
|
|||
|
return None, None
|
|||
|
|
|||
|
def _create_board_direct(self, parent_obj: 'bpy.types.Object', final_data: dict, center: 'mathutils.Vector', dimensions: 'mathutils.Vector') -> Optional['bpy.types.Object']:
|
|||
|
"""
|
|||
|
[V2] 通过直接操作bpy.data来安全地创建板材对象,避免bpy.ops的上下文错误。
|
|||
|
"""
|
|||
|
try:
|
|||
|
# 1. 创建网格和对象数据
|
|||
|
mesh_name = f"Board_Mesh_{parent_obj.name}"
|
|||
|
board_name = f"Board_{parent_obj.name}"
|
|||
|
mesh = bpy.data.meshes.new(mesh_name)
|
|||
|
board_obj = bpy.data.objects.new(board_name, mesh)
|
|||
|
|
|||
|
# 2. 将新对象链接到与父对象相同的集合中
|
|||
|
if parent_obj.users_collection:
|
|||
|
parent_obj.users_collection[0].objects.link(board_obj)
|
|||
|
else:
|
|||
|
# 如果父对象不在任何集合中,则回退到场景主集合
|
|||
|
bpy.context.scene.collection.objects.link(board_obj)
|
|||
|
|
|||
|
# 3. 直接根据最终尺寸创建顶点。这可以确保对象的缩放比例始终为(1,1,1)
|
|||
|
dx, dy, dz = dimensions.x / 2, dimensions.y / 2, dimensions.z / 2
|
|||
|
verts = [
|
|||
|
(dx, dy, dz), (-dx, dy, dz), (-dx, -dy, dz), (dx, -dy, dz),
|
|||
|
(dx, dy, -dz), (-dx, dy, -dz), (-dx, -dy, -dz), (dx, -dy, -dz),
|
|||
|
]
|
|||
|
faces = [
|
|||
|
(0, 1, 2, 3), (4, 7, 6, 5), (0, 4, 5, 1),
|
|||
|
(1, 5, 6, 2), (2, 6, 7, 3), (3, 7, 4, 0)
|
|||
|
]
|
|||
|
mesh.from_pydata(verts, [], faces)
|
|||
|
mesh.update()
|
|||
|
|
|||
|
# 4. 设置最终的位置和父子关系
|
|||
|
board_obj.location = center
|
|||
|
board_obj.parent = parent_obj
|
|||
|
|
|||
|
return board_obj
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"❌ 使用直接数据创建板材时失败: {e}")
|
|||
|
# 清理创建失败时可能产生的孤立数据
|
|||
|
if 'board_obj' in locals() and board_obj and board_obj.name in bpy.data.objects:
|
|||
|
bpy.data.objects.remove(board_obj, do_unlink=True)
|
|||
|
if 'mesh' in locals() and mesh and mesh.name in bpy.data.meshes:
|
|||
|
bpy.data.meshes.remove(mesh)
|
|||
|
return None
|
|||
|
|
|||
|
def _add_board_part(self, part_obj: 'bpy.types.Object', final_data: dict) -> Optional['bpy.types.Object']:
|
|||
|
"""
|
|||
|
[V2] 将板材对象添加到部件对象的集合中。
|
|||
|
"""
|
|||
|
try:
|
|||
|
# 1. 计算板材的精确尺寸和中心点
|
|||
|
center, dimensions = self._calculate_board_dimensions(final_data)
|
|||
|
if not center or not dimensions:
|
|||
|
logger.warning(f"无法计算板材尺寸,跳过添加板材: {final_data.get('name')}")
|
|||
|
return None
|
|||
|
|
|||
|
# 2. 使用直接数据创建板材对象
|
|||
|
board_obj = self._create_board_direct(
|
|||
|
part_obj, final_data, center, dimensions)
|
|||
|
if not board_obj:
|
|||
|
logger.warning(
|
|||
|
f"使用直接数据创建板材失败,跳过添加板材: {final_data.get('name')}")
|
|||
|
return None
|
|||
|
|
|||
|
# 3. 设置板材属性
|
|||
|
board_obj["sw_face_type"] = "board"
|
|||
|
board_obj["sw_uid"] = part_obj.get("sw_uid")
|
|||
|
board_obj["sw_cp"] = part_obj.get("sw_cp")
|
|||
|
board_obj["sw_typ"] = "board"
|
|||
|
|
|||
|
# 4. 关联材质
|
|||
|
color = final_data.get("ckey", "mat_default")
|
|||
|
if color:
|
|||
|
try:
|
|||
|
# 导入材质管理器
|
|||
|
from suw_core.material_manager import MaterialManager
|
|||
|
material_manager = MaterialManager()
|
|||
|
material = material_manager.get_texture(color)
|
|||
|
|
|||
|
if material and board_obj.data:
|
|||
|
board_obj.data.materials.clear()
|
|||
|
board_obj.data.materials.append(material)
|
|||
|
logger.info(f"✅ 材质 {color} 已关联到板材 {board_obj.name}")
|
|||
|
else:
|
|||
|
logger.warning(f"材质 {color} 未找到或板材数据无效")
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"关联材质失败: {e}")
|
|||
|
|
|||
|
# 5. 启用UV
|
|||
|
self.enable_uv_for_board(board_obj)
|
|||
|
|
|||
|
return board_obj
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
logger.error(f"❌ 添加板材失败: {e}")
|
|||
|
return None
|
|||
|
|
|||
|
|
|||
|
# ==================== 模块实例 ====================
|
|||
|
|
|||
|
# 全局实例
|
|||
|
part_creator = None
|
|||
|
|
|||
|
|
|||
|
def init_part_creator():
|
|||
|
"""初始化部件创建器 - 不再需要suw_impl参数"""
|
|||
|
global part_creator
|
|||
|
part_creator = PartCreator()
|
|||
|
return part_creator
|
|||
|
|
|||
|
|
|||
|
def get_part_creator():
|
|||
|
"""获取全局部件创建器实例"""
|
|||
|
global part_creator
|
|||
|
if part_creator is None:
|
|||
|
part_creator = init_part_creator()
|
|||
|
return part_creator
|
|||
|
|
|||
|
|
|||
|
# 确保PartCreator全局实例正确初始化
|
|||
|
if part_creator is None:
|
|||
|
part_creator = init_part_creator()
|