suwoodblender/blenderpython/suw_core/part_creator11.py

492 lines
19 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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参数