suwoodblender/blenderpython/suw_core/part_creator11.py

492 lines
19 KiB
Python
Raw Normal View History

2025-07-18 17:09:39 +08:00
#!/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 .material_manager import material_manager
from .memory_manager import memory_manager
import time
import logging
from typing import Dict, Any, Optional, List
# 设置日志
logger = logging.getLogger(__name__)
# 检查Blender可用性
try:
import bpy
BLENDER_AVAILABLE = True
except ImportError:
BLENDER_AVAILABLE = False
# 导入依赖模块
# ==================== 部件创建器类 ====================
class PartCreator:
"""部件创建器 - 负责所有部件相关操作"""
def __init__(self, suw_impl=None):
"""
初始化部件创建器
Args:
suw_impl: SUWImpl实例引用可选
"""
self.suw_impl = suw_impl
# 部件数据存储
self.parts = {} # 按uid存储部件数据
# 创建统计
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]:
"""获取零件信息 - 保持原始方法名和参数"""
uid = data.get("uid")
if uid not in self.parts:
self.parts[uid] = {}
return self.parts[uid]
def c04(self, data: Dict[str, Any]):
"""c04 - 添加部件 - 保持原始方法名"""
return self.create_part(data)
def create_part(self, data: Dict[str, Any]):
"""创建部件 - 保持原始方法名和参数"""
try:
if not BLENDER_AVAILABLE:
logger.warning("Blender不可用无法创建部件")
return None
uid = data.get("uid")
root = data.get("cp")
if not uid or root is None:
logger.error("缺少必要的uid或cp参数")
return None
logger.info(f"开始创建部件: uid={uid}, cp={root}")
# 【数据结构优先策略】先处理数据结构后处理Blender对象
parts = self.get_parts(data)
# 检查数据结构中是否已存在
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}")
# 【关键】数据结构优先清理对应c09的删除顺序
del parts[root]
# 创建部件容器
part_name = f"Part_{root}"
part = bpy.data.objects.new(part_name, None)
bpy.context.scene.collection.objects.link(part)
logger.info(f"✅ 创建Part对象: {part_name}")
# 设置部件属性
part["sw_uid"] = uid
part["sw_cp"] = root
part["sw_typ"] = "part"
# 【对称性】记录创建的对象类型便于c09对称删除
part["sw_created_objects"] = {
"boards": [],
"materials": [],
"uv_layers": [],
"creation_timestamp": time.time()
}
# 存储部件到数据结构(数据结构优先)
parts[root] = part
memory_manager.register_object(part)
logger.info(f"✅ 部件存储到数据结构: uid={uid}, cp={root}")
# 处理finals数据
finals = data.get("finals", [])
logger.info(f"📦 处理 {len(finals)} 个板材数据")
created_boards = 0
created_board_names = []
for i, final_data in enumerate(finals):
try:
board = self.create_board_with_material_and_uv(
part, final_data)
if board:
created_boards += 1
# 【对称性】记录创建的板材便于c09删除
created_board_names.append(board.name)
logger.info(
f"✅ 板材 {i+1}/{len(finals)} 创建成功: {board.name}")
else:
logger.warning(f"⚠️ 板材 {i+1}/{len(finals)} 创建失败")
except Exception as e:
logger.error(f"❌ 创建板材 {i+1}/{len(finals)} 失败: {e}")
# 【对称性】更新创建记录
part["sw_created_objects"]["boards"] = created_board_names
logger.info(f"📊 板材创建统计: {created_boards}/{len(finals)} 成功")
# 更新统计信息
self.creation_stats["parts_created"] += 1
self.creation_stats["boards_created"] += created_boards
# 验证创建结果
if part.name in bpy.data.objects:
logger.info(f"🎉 部件创建完全成功: {part_name}")
return part
else:
logger.error(f"❌ 部件创建验证失败: {part_name} 不在Blender中")
self.creation_stats["creation_errors"] += 1
return None
except Exception as e:
logger.error(f"❌ 创建部件失败: {e}")
self.creation_stats["creation_errors"] += 1
import traceback
logger.error(traceback.format_exc())
return None
def create_board_with_material_and_uv(self, part, data):
"""创建板材并关联材质和启用UV - 保持原始方法名和参数"""
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})")
# 创建精确尺寸的立方体
bpy.ops.mesh.primitive_cube_add(
size=1,
location=(center_x, center_y, center_z)
)
# 安全获取active_object
if hasattr(bpy.context, 'active_object') and bpy.context.active_object:
board = bpy.context.active_object
else:
# 回退方案:查找最近创建的网格对象
board = None
for obj in reversed(list(bpy.data.objects)):
if obj.type == 'MESH':
board = obj
break
if not board:
logger.error("无法获取活动对象")
raise Exception("无法获取活动对象")
# 缩放到精确尺寸
board.scale = (size_x, size_y, size_z)
# 设置属性和父子关系
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}")
# 关联材质 - 使用动态初始化方式
color = data.get("ckey", "mat_default")
if color:
try:
# 【修复】使用与CommandDispatcher相同的初始化方式
from . import material_manager as mm_module
if not mm_module.material_manager:
mm_module.material_manager = mm_module.MaterialManager(
self.suw_impl)
material = mm_module.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 material_error:
logger.error(f"❌ 材质处理失败: {material_error}")
logger.warning(f"材质 {color} 未找到或板材数据无效")
# 启用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:
# 创建默认立方体
bpy.ops.mesh.primitive_cube_add(
size=1,
location=(0, 0, 0)
)
# 安全获取active_object
if hasattr(bpy.context, 'active_object') and bpy.context.active_object:
board = bpy.context.active_object
else:
# 回退方案:查找最近创建的网格对象
board = None
for obj in reversed(list(bpy.data.objects)):
if obj.type == 'MESH':
board = obj
break
if not board:
logger.error("无法获取活动对象")
raise Exception("无法获取活动对象")
# 设置属性和父子关系
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}")
# 关联默认材质 - 使用动态初始化方式
color = data.get("ckey", "mat_default")
try:
from . import material_manager as mm_module
if not mm_module.material_manager:
mm_module.material_manager = mm_module.MaterialManager(
self.suw_impl)
material = mm_module.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}")
except Exception as material_error:
logger.error(f"❌ 默认材质处理失败: {material_error}")
# 启用UV
self.enable_uv_for_board(board)
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:
stats = {
"manager_type": "PartCreator",
"parts_by_uid": {uid: len(parts) for uid, parts in self.parts.items()},
"total_parts": sum(len(parts) for parts in self.parts.values()),
"creation_stats": self.creation_stats.copy(),
"suw_impl_attached": self.suw_impl 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("创建统计信息已重置")
# ==================== 模块实例 ====================
# 全局实例将由SUWImpl初始化时设置
part_creator = None
def init_part_creator(suw_impl):
"""初始化部件创建器"""
global part_creator
part_creator = PartCreator(suw_impl)
return part_creator
# 确保PartCreator全局实例正确初始化
if part_creator is None:
print("🔧 强制初始化PartCreator全局实例...")
part_creator = PartCreator(None) # 【修复】传入None参数