suwoodblender/blenderpython/suw_core/part_creator.py

613 lines
23 KiB
Python
Raw 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.

#!/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
# 设置日志
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_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}")
# 【修复4】设置部件属性
part["sw_uid"] = uid
part["sw_cp"] = root
part["sw_typ"] = "part"
# 【修复5】存储部件到数据结构
parts[root] = part
logger.info(f"✅ 部件存储到数据结构: 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} 不在Blender中")
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 []
# ==================== 模块实例 ====================
# 全局实例
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()