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