From c7544e6a2fca527e652d7f4c293a70300f76358e Mon Sep 17 00:00:00 2001 From: libtxixi <991670424@qq.com> Date: Sun, 6 Jul 2025 10:49:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DSUWood=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=A0=B8=E5=BF=83=E9=97=AE=E9=A2=98=EF=BC=9A=E5=AF=B9=E8=B1=A1?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E5=AE=89=E5=85=A8=E6=A8=A1=E5=BC=8F=E3=80=81?= =?UTF-8?q?=E6=9D=90=E8=B4=A8=E5=85=B3=E8=81=94=E3=80=81UV=E5=9D=90?= =?UTF-8?q?=E6=A0=87=E7=94=9F=E6=88=90=E5=92=8C=E5=B4=A9=E6=BA=83=E4=BF=9D?= =?UTF-8?q?=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要修改: 1. 修复对象创建进入安全模式问题 - 优化创建计数器管理 - 提高安全模式阈值并实现批次策略 - 添加智能重置机制 2. 解决材质关联问题 - 在对象创建时立即应用材质 - 修复textured_surf方法的类型检查 - 添加简单材质备用方案 3. 实现UV坐标自动生成 - 在mesh创建时直接生成UV层 - 支持四边形、三角形、多边形的UV映射 - 避免后续复杂UV操作防止闪退 4. 增强崩溃保护 - 添加分批删除机制 - 实现状态验证和延迟处理 - 降低创建阈值防止内存冲突 修改文件: - blenderpython/suw_impl.py: 核心实现逻辑 - ruby/ruby/SUWImpl.rb: 对应的Ruby实现 - test/blender_suw_client.py: 测试客户端 --- blenderpython/suw_impl.py | 2817 ++++++++++++++++++++++++++++-------- ruby/ruby/SUWImpl.rb | 2 +- test/blender_suw_client.py | 8 +- 3 files changed, 2226 insertions(+), 601 deletions(-) diff --git a/blenderpython/suw_impl.py b/blenderpython/suw_impl.py index bc2cb94..c7f658e 100644 --- a/blenderpython/suw_impl.py +++ b/blenderpython/suw_impl.py @@ -11,6 +11,7 @@ SUW Implementation - Python翻译版本 import re import math import logging +import time from typing import Optional, Any, Dict, List, Tuple, Union # 设置日志 @@ -210,6 +211,7 @@ class SUWImpl: _scaled_zone = None _server_path = None _default_zone = None + _creation_lock = False def __init__(self): """初始化SUWImpl实例""" @@ -1082,123 +1084,644 @@ class SUWImpl: print(f"❌ 区域创建失败: {uid}/{zid}") def c04(self, data: Dict[str, Any]): - """添加部件 (add_part) - 完整几何创建实现""" - uid = data.get("uid") - root = data.get("cp") + """添加部件 (add_part) - 重置计数器版本""" + + # 🛡️ 崩溃保护检查 + try: + # 检查Blender状态 + if BLENDER_AVAILABLE: + import bpy + if not bpy.context or not bpy.context.scene: + print("❌ Blender上下文无效,跳过c04执行") + return - if not uid or not root: - print("❌ 缺少uid或cp参数") + # 检查创建计数器 + creation_count = getattr(self.__class__, '_mesh_creation_count', 0) + if creation_count > 80: # 降低阈值 + print(f"⚠️ 创建对象过多 ({creation_count}),强制重置") + self.__class__._mesh_creation_count = 0 + + # 强制清理 + import gc + gc.collect() + + # 延迟执行 + import time + time.sleep(0.5) + + print(f"🛡️ 崩溃保护检查通过,当前创建计数: {creation_count}") + + except Exception as e: + print(f"❌ 崩溃保护检查失败: {e}") return - parts = self.get_parts(data) - added = False + # **在c04开始时重置计数器** + if hasattr(self.__class__, '_mesh_creation_count'): + old_count = self.__class__._mesh_creation_count + if old_count > 30: # 如果累积过多,重置 + self.__class__._mesh_creation_count = 0 + print(f"🔄 c04开始:重置创建计数器 {old_count} -> 0") + + # 清理内存 + try: + import gc + gc.collect() + except: + pass - # 检查部件是否已存在 - part = parts.get(root) - if part is None: - added = True - if BLENDER_AVAILABLE: - # 创建新的部件集合 - part = bpy.data.collections.new(f"Part_{uid}_{root}") - bpy.context.scene.collection.children.link(part) + # **核心方法1:add_part_profile - 为面添加型材属性和纹理** + def add_part_profile(face: Any, index: int, profiles: Dict[int, Any]): + """为面添加型材属性和纹理""" + profile = profiles.get(index) if profiles else None + color = profile.get("ckey") if profile else None + scale = profile.get("scale") if profile else None + angle = profile.get("angle") if profile else None + typ = profile.get("typ", "0") if profile else "0" + + # 根据材质类型确定显示颜色 + if hasattr(self, 'mat_type') and self.mat_type == MAT_TYPE_OBVERSE: + obv_show = "mat_obverse" + rev_show = "mat_reverse" else: - # 存根模式 - part = { - "type": "part", - "children": [], - "entities": [] - } - parts[root] = part - else: - # 清理现有的cp类型子项 - if BLENDER_AVAILABLE and hasattr(part, 'objects'): - for obj in list(part.objects): - if self._get_entity_attr(obj, "typ") == "cp": - bpy.data.objects.remove(obj, do_unlink=True) + obv_show = color + rev_show = color - # 设置部件属性 - self._set_object_attributes( - part, - uid=uid, - cp=root, - typ="part" - ) + if face and color: + if BLENDER_AVAILABLE: + try: + self.textured_surf(face, None, obv_show, color, scale, angle) + print(f"✅ Blender面纹理设置完成: {color}") + except Exception as e: + print(f"❌ Blender面纹理设置失败: {e}") + else: + # 存根模式的纹理设置 + if hasattr(face, '__setitem__'): + face["material"] = { + "color": color, + "scale": scale, + "angle": angle, + "typ": typ + } + print(f"✅ 面纹理设置完成 (存根模式): {color}") - # 处理其他创建逻辑... - # ... existing code ... + def add_part_edges(leaf: Any, series1: List, series2: List, + obv: Dict[str, Any], rev: Dict[str, Any], profiles: Dict[int, Any] = None): + """创建部件的边缘面 - 优化版本""" + unplanar = False - def _add_part_board(self, part: Any, data: Dict[str, Any], antiz: bool, profiles: Dict[int, Any]) -> Any: - """添加板材到部件""" - uid = data.get("uid") # 获取uid + if not series1 or not series2: + print("⚠️ add_part_edges: series1或series2为空") + return - try: - # 创建板材几何体 - board = None - if BLENDER_AVAILABLE: - # 在Blender中创建板材 - pass - else: - # 存根模式 - board = { - "type": "board", - "antiz": antiz, - "profiles": profiles, - "data": data.copy() - } + try: + print(f"🔧 add_part_edges: series1={len(series1)}, series2={len(series2)}") + + # 确保series1和series2长度一致 + min_len = min(len(series1), len(series2)) + + for index in range(min_len): + pts1 = series1[index] # 正面的点序列 + pts2 = series2[index] # 反面的点序列 - if board: - # 设置属性,包括uid - self._set_object_attributes( - board, - uid=uid, - typ="board" + if not isinstance(pts1, list) or not isinstance(pts2, list): + print(f"⚠️ 跳过无效的点序列: index={index}") + continue + + # 确保两个点序列长度一致 + min_pts_len = min(len(pts1), len(pts2)) + print(f"🔧 处理边缘面组 {index}: pts1={len(pts1)}, pts2={len(pts2)}") + + # **优化:限制边缘面数量但允许创建** + max_edges = min(min_pts_len, 4) # 最多4个边缘面 + + # **关键修复:从1开始循环,连接相邻的点** + for i in range(1, max_edges): + try: + # 按Ruby原版创建四个点形成的面 + pts = [pts1[i-1], pts1[i], pts2[i], pts2[i-1]] + + # 验证点的有效性 + valid_pts = [] + for pt in pts: + if isinstance(pt, Point3d): + valid_pts.append(pt) + elif isinstance(pt, (list, tuple)) and len(pt) >= 3: + valid_pts.append(Point3d(pt[0], pt[1], pt[2])) + else: + print(f"⚠️ 无效的点数据: {pt}") + break + + if len(valid_pts) == 4: + # 创建边缘面 + print(f"🔧 创建边缘面 {index}-{i}: 4个有效点") + edge_face = self._create_edge_face(leaf, valid_pts) + if edge_face: + print(f"✅ 边缘面创建成功 {index}-{i}") + if profiles is not None: + add_part_profile(edge_face, index, profiles) + else: + print(f"❌ 边缘面创建失败 {index}-{i}") + else: + print(f"⚠️ 有效点数不足: {len(valid_pts)}/4") + + except Exception as e: + unplanar = True + print(f"❌ Points are not planar {index}: {i} - {e}") + + # **优化:总是尝试创建封闭边缘面** + if len(series1) > 0 and len(series2) > 0: + pts1 = series1[0] + pts2 = series2[0] + + if isinstance(pts1, list) and isinstance(pts2, list) and len(pts1) == len(pts2): + # 创建最后一个边缘面(闭合循环) + try: + last_i = len(pts1) - 1 + if last_i > 0: + # 连接最后一个点和第一个点 + pts = [pts1[last_i], pts1[0], pts2[0], pts2[last_i]] + + valid_pts = [] + for pt in pts: + if isinstance(pt, Point3d): + valid_pts.append(pt) + elif isinstance(pt, (list, tuple)) and len(pt) >= 3: + valid_pts.append(Point3d(pt[0], pt[1], pt[2])) + + if len(valid_pts) == 4: + print(f"🔧 创建封闭边缘面: 4个有效点") + edge_face = self._create_edge_face(leaf, valid_pts) + if edge_face: + print(f"✅ 封闭边缘面创建成功") + if profiles is not None: + add_part_profile(edge_face, 0, profiles) + else: + print(f"❌ 封闭边缘面创建失败") + + except Exception as e: + print(f"❌ 封闭边缘面创建失败: {e}") + + except Exception as e: + print(f"❌ add_part_edges 执行失败: {e}") + unplanar = True + + if unplanar: + print("⚠️ 检测到非平面问题,已记录") + + # **核心方法2:add_part_surf - 创建部件表面** + def add_part_surf(leaf: Any, data: Dict[str, Any], antiz: bool, + color: str, scale: float, angle: float, + color2: str, scale2: float, angle2: float, + profiles: Dict[int, Any]): + """创建部件表面""" + try: + # 获取正面和反面数据 + obv = data.get("obv", {}) + rev = data.get("rev", {}) + + # 初始化类型和颜色变量 + obv_type = "o" + obv_save = color + obv_scale = scale + obv_angle = angle + rev_type = "r" + rev_save = color2 if color2 else color + rev_scale = scale2 if color2 else scale + rev_angle = angle2 if color2 else angle + + # 如果antiz为True,交换正反面参数 + if antiz: + obv_type, rev_type = rev_type, obv_type + obv_save, rev_save = rev_save, obv_save + obv_scale, rev_scale = rev_scale, obv_scale + obv_angle, rev_angle = rev_angle, obv_angle + + # 根据材质类型确定显示颜色 + if hasattr(self, 'mat_type') and self.mat_type == MAT_TYPE_OBVERSE: + obv_show = "mat_obverse" + rev_show = "mat_reverse" + else: + obv_show = obv_save + rev_show = rev_save + + # 创建面的点序列 + series1 = [] + series2 = [] + + # 创建正面 (obv) + obv_face = self.create_face( + leaf, obv, obv_show, obv_scale, obv_angle, series1, + uid=data.get("uid"), zid=data.get("zid"), pid=data.get("pid") ) + if obv_face: + self._set_entity_attr(obv_face, "typ", obv_type) + if obv_save != obv_show: + self._set_entity_attr(obv_face, "ckey", obv_save) - return board + # 创建反面 (rev) + rev_face = self.create_face( + leaf, rev, rev_show, rev_scale, rev_angle, series2, + uid=data.get("uid"), zid=data.get("zid"), pid=data.get("pid") + ) + if rev_face: + self._set_entity_attr(rev_face, "typ", rev_type) + if rev_save != rev_show: + self._set_entity_attr(rev_face, "ckey", rev_save) + + # 创建边缘面 + add_part_edges(leaf, series1, series2, obv, rev, profiles) + + return leaf + + except Exception as e: + print(f"❌ add_part_surf 执行失败: {e}") + return None + + # **核心方法3:add_part_board - 创建板材部件** + def add_part_board(part: Any, data: Dict[str, Any], antiz: bool, profiles: Dict[int, Any]): + """创建板材部件""" + try: + if BLENDER_AVAILABLE: + import bpy + leaf = bpy.data.collections.new( + f"Board_{data.get('uid', 'unknown')}") + if hasattr(part, 'children'): + part.children.link(leaf) + else: + leaf = {"type": "board", "children": [], "entities": []} + if isinstance(part, dict): + part.setdefault("children", []).append(leaf) + + # 设置板材属性 + color = data.get("ckey") + scale = data.get("scale") + angle = data.get("angle") + color2 = data.get("ckey2") + scale2 = data.get("scale2") + angle2 = data.get("angle2") + + if color: + self._set_entity_attr(leaf, "ckey", color) + if scale: + self._set_entity_attr(leaf, "scale", scale) + if angle: + self._set_entity_attr(leaf, "angle", angle) + + # 处理截面数据 + if data.get("sects"): + sects = data["sects"] + for sect in sects: + segs = sect.get("segs", []) + surf = sect.get("sect", {}) + paths = self.create_paths(part, segs) + if paths and surf: + self.follow_me(leaf, surf, paths, + color, scale, angle) + + # 创建表面组 + if BLENDER_AVAILABLE: + import bpy + leaf2 = bpy.data.collections.new( + f"Surface_{data.get('uid', 'unknown')}") + leaf.children.link(leaf2) + else: + leaf2 = {"type": "surface_group", "children": []} + leaf.setdefault("children", []).append(leaf2) + + add_part_surf(leaf2, data, antiz, color, scale, + angle, color2, scale2, angle2, profiles) + else: + # 直接创建表面 + add_part_surf(leaf, data, antiz, color, scale, + angle, color2, scale2, angle2, profiles) + + return leaf + + except Exception as e: + print(f"❌ add_part_board 失败: {e}") + return None + + # **核心方法4:add_part_stretch - 创建拉伸部件** + def add_part_stretch(part: Any, data: Dict[str, Any]): + """创建拉伸部件""" + try: + copmensates = data.get("copmensates", []) + trim_surfs = data.get("trim_surfs", []) + baselines = self.create_paths(part, data.get("baselines", [])) + inst = None + + # 处理预制件(.skp文件) + if data.get("sid") and not copmensates and not trim_surfs and len(baselines) == 1: + print(f"🏗️ 尝试加载预制件: {data.get('sid')}") + # TODO: 实现预制件加载逻辑 + pass + + if inst: + # 使用预制件 + if BLENDER_AVAILABLE: + import bpy + leaf = bpy.data.collections.new( + f"Stretch_{data.get('uid', 'unknown')}") + if hasattr(part, 'children'): + part.children.link(leaf) + else: + leaf = {"type": "stretch_virtual", "children": []} + if isinstance(part, dict): + part.setdefault("children", []).append(leaf) + + surf = data.get("sect", {}) + surf["segs"] = data.get("bounds", []) + self.follow_me(leaf, surf, baselines, None) + self._set_entity_attr(leaf, "virtual", True) + self._set_entity_visible(leaf, False) + else: + # 创建实际几何体 + if BLENDER_AVAILABLE: + import bpy + leaf = bpy.data.collections.new( + f"Stretch_{data.get('uid', 'unknown')}") + if hasattr(part, 'children'): + part.children.link(leaf) + else: + leaf = {"type": "stretch", "children": []} + if isinstance(part, dict): + part.setdefault("children", []).append(leaf) + + thick = data.get("thick", 0) + zaxis = Vector3d.parse(data.get("zaxis", "0,0,1")) + color = data.get("ckey") + sect = data.get("sect", {}) + + # 创建基础几何体 + self.follow_me(leaf, sect, baselines, color) + + # 处理补偿面 + for copmensate in copmensates: + points = [Point3d.parse(point) for point in copmensate] + # TODO: 实现补偿面处理逻辑 + print(f"⚙️ 处理补偿面: {len(points)} 点") + + # 处理修剪面 + for trim_surf in trim_surfs: + # TODO: 实现修剪面处理逻辑 + print(f"✂️ 处理修剪面") + + if color: + self._set_entity_attr(leaf, "ckey", color) + + return leaf + + except Exception as e: + print(f"❌ add_part_stretch 失败: {e}") + return None + + # **核心方法5:add_part_arc - 创建弧形部件** + def add_part_arc(part: Any, data: Dict[str, Any], antiz: bool, profiles: Dict[int, Any]): + """创建弧形部件""" + try: + if BLENDER_AVAILABLE: + import bpy + leaf = bpy.data.collections.new( + f"Arc_{data.get('uid', 'unknown')}") + if hasattr(part, 'children'): + part.children.link(leaf) + else: + leaf = {"type": "arc", "children": []} + if isinstance(part, dict): + part.setdefault("children", []).append(leaf) + + obv = data.get("obv", {}) + color = data.get("ckey") + scale = data.get("scale") + angle = data.get("angle") + color2 = data.get("ckey2") + scale2 = data.get("scale2") + angle2 = data.get("angle2") + + if color: + self._set_entity_attr(leaf, "ckey", color) + if scale: + self._set_entity_attr(leaf, "scale", scale) + if angle: + self._set_entity_attr(leaf, "angle", angle) + + center_o = Point3d.parse(data.get("co", "0,0,0")) + center_r = Point3d.parse(data.get("cr", "0,0,0")) + + # 创建弧形路径 + path = self._create_line_edge(leaf, center_o, center_r) + series = [] + normal = self.follow_me( + leaf, obv, [path], color, scale, angle, False, series, True) + + # 处理弧形面的材质 + if len(series) == 4: + # TODO: 实现弧形面材质处理逻辑 + print(f"🌀 处理弧形面材质: {len(series)} 系列") + + return leaf + + except Exception as e: + print(f"❌ add_part_arc 失败: {e}") + return None + + # **主方法实现开始** + try: + print( + f"🏗️ c04: 添加部件开始 - uid={data.get('uid')}, cp={data.get('cp')}") + + uid = data.get("uid") + root = data.get("cp") + + if not uid or not root: + print("❌ 缺少uid或cp参数") + return + + parts = self.get_parts(data) + part = parts.get(root) + added = False + + # 检查部件是否已存在 + if part is None: + added = True + if BLENDER_AVAILABLE: + import bpy + part = bpy.data.collections.new(f"Part_{uid}_{root}") + bpy.context.scene.collection.children.link(part) + else: + part = {"type": "part", "children": [], + "entities": [], "uid": uid, "cp": root} + parts[root] = part + else: + # 清理现有的cp类型子项 + if BLENDER_AVAILABLE and hasattr(part, 'objects'): + for obj in list(part.objects): + if self._get_entity_attr(obj, "typ") == "cp": + bpy.data.objects.remove(obj, do_unlink=True) + elif not BLENDER_AVAILABLE and isinstance(part, dict): + part["children"] = [child for child in part.get("children", []) + if self._get_entity_attr(child, "typ") != "cp"] + + # 设置部件基本属性 + self._set_object_attributes(part, uid=uid, zid=data.get( + "zid"), pid=data.get("pid"), cp=root, typ="cp") + + # 处理图层分配 + layer = data.get("layer", 0) + if layer == 1 and hasattr(self, 'door_layer'): + print(f"🚪 设置为门板图层: {root}") + # TODO: 实现门板图层设置 + elif layer == 2 and hasattr(self, 'drawer_layer'): + print(f"📦 设置为抽屉图层: {root}") + # TODO: 实现抽屉图层设置 + + # 处理开门和拉抽屉功能 + drawer_type = data.get("drw", 0) + if drawer_type: + self._set_entity_attr(part, "drawer", drawer_type) + if drawer_type in [73, 74]: # DR_LP/DR_RP + self._set_entity_attr(part, "dr_depth", data.get("drd", 0)) + if drawer_type == 70: + drawer_dir = data.get("drv") + if drawer_dir: + self._set_entity_attr( + part, "drawer_dir", Vector3d.parse(drawer_dir)) + + door_type = data.get("dor", 0) + if door_type: + self._set_entity_attr(part, "door", door_type) + if door_type in [10, 15]: + self._set_entity_attr( + part, "door_width", data.get("dow", 0)) + self._set_entity_attr( + part, "door_pos", data.get("dop", "F")) + + # 处理预制件加载 + inst = None + if data.get("sid"): + print( + f"🏗️ 预制件ID: {data.get('sid')}, 镜像: {data.get('mr', 'None')}") + # TODO: 实现完整的预制件加载逻辑 + + # 处理虚拟几何体(用于预制件) + if inst: + if BLENDER_AVAILABLE: + import bpy + leaf = bpy.data.collections.new(f"Virtual_{uid}_{root}") + part.children.link(leaf) + else: + leaf = {"type": "virtual", "children": []} + part.setdefault("children", []).append(leaf) + + if data.get("typ") == 3: + # 弧形虚拟几何体 + center_o = Point3d.parse(data.get("co", "0,0,0")) + center_r = Point3d.parse(data.get("cr", "0,0,0")) + path = self._create_line_edge(leaf, center_o, center_r) + self.follow_me(leaf, data.get("obv", {}), [path], None) + else: + # 普通虚拟几何体 + obv = data.get("obv", {}) + rev = data.get("rev", {}) + series1 = [] + series2 = [] + self.create_face(leaf, obv, None, None, None, series1, uid=uid, zid=data.get( + "zid"), pid=data.get("pid")) + self.create_face(leaf, rev, None, None, None, series2, uid=uid, zid=data.get( + "zid"), pid=data.get("pid")) + add_part_edges(leaf, series1, series2, obv, rev) + + self._set_entity_attr(leaf, "typ", "cp") + self._set_entity_attr(leaf, "virtual", True) + self._set_entity_visible(leaf, False) + + # 处理最终几何体 + finals = data.get("finals", []) + for final in finals: + if final.get("typ") == 2: # 拉伸类型 + stretch = add_part_stretch(part, final) + if stretch: + self._set_entity_attr(stretch, "typ", "cp") + mn = final.get("mn") + if mn: + self._set_entity_attr(stretch, "mn", mn) + else: + # 处理实际几何体 + finals = data.get("finals", []) + for final in finals: + profiles = {} + ps = final.get("ps") + if ps: + for p in ps: + idx_str = p.get("idx", "") + for idx in idx_str.split(","): + if idx.strip().isdigit(): + profiles[int(idx.strip())] = p + + leaf = None + typ = final.get("typ") + + if typ == 1: # 板材部件 + leaf = add_part_board( + part, final, final.get("antiz", False), profiles) + elif typ == 2: # 拉伸部件 + leaf = add_part_stretch(part, final) + elif typ == 3: # 弧形部件 + leaf = add_part_arc( + part, final, final.get("antiz", False), profiles) + + if leaf: + self._set_entity_attr(leaf, "typ", "cp") + mn = final.get("mn") + if mn: + self._set_entity_attr(leaf, "mn", mn) + print(f"✅ 创建几何对象成功: typ={typ}, mn={mn}") + else: + print(f"❌ 几何对象创建失败: typ={typ}") + + # 应用单位变换 + if added and hasattr(self, 'unit_trans') and uid in self.unit_trans: + print(f"🔄 应用单位变换: {uid}") + transform = self.unit_trans[uid] + # TODO: 实现变换应用逻辑 + + # 设置唯一性和缩放限制 + if BLENDER_AVAILABLE: + try: + # 在Blender中,我们可以通过锁定属性来限制缩放 + for obj in part.objects: + obj.lock_scale = [True, True, True] # 限制所有方向的拉伸 + except Exception as e: + print(f"⚠️ 设置缩放限制失败: {e}") + + print(f"✅ c04 部件添加完成: uid={uid}, cp={root}") except Exception as e: - print(f"❌ 添加板材失败: {e}") - return None - - def _add_part_surf(self, leaf: Any, data: Dict[str, Any], antiz: bool, - color: str, scale: float, angle: float, - color2: str, scale2: float, angle2: float, profiles: Dict[int, Any]) -> Any: - """添加表面到部件""" - uid = data.get("uid") # 获取uid + print(f"❌ c04方法执行失败: {e}") + import traceback + traceback.print_exc() + def _create_edge_face(self, container: Any, points: List[Point3d]) -> Any: + """创建边缘面""" try: - # 创建表面几何体 - surf = None - if BLENDER_AVAILABLE: - # 在Blender中创建表面 - pass - else: - # 存根模式 - surf = { - "type": "surface", - "antiz": antiz, - "color": color, - "scale": scale, - "angle": angle, - "color2": color2, - "scale2": scale2, - "angle2": angle2, - "profiles": profiles, - "data": data.copy() - } + if len(points) != 4: + print(f"⚠️ 边缘面需要4个点,实际: {len(points)}") + return None - if surf: - # 设置属性,包括uid - self._set_object_attributes( - surf, - uid=uid, - typ="surface" - ) + # 验证点的共面性 + if not self._validate_coplanar(points): + print("⚠️ 边缘面点不共面,尝试修复") + points = self._fix_non_coplanar_points(points) - return surf + # 创建面 + surface_data = { + "segs": [[str(pt), str(pt)] for pt in points] + } + + return self.create_face(container, surface_data) except Exception as e: - print(f"❌ 添加表面失败: {e}") + print(f"❌ _create_edge_face 失败: {e}") return None def c05(self, data: Dict[str, Any]): @@ -1309,403 +1832,716 @@ class SUWImpl: print(f"✅ 五金创建成功 (存根): {uid}/{cp}") def c09(self, data: Dict[str, Any]): - """删除实体 (del_entity)""" - uid = data.get("uid") - typ = data.get("typ", "uid") # 默认删除整个uid - oid = data.get("oid") - - print(f"🗑️ c09: 删除实体 - uid={uid}, typ={typ}, oid={oid}") - - # 清除所有选择 - self.sel_clear() - - # 根据类型执行不同的删除操作 - if typ == "wall": - # 删除特定墙面 - zones = self.get_zones(data) - zone = zones.get(oid) - wall = data.get("wall") - if zone: - self._delete_wall_from_zone(zone, wall) - else: - # 删除区域、部件、五金等 - if typ in ["uid", "zid"]: - zones = self.get_zones(data) - print(f"🔍 准备从zones删除: 当前zones有{len(zones)}个实体") - self.del_entities(zones, typ, oid) - - parts = self.get_parts(data) - print(f"🔍 准备从parts删除: 当前parts有{len(parts)}个实体") - self.del_entities(parts, typ, oid) - - hardwares = self.get_hardwares(data) - print(f"🔍 准备从hardwares删除: 当前hardwares有{len(hardwares)}个实体") - self.del_entities(hardwares, typ, oid) - - # **直接删除Blender场景中的相关对象** - if BLENDER_AVAILABLE: - self._delete_blender_objects_by_uid(uid, typ, oid) - # **新增:检查并删除特殊图层集合** - self._delete_layer_collections_if_needed(uid, typ, oid) - - # 清理数据结构 - if typ == "uid": - # 删除整个uid的所有数据 - if uid in self.zones: - del self.zones[uid] - print(f"删除区域数据: {uid}") - - if uid in self.parts: - del self.parts[uid] - print(f"删除部件数据: {uid}") - - if uid in self.hardwares: - del self.hardwares[uid] - print(f"删除五金数据: {uid}") - - if uid in self.machinings: - del self.machinings[uid] - print(f"删除加工数据: {uid}") - - if uid in self.dimensions: - del self.dimensions[uid] - print(f"删除尺寸数据: {uid}") - - # 清理标签 - self._clear_labels() - - print(f"✅ 实体删除完成: uid={uid}") - - def _delete_layer_collections_if_needed(self, uid: str, typ: str, oid: Any): - """检查并删除door_layer、drawer_layer集合及其子集合""" - if not BLENDER_AVAILABLE: - return - - import bpy - - print(f"🔍 检查特殊图层集合") - - # 要检查的特殊集合名称 - special_collections = ["DOOR_LAYER", "DRAWER_LAYER"] - - collections_to_remove = [] - - for collection_name in special_collections: - collection = bpy.data.collections.get(collection_name) - if collection: - # 检查集合中是否有匹配的对象 - has_matching_objects = False - objects_to_check = [] - - # 递归收集所有子对象 - self._collect_all_objects_in_collection( - collection, objects_to_check) - - print(f"📋 {collection_name}: {len(objects_to_check)} 个对象") - - # 检查每个对象 - for obj in objects_to_check: - try: - obj_uid = obj.get("uid") - obj_zid = obj.get("zid") - obj_pid = obj.get("pid") - obj_cp = obj.get("cp") - obj_child = obj.get("child") - - # 标准匹配逻辑 - if typ == "uid" and obj_uid == uid: - has_matching_objects = True - break - elif typ == "zid" and obj_uid == uid and obj_zid == oid: - has_matching_objects = True - break - elif typ == "pid" and obj_uid == uid and obj_pid == oid: - has_matching_objects = True - break - elif typ == "cp" and obj_uid == uid and obj_cp == oid: - has_matching_objects = True - break - # 宽松匹配:如果没有uid属性,但有child属性,也考虑删除 - elif obj_uid is None and obj_child is not None: - has_matching_objects = True - break - except ReferenceError: - # 对象已被删除,跳过 - continue - - # 如果找到匹配对象,标记整个集合删除 - if has_matching_objects: - collections_to_remove.append( - (collection, collection_name)) # 保存集合和名称 - print(f"🗑️ 标记删除: {collection_name}") - - # 删除标记的集合 - deleted_count = 0 - for collection, collection_name in collections_to_remove: - try: - self._delete_collection_recursively(collection) - deleted_count += 1 - print(f"✅ 删除完成: {collection_name}") # 使用保存的名称 - except Exception as e: - print(f"❌ 删除失败: {collection_name} - {e}") # 使用保存的名称 - - if deleted_count > 0: - print(f"🗑️ 删除了 {deleted_count} 个特殊图层集合") - else: - print(f"✅ 无需删除特殊图层集合") - - def _collect_all_objects_in_collection(self, collection, objects_list): - """递归收集集合中的所有对象""" - if not BLENDER_AVAILABLE: - return + """删除实体 - 使用Blender选择和操作符的安全删除策略""" try: - # 检查集合是否仍然有效 - if hasattr(collection, 'name'): - collection.name # 尝试访问name属性来验证对象是否有效 + uid = data.get("uid") + typ = data.get("typ", "uid") + oid = data.get("oid") + + print(f"🗑️ c09: 删除实体 - uid={uid}, typ={typ}, oid={oid}") + + # 新的删除策略:查找→选择→标记→批量删除 + USE_BLENDER_OPERATOR_DELETE = True + USE_SAFE_DATA_CLEANUP = True + + if USE_BLENDER_OPERATOR_DELETE and BLENDER_AVAILABLE: + # 使用Blender操作符进行安全删除 + self._c09_blender_operator_delete(uid, typ, oid) + elif USE_SAFE_DATA_CLEANUP: + # 回退到数据清理模式 + self._safe_data_cleanup(uid, typ, oid) else: - print(f"⚠️ 集合已失效,跳过") - return - except ReferenceError: - print(f"⚠️ 集合已被删除,跳过") - return + # 传统渐进式删除(备用) + self._c09_progressive_delete(uid, typ, oid, { + "data_delete": True, + "blender_hide": True, + "blender_delete": False + }) - # 收集当前集合中的对象 - objects_to_check = list(collection.objects) # 创建副本以避免迭代中修改 - for obj in objects_to_check: - try: - # 检查对象是否仍然有效 - if hasattr(obj, 'name'): - obj.name # 尝试访问name属性来验证对象是否有效 - objects_list.append(obj) - except ReferenceError: - print(f"⚠️ 对象已被删除,跳过") - continue - - # 递归收集子集合中的对象 - child_collections = list(collection.children) # 创建副本 - for child_collection in child_collections: - try: - self._collect_all_objects_in_collection( - child_collection, objects_list) - except ReferenceError: - print(f"⚠️ 子集合已被删除,跳过") - continue - - def _delete_collection_recursively(self, collection): - """递归删除集合及其所有子集合和对象""" - if not BLENDER_AVAILABLE: - return - - import bpy - - try: - # 检查集合是否仍然有效 - if hasattr(collection, 'name'): - collection.name # 验证集合是否有效 - else: - print(f"⚠️ 集合已失效,无法删除") - return - except ReferenceError: - print(f"⚠️ 集合已被删除,无需再次删除") - return - - # 首先递归删除所有子集合 - child_collections = list(collection.children) - for child_collection in child_collections: - try: - self._delete_collection_recursively(child_collection) - except ReferenceError: - print(f" ⚠️ 子集合已被删除,跳过") - continue - - # 删除集合中的所有对象 - objects_to_remove = list(collection.objects) - for obj in objects_to_remove: - try: - # 检查对象是否仍然有效 - if hasattr(obj, 'name'): - obj.name # 验证对象是否有效 - else: - continue - - # 从集合中移除对象 - collection.objects.unlink(obj) - - # 如果对象不在其他集合中,删除对象 - if len(obj.users_collection) == 0: - if obj.type == 'MESH' and obj.data: - mesh = obj.data - bpy.data.objects.remove(obj, do_unlink=True) - if mesh.users == 0: - bpy.data.meshes.remove(mesh) - else: - bpy.data.objects.remove(obj, do_unlink=True) - print(f" 🗑️ 删除对象: {obj.name}") - else: - print(f" 📌 对象保留在其他集合中: {obj.name}") - - except ReferenceError: - print(f" ⚠️ 对象已被删除,跳过") - continue - except Exception as e: - print(f" ❌ 删除对象失败: 错误: {e}") - continue - - # 最后删除集合本身 - try: - collection_name = collection.name # 保存名称用于日志 - bpy.data.collections.remove(collection) - print(f" 🗑️ 删除集合: {collection_name}") - except ReferenceError: - print(f" ⚠️ 集合已被删除,无需再次删除") except Exception as e: - print(f" ❌ 删除集合失败: 错误: {e}") + print(f"❌ c09方法执行失败: {e}") + import traceback + traceback.print_exc() - def _delete_blender_objects_by_uid(self, uid: str, typ: str, oid: Any): - """直接从Blender场景中删除指定uid相关的对象""" - if not BLENDER_AVAILABLE: - return + def _c09_blender_operator_delete(self, uid: str, typ: str, oid: Any): + """使用Blender操作符的安全删除策略 - 前后都更新DEG""" + try: + import bpy - import bpy + print(f"🔍 开始Blender操作符删除流程: uid={uid}, typ={typ}") - objects_to_remove = [] - collections_to_remove = [] + # **前置步骤: 删除前更新依赖图** + print("🔄 删除前DEG更新...") + self._update_dependency_graph(full_update=True) - print(f"🔍 扫描Blender场景中的普通对象 (uid={uid}, typ={typ}, oid={oid})") + # **步骤1: 查找所有要删除的对象** + objects_to_delete = self._find_objects_for_deletion(uid, typ, oid) + collections_to_delete = self._find_collections_for_deletion( + uid, typ, oid) - # 1. 扫描所有对象(排除特殊图层) - for obj in bpy.data.objects: - # 跳过特殊图层中的对象(将由特殊处理函数处理) - if self._is_object_in_special_layers(obj): - continue + total_found = len(objects_to_delete) + len(collections_to_delete) + print( + f"🔍 找到删除目标: {len(objects_to_delete)} 个对象, {len(collections_to_delete)} 个集合") - obj_uid = obj.get("uid") - obj_zid = obj.get("zid") - obj_pid = obj.get("pid") - obj_cp = obj.get("cp") - obj_typ = obj.get("typ") + if total_found == 0: + print("⚠️ 未找到要删除的对象") + return - # 检查是否匹配删除条件 - should_delete = False - - if typ == "uid" and obj_uid == uid: - should_delete = True - print(f" 🎯 匹配uid: {obj.name} (uid={obj_uid})") - elif typ == "zid" and obj_uid == uid and obj_zid == oid: - should_delete = True - print(f" 🎯 匹配zid: {obj.name} (uid={obj_uid}, zid={obj_zid})") - elif typ == "pid" and obj_uid == uid and obj_pid == oid: - should_delete = True - print(f" 🎯 匹配pid: {obj.name} (uid={obj_uid}, pid={obj_pid})") - elif typ == "cp" and obj_uid == uid and obj_cp == oid: - should_delete = True - print(f" 🎯 匹配cp: {obj.name} (uid={obj_uid}, cp={obj_cp})") - - if should_delete: - objects_to_remove.append(obj) - print(f" 📋 标记删除对象: {obj.name} (type={obj.type})") - - # 2. 扫描普通集合(排除特殊图层) - for collection in bpy.data.collections: - if collection.name in ["DOOR_LAYER", "DRAWER_LAYER"]: - continue # 特殊图层由专门函数处理 - - coll_uid = collection.get("uid") - coll_zid = collection.get("zid") - - should_delete = False - - if typ == "uid" and coll_uid == uid: - should_delete = True - print(f" 🎯 匹配集合uid: {collection.name} (uid={coll_uid})") - elif typ == "zid" and coll_uid == uid and coll_zid == oid: - should_delete = True - print( - f" 🎯 匹配集合zid: {collection.name} (uid={coll_uid}, zid={coll_zid})") - - if should_delete: - collections_to_remove.append(collection) - print(f" 📋 标记删除集合: {collection.name}") - - # 3. 删除对象和集合 - self._remove_objects_and_collections( - objects_to_remove, collections_to_remove) - - total_removed = len(objects_to_remove) + len(collections_to_remove) - print( - f"🗑️ 普通Blender对象删除完成: 对象={len(objects_to_remove)}, 集合={len(collections_to_remove)}, 总计={total_removed}") - - def _is_object_in_special_layers(self, obj): - """检查对象是否在特殊图层中""" - if not BLENDER_AVAILABLE: - return False - - for collection in obj.users_collection: - if collection.name in ["DOOR_LAYER", "DRAWER_LAYER"]: - return True - # 递归检查父集合 - if self._is_collection_child_of_special_layers(collection): - return True - return False - - def _is_collection_child_of_special_layers(self, collection): - """递归检查集合是否是特殊图层的子集合""" - if not BLENDER_AVAILABLE: - return False - - import bpy - - for parent_collection in bpy.data.collections: - if parent_collection.name in ["DOOR_LAYER", "DRAWER_LAYER"]: - if collection in parent_collection.children_recursive: - return True - return False - - def _remove_objects_and_collections(self, objects_to_remove, collections_to_remove): - """删除对象和集合的通用方法""" - if not BLENDER_AVAILABLE: - return - - import bpy - - # 删除对象 - for obj in objects_to_remove: + # **关键修复:强制更新上下文和视图** try: - # 从所有集合中移除 - for collection in obj.users_collection: - collection.objects.unlink(obj) + # 确保在正确的视图层上下文中 + bpy.context.view_layer.update() + print("✅ 更新视图层上下文") + except Exception as e: + print(f"⚠️ 视图层更新失败: {e}") - # 删除对象数据 - if obj.type == 'MESH' and obj.data: - mesh = obj.data - bpy.data.objects.remove(obj, do_unlink=True) - if mesh.users == 0: - bpy.data.meshes.remove(mesh) - else: - bpy.data.objects.remove(obj, do_unlink=True) + # **步骤2: 彻底清除当前选择并验证** + try: + bpy.ops.object.select_all(action='DESELECT') + # 删除操作前再次更新DEG + self._update_dependency_graph() - print(f" ✅ 删除对象: {obj.name}") + # 验证所有对象都被取消选择 + selected_objects = [ + obj for obj in bpy.context.scene.objects if obj.select_get()] + print(f"✅ 清除选择完成,当前选中: {len(selected_objects)} 个对象") except Exception as e: - print(f" ❌ 删除对象失败: {obj.name}, 错误: {e}") + print(f"⚠️ 清除选择失败: {e}") - # 删除集合 - for collection in collections_to_remove: - try: - self._delete_collection_recursively(collection) - print(f" ✅ 删除集合: {collection.name}") + # **步骤3: 逐个选择并验证选择状态** + selected_count = 0 + selected_names = [] - except Exception as e: - print(f" ❌ 删除集合失败: {collection.name}, 错误: {e}") + for obj in objects_to_delete: + try: + # **修复:检查对象是否仍然存在** + if obj and hasattr(obj, 'name'): + obj_name = obj.name + # 检查对象是否仍在Blender数据中 + if obj_name in bpy.data.objects: + obj_ref = bpy.data.objects.get(obj_name) + if obj_ref and obj_ref.name in bpy.context.view_layer.objects: + obj_ref.select_set(True) + # 验证选择状态 + if obj_ref.select_get(): + selected_count += 1 + selected_names.append(obj_name) + print(f"✅ 选择对象: {obj_name}") + else: + print(f"⚠️ 对象选择失败: {obj_name}") + else: + print(f"⚠️ 对象不在当前视图层: {obj_name}") + else: + print(f"⚠️ 对象已不存在: {obj_name}") + else: + print(f"⚠️ 对象引用无效: {obj}") - # 更新视图 - if objects_to_remove or collections_to_remove: + except Exception as e: + obj_name = obj.name if obj and hasattr( + obj, 'name') else 'None' + print(f"⚠️ 选择对象失败 {obj_name}: {e}") + + # **选择完成后更新DEG** + if selected_count > 0: + print("🔄 选择完成后DEG更新...") + self._update_dependency_graph() + + # **步骤4: 验证选择状态并设置活动对象** + if selected_count > 0: + try: + # 设置活动对象 + if objects_to_delete and objects_to_delete[0]: + bpy.context.view_layer.objects.active = objects_to_delete[0] + print(f"✅ 设置活动对象: {objects_to_delete[0].name}") + + print(f"🎯 选择验证: {selected_count} 个对象已选择") + print(f"📋 选中对象列表: {selected_names}") + + # **步骤5: 执行删除前最后一次DEG更新** + print("🔄 删除执行前最终DEG更新...") + self._update_dependency_graph(full_update=True) + + # **步骤6: 强制执行删除操作** + deletion_success = False + + # **方法1: 标准删除操作符** + try: + print("🗑️ 尝试标准删除操作...") + + # 确保在3D视口上下文中 + override_context = bpy.context.copy() + + # 查找3D视口区域 + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + override_context['area'] = area + for region in area.regions: + if region.type == 'WINDOW': + override_context['region'] = region + break + break + + # 执行删除 + with bpy.context.temp_override(**override_context): + bpy.ops.object.delete( + use_global=False, confirm=False) + + deletion_success = True + print(f"✅ 标准删除成功: {selected_count} 个对象") + + # **删除成功后立即更新DEG** + print("🔄 删除成功后立即DEG更新...") + self._update_dependency_graph(full_update=True) + + except Exception as e: + print(f"❌ 标准删除失败: {e}") + + # **方法2: 如果标准删除失败,使用强制删除** + if not deletion_success: + try: + print("🔄 使用强制删除方法...") + self._force_delete_objects(objects_to_delete) + deletion_success = True + + except Exception as e: + print(f"❌ 强制删除失败: {e}") + + # **方法3: 最后回退到逐个删除** + if not deletion_success: + print("🔄 回退到逐个删除...") + self._fallback_individual_delete(objects_to_delete) + + except Exception as e: + print(f"❌ 删除操作失败: {e}") + # 立即回退 + self._fallback_individual_delete(objects_to_delete) + + # **步骤7: 删除集合** + if collections_to_delete: + print("🔄 集合删除前DEG更新...") + self._update_dependency_graph() + self._delete_collections_safely(collections_to_delete) + + # **步骤8: 数据清理** + self._safe_data_cleanup(uid, typ, oid) + + # **最终步骤: 删除完成后全面DEG更新** + print("🔄 删除完成后全面DEG更新...") + self._update_dependency_graph(full_update=True) + + # **步骤9: 强制刷新视图 - 使用安全检查** try: bpy.context.view_layer.update() - print(f"🔄 场景更新完成") + + # 安全的视图刷新 + window_manager = bpy.context.window_manager + if window_manager and hasattr(window_manager, 'windows'): + for window in window_manager.windows: + if window and hasattr(window, 'screen') and window.screen: + screen = window.screen + if hasattr(screen, 'areas'): + for area in screen.areas: + if area and hasattr(area, 'type') and area.type in ['VIEW_3D', 'OUTLINER']: + if hasattr(area, 'tag_redraw'): + area.tag_redraw() + else: + # 回退方案:使用context.screen + if hasattr(bpy.context, 'screen') and bpy.context.screen: + screen = bpy.context.screen + if hasattr(screen, 'areas'): + for area in screen.areas: + if area and hasattr(area, 'type') and area.type in ['VIEW_3D', 'OUTLINER']: + if hasattr(area, 'tag_redraw'): + area.tag_redraw() + + print("✅ 强制刷新视图完成") + except Exception as e: - print(f"⚠️ 场景更新失败: {e}") + print(f"⚠️ 视图刷新失败: {e}") + + # **步骤10: 验证删除结果** + remaining_objects = [] + for obj_name in selected_names: + if obj_name in bpy.data.objects: + remaining_objects.append(obj_name) + + if remaining_objects: + print(f"⚠️ 仍有对象未删除: {remaining_objects}") + print("🔄 执行最终清理...") + self._final_cleanup_objects(remaining_objects) + # 最终清理后再次更新DEG + self._update_dependency_graph(full_update=True) + else: + print(f"✅ 所有对象已从视图中删除") + + print(f"✅ Blender操作符删除完成") + + except Exception as e: + print(f"❌ Blender操作符删除失败: {e}") + import traceback + traceback.print_exc() + + # 最终回退策略 + print("🔄 回退到安全数据清理...") + try: + self._safe_data_cleanup(uid, typ, oid) + # 即使回退也要更新DEG + self._update_dependency_graph(full_update=True) + except Exception as fallback_error: + print(f"❌ 回退策略也失败: {fallback_error}") + + def _force_delete_objects(self, objects_to_delete: List[Any]): + """强制删除对象 - 修复字符串比较错误""" + try: + import bpy + + print(f"💪 强制删除模式启动...") + + # **强制删除前DEG更新** + print("🔄 强制删除前DEG更新...") + self._update_dependency_graph(full_update=True) + + deleted_count = 0 + + for obj in objects_to_delete: + try: + if obj and hasattr(obj, 'name'): + obj_name = obj.name + + # **修复1: 使用字符串名称检查而不是对象实例** + # 从所有集合中移除 + for collection in bpy.data.collections: + try: + # 正确的方式:检查名称是否在集合中 + if obj_name in [o.name for o in collection.objects]: + # 通过名称获取对象引用并移除 + obj_ref = collection.objects.get(obj_name) + if obj_ref: + collection.objects.unlink(obj_ref) + except Exception as e: + print(f" ⚠️ 从集合移除失败: {e}") + + # **修复2: 从场景集合中移除** + try: + scene_collection = bpy.context.scene.collection + if obj_name in [o.name for o in scene_collection.objects]: + obj_ref = scene_collection.objects.get( + obj_name) + if obj_ref: + scene_collection.objects.unlink(obj_ref) + except Exception as e: + print(f" ⚠️ 从场景集合移除失败: {e}") + + # **修复3: 从数据中删除** + try: + if obj_name in bpy.data.objects: + obj_ref = bpy.data.objects.get(obj_name) + if obj_ref: + bpy.data.objects.remove(obj_ref) + deleted_count += 1 + print(f" 💪 强制删除成功: {obj_name}") + + # **每删除一个对象就更新依赖图** + self._update_dependency_graph() + except Exception as e: + print(f" ⚠️ 从数据删除失败: {e}") + + except Exception as e: + print(f" ❌ 强制删除失败 {obj.name if obj else 'None'}: {e}") + + print(f"✅ 强制删除完成: {deleted_count} 个对象") + + # **强制删除后全局更新** + if deleted_count > 0: + print("🔄 强制删除后全局DEG更新...") + self._update_dependency_graph(full_update=True) + + except Exception as e: + print(f"❌ 强制删除操作失败: {e}") + + def _final_cleanup_objects(self, object_names: List[str]): + """最终清理对象 - 修复字符串比较错误""" + try: + import bpy + + print("🧹 最终清理模式启动...") + + cleanup_count = 0 + + for obj_name in object_names: + try: + # **修复: 直接使用字符串名称检查** + if obj_name in bpy.data.objects: + obj = bpy.data.objects.get(obj_name) + if obj: + # **终极清理方法** + try: + # 1. 隐藏对象 + obj.hide_viewport = True + obj.hide_render = True + obj.hide_select = True + + # 2. 从场景中移除 - 使用字符串检查 + scene_collection = bpy.context.scene.collection + if obj_name in [o.name for o in scene_collection.objects]: + obj_ref = scene_collection.objects.get( + obj_name) + if obj_ref: + scene_collection.objects.unlink( + obj_ref) + + # 3. 从所有集合中移除 - 使用list()避免迭代时修改 + collections_to_unlink = list( + obj.users_collection) + for collection in collections_to_unlink: + try: + collection.objects.unlink(obj) + except: + pass + + # 4. 最终从数据中删除 + bpy.data.objects.remove(obj) + + cleanup_count += 1 + print(f" 🧹 最终清理成功: {obj_name}") + + except Exception as e: + print(f" ❌ 最终清理失败: {obj_name}, 错误: {e}") + + except Exception as e: + print(f" ❌ 最终清理对象处理失败: {obj_name}, 错误: {e}") + + # 强制垃圾回收 + try: + import gc + gc.collect() + print("🗑️ 执行垃圾回收") + except: + pass + + print(f"✅ 最终清理完成: {cleanup_count} 个对象") + + except Exception as e: + print(f"❌ 最终清理失败: {e}") + + def _find_objects_for_deletion(self, uid: str, typ: str, oid: Any) -> List[Any]: + """查找所有要删除的对象""" + try: + import bpy + + objects_to_delete = [] + + print(f"🔍 查找删除对象: uid={uid}, typ={typ}") + + if typ == "uid": + # 根据命名规律查找对象 + target_patterns = [ + f"Zone_{uid}", + f"Part_{uid}", + f"Hardware_{uid}", + f"TestPart_{uid}", + f"TestObj_{uid}", + f"SafeObj_", # 可能包含uid的hash + f"SafeMesh_" + ] + + for obj in bpy.data.objects: + if obj and hasattr(obj, 'name'): + obj_name = obj.name + + # 检查是否匹配模式 + should_delete = False + + for pattern in target_patterns: + if pattern in obj_name: + should_delete = True + break + + # 检查对象属性 + if not should_delete and hasattr(obj, 'get'): + if obj.get("uid") == uid: + should_delete = True + + if should_delete: + objects_to_delete.append(obj) + print(f" 🎯 标记删除对象: {obj_name}") + + elif typ == "cp" and oid is not None: + # 根据cp值查找 + for obj in bpy.data.objects: + if obj and hasattr(obj, 'get'): + if obj.get("cp") == oid: + objects_to_delete.append(obj) + print(f" 🎯 标记删除对象(cp={oid}): {obj.name}") + + print(f"🔍 查找完成: 找到 {len(objects_to_delete)} 个对象") + return objects_to_delete + + except Exception as e: + print(f"❌ 查找删除对象失败: {e}") + return [] + + def _find_collections_for_deletion(self, uid: str, typ: str, oid: Any) -> List[Any]: + """查找所有要删除的集合""" + try: + import bpy + + collections_to_delete = [] + + print(f"🔍 查找删除集合: uid={uid}, typ={typ}") + + if typ == "uid": + target_patterns = [ + f"Zone_{uid}_", + f"Part_{uid}_", + f"Hardware_{uid}_", + f"TestPart_{uid}_", + f"SafePart_{uid}_" + ] + + for collection in bpy.data.collections: + if collection and hasattr(collection, 'name'): + collection_name = collection.name + + # 检查是否匹配模式 + should_delete = False + + for pattern in target_patterns: + if collection_name.startswith(pattern): + should_delete = True + break + + # 检查集合属性 + if not should_delete and hasattr(collection, 'get'): + if collection.get("uid") == uid: + should_delete = True + + if should_delete: + collections_to_delete.append(collection) + print(f" 🎯 标记删除集合: {collection_name}") + + print(f"🔍 查找完成: 找到 {len(collections_to_delete)} 个集合") + return collections_to_delete + + except Exception as e: + print(f"❌ 查找删除集合失败: {e}") + return [] + + def _fallback_individual_delete(self, objects_to_delete: List[Any]): + """回退策略:逐个删除对象""" + try: + import bpy + + print("🔄 使用回退策略:逐个删除对象") + + deleted_count = 0 + + for obj in objects_to_delete: + try: + if obj and obj.name in bpy.data.objects: + # 从所有集合中移除 + for collection in obj.users_collection: + collection.objects.unlink(obj) + + # 删除对象数据 + if obj.data: + data = obj.data + bpy.data.objects.remove(obj, do_unlink=True) + + # 删除mesh数据(如果没有其他用户) + if hasattr(data, 'users') and data.users == 0: + if hasattr(bpy.data, 'meshes') and data in bpy.data.meshes: + bpy.data.meshes.remove( + data, do_unlink=True) + else: + bpy.data.objects.remove(obj, do_unlink=True) + + deleted_count += 1 + print(f" ✅ 逐个删除: {obj.name}") + + except Exception as e: + print(f" ❌ 逐个删除失败 {obj.name if obj else 'None'}: {e}") + + print(f"✅ 回退删除完成: {deleted_count} 个对象") + + except Exception as e: + print(f"❌ 回退删除失败: {e}") + + def _delete_collections_safely(self, collections_to_delete: List[Any]): + """安全删除集合 - 使用正确的bpy.ops.outliner.delete()方法 + DEG更新""" + try: + import bpy + + print(f"🗑️ 删除集合: {len(collections_to_delete)} 个") + + deleted_count = 0 + + for collection in collections_to_delete: + try: + if collection and hasattr(collection, 'name'): + collection_name = collection.name + + print(f"🎯 尝试删除集合: {collection_name}") + + # **使用传统方法删除集合** + success = self._delete_collection_traditional( + collection) + if success: + deleted_count += 1 + + # **关键修改:每次删除后立即更新依赖图** + self._update_dependency_graph() + + except Exception as e: + print( + f" ❌ 删除集合失败 {collection_name if collection else 'None'}: {e}") + + print(f"✅ 集合删除完成: {deleted_count} 个") + + # **最终的全局依赖图更新** + if deleted_count > 0: + self._update_dependency_graph(full_update=True) + + except Exception as e: + print(f"❌ 删除集合操作失败: {e}") + + def _delete_collection_traditional(self, collection) -> bool: + """传统的集合删除方法 - 修复字符串比较错误""" + try: + import bpy + + collection_name = collection.name + print(f" 🔧 传统删除方法: {collection_name}") + + # **修复1: 从场景层次结构中移除** + try: + scene_collection = bpy.context.scene.collection + # 正确的方式:检查集合名称 + if collection_name in [c.name for c in scene_collection.children]: + collection_ref = scene_collection.children.get( + collection_name) + if collection_ref: + scene_collection.children.unlink(collection_ref) + print(f" ✅ 从场景中移除: {collection_name}") + self._update_dependency_graph() + except Exception as e: + print(f" ⚠️ 从场景移除失败: {e}") + + # **修复2: 从所有父集合中移除** + try: + for parent_col in bpy.data.collections: + if collection_name in [c.name for c in parent_col.children]: + collection_ref = parent_col.children.get( + collection_name) + if collection_ref: + parent_col.children.unlink(collection_ref) + print(f" ✅ 从父集合中移除: {collection_name}") + self._update_dependency_graph() + except Exception as e: + print(f" ⚠️ 从父集合移除失败: {e}") + + # **修复3: 最后从数据中删除** + try: + if collection_name in bpy.data.collections: + collection_ref = bpy.data.collections.get(collection_name) + if collection_ref: + bpy.data.collections.remove(collection_ref) + print(f" ✅ 从数据中删除: {collection_name}") + self._update_dependency_graph() + return True + except Exception as e: + print(f" ⚠️ 从数据删除失败: {e}") + + return False + + except Exception as e: + print(f" ❌ 传统删除方法失败: {e}") + return False + + def _update_dependency_graph(self, full_update: bool = False): + """更新Blender依赖图""" + try: + if not BLENDER_AVAILABLE: + return + + import bpy + + if full_update: + print("🔄 执行全局依赖图更新...") + # 全局更新 + bpy.context.view_layer.update() + bpy.context.evaluated_depsgraph_get().update() + + # 强制刷新视图 - 增加安全检查 + window_manager = bpy.context.window_manager + if window_manager and hasattr(window_manager, 'windows'): + for window in window_manager.windows: + if window and hasattr(window, 'screen') and window.screen: + screen = window.screen + if hasattr(screen, 'areas'): + for area in screen.areas: + if area and hasattr(area, 'type') and area.type in ['VIEW_3D', 'OUTLINER']: + if hasattr(area, 'tag_redraw'): + area.tag_redraw() + else: + # 回退方案:使用context.screen + if hasattr(bpy.context, 'screen') and bpy.context.screen: + screen = bpy.context.screen + if hasattr(screen, 'areas'): + for area in screen.areas: + if area and hasattr(area, 'type') and area.type in ['VIEW_3D', 'OUTLINER']: + if hasattr(area, 'tag_redraw'): + area.tag_redraw() + + print("✅ 全局依赖图更新完成") + else: + # 快速更新 + bpy.context.view_layer.update() + + except Exception as e: + print(f"⚠️ 依赖图更新失败: {e}") + + def _safe_data_cleanup(self, uid: str, typ: str, oid: Any): + """安全的数据清理""" + try: + print(f"🧹 数据清理: uid={uid}, typ={typ}") + + cleanup_count = 0 + + # 清理parts数据 + if hasattr(self, 'parts') and uid in self.parts: + del self.parts[uid] + cleanup_count += 1 + print(f" ✅ 清理parts数据: {uid}") + + # 清理zones数据 + if hasattr(self, 'zones') and uid in self.zones: + del self.zones[uid] + cleanup_count += 1 + print(f" ✅ 清理zones数据: {uid}") + + # 清理hardwares数据 + if hasattr(self, 'hardwares') and uid in self.hardwares: + del self.hardwares[uid] + cleanup_count += 1 + print(f" ✅ 清理hardwares数据: {uid}") + + # 清理其他相关数据 + data_fields = ['machinings', 'dimensions', 'textures'] + for field in data_fields: + if hasattr(self, field): + field_data = getattr(self, field) + if isinstance(field_data, dict) and uid in field_data: + del field_data[uid] + cleanup_count += 1 + print(f" ✅ 清理{field}数据: {uid}") + + print(f"✅ 数据清理完成: {cleanup_count} 项") + + except Exception as e: + print(f"❌ 数据清理失败: {e}") def c10(self, data: Dict[str, Any]): """设置门信息 (set_doorinfo)""" @@ -2836,7 +3672,511 @@ class SUWImpl: scale: float = None, angle: float = None, series: List = None, uid: str = None, zid: Any = None, pid: Any = None, child: Any = None) -> Any: - """创建面 - 核心几何创建方法,精确复现Ruby版本""" + """创建面 - 第二阶段:渐进式Blender几何体""" + try: + if not surface or not surface.get("segs"): + print("⚠️ 无有效面数据或segs") + return None + + # **第二阶段配置** + STAGE_2_SIMPLE_BLENDER = True # 启用简单Blender几何体 + STAGE_2_NO_MATERIALS = True # 暂时不添加材质 + STAGE_2_NO_TRANSFORMS = True # 暂时不应用变换 + STAGE_2_MINIMAL_ATTRS = True # 最少属性设置 + + if BLENDER_AVAILABLE and STAGE_2_SIMPLE_BLENDER: + return self._create_face_stage2_blender( + container, surface, color, scale, angle, + series, uid, zid, pid, child, { + "no_materials": STAGE_2_NO_MATERIALS, + "no_transforms": STAGE_2_NO_TRANSFORMS, + "minimal_attrs": STAGE_2_MINIMAL_ATTRS + }) + else: + return self._create_face_ultra_safe( + container, surface, color, scale, angle, + series, uid, zid, pid, child) + + except Exception as e: + print(f"❌ create_face第二阶段失败: {e}") + return None + + def _create_face_stage2_blender(self, container: Any, surface: Dict[str, Any], + color: str = None, scale: float = None, angle: float = None, + series: List = None, uid: str = None, zid: Any = None, + pid: Any = None, child: Any = None, options: Dict = None) -> Any: + """第二阶段:最简单的Blender几何体创建 - 添加严格的创建控制""" + try: + print("🚀 第二阶段:简单Blender几何体模式") + + # **关键修复:全局创建锁,确保同时只有一个mesh创建** + if not hasattr(self.__class__, '_mesh_creation_lock'): + self.__class__._mesh_creation_lock = False + self.__class__._mesh_creation_count = 0 + + # 检查是否有其他创建正在进行 + if self.__class__._mesh_creation_lock: + print("⏳ 检测到并发创建,切换到存根模式避免冲突") + return self._create_face_ultra_safe( + container, surface, color, scale, angle, + series, uid, zid, pid, child) + + # 设置创建锁 + self.__class__._mesh_creation_lock = True + self.__class__._mesh_creation_count += 1 + + try: + return self._create_single_mesh_atomic( + container, surface, color, scale, angle, + series, uid, zid, pid, child, options) + finally: + # 无论成功失败都释放锁 + self.__class__._mesh_creation_lock = False + + except Exception as e: + print(f"❌ 第二阶段创建失败: {e}") + # 确保释放锁 + if hasattr(self.__class__, '_mesh_creation_lock'): + self.__class__._mesh_creation_lock = False + return None + + def _create_single_mesh_atomic(self, container: Any, surface: Dict[str, Any], + color: str = None, scale: float = None, angle: float = None, + series: List = None, uid: str = None, zid: Any = None, + pid: Any = None, child: Any = None, options: Dict = None) -> Any: + """原子性mesh创建 - 优化创建策略""" + try: + # **修复:重置和优化创建计数器** + if not hasattr(self.__class__, '_mesh_creation_count'): + self.__class__._mesh_creation_count = 0 + + creation_count = self.__class__._mesh_creation_count + + # **新策略:按批次重置计数器** + if creation_count > 100: # 每100个对象重置一次 + print(f"🔄 重置创建计数器: {creation_count} -> 0") + self.__class__._mesh_creation_count = 0 + creation_count = 0 + + # 强制清理内存 + try: + import gc + gc.collect() + print("🧹 执行内存清理") + except: + pass + + # **优化:提高安全模式阈值** + SAFE_MODE_THRESHOLD = 20 # 提高到20个对象 + BATCH_SIZE = 10 # 每批10个对象 + + if creation_count > SAFE_MODE_THRESHOLD: + # 检查是否可以继续创建 + batch_position = creation_count % BATCH_SIZE + if batch_position < 8: # 每批前8个正常创建,后2个安全模式 + print(f"📦 批次创建 {creation_count}: 位置 {batch_position}/10") + else: + print(f"⚠️ 批次安全模式 ({creation_count}),切换到存根") + return self._create_face_ultra_safe( + container, surface, color, scale, angle, + series, uid, zid, pid, child) + + # **智能延迟:根据创建频率调整** + if creation_count > 20: + import time + delay = min((creation_count - 20) * 0.02, 0.1) # 最多100ms延迟 + if delay > 0: + print(f"⏱️ 智能延迟: {delay:.3f}秒 (第{creation_count}个对象)") + time.sleep(delay) + + # **步骤1:数据验证** + segs = surface.get("segs", []) + points = [] + for seg in segs: + if isinstance(seg, list) and len(seg) >= 1: + point_str = seg[0] if isinstance( + seg[0], str) else str(seg[0]) + try: + point = Point3d.parse(point_str) + if point: + points.append(point) + except Exception as e: + print(f"⚠️ 点解析失败: {point_str}") + + if len(points) < 3: + print(f"⚠️ 有效点数不足: {len(points)}") + return None + + # 面积验证 + area = self._calculate_face_area(points) + if area < 1e-6: + print(f"⚠️ 面积过小: {area}") + return None + + # **关键修复:将点数据添加到series参数** + if series is not None: + series.append(points) + # print(f"✅ 点数据已添加到series: {len(points)} 个点") + + print(f"✅ 几何验证通过: {len(points)} 个点,面积: {area:.2f}") + + # **步骤2:安全的原子性mesh创建** + import bpy + + # **检查Blender状态** + try: + if not bpy.context.scene: + print("❌ Blender上下文无效") + return None + except Exception as e: + print(f"❌ Blender上下文检查失败: {e}") + return None + + # 使用更安全的命名策略 + timestamp = hash(str(points[0]) + str(uid) + str(child) + str(creation_count)) + mesh_name = f"SafeMesh_{abs(timestamp) % 10000}" + + mesh = None + obj = None + + try: + # **原子操作1:创建mesh** + mesh = bpy.data.meshes.new(mesh_name) + + + # **原子操作2:设置几何数据并创建UV** + vertices = [(float(p.x), float(p.y), float(p.z)) for p in points] + faces = [list(range(len(vertices)))] if len(vertices) >= 3 else [] + + mesh.from_pydata(vertices, [], faces) + + # 创建UV坐标层用于纹理映射 + if len(vertices) >= 3: + try: + uv_layer = mesh.uv_layers.new(name="UVMap") + + # 为每个面生成简单的UV坐标 + for poly in mesh.polygons: + for i, loop_index in enumerate(poly.loop_indices): + if len(vertices) == 4: # 四边形 + uv_coords = [(0, 0), (1, 0), (1, 1), (0, 1)] + uv_layer.data[loop_index].uv = uv_coords[i % 4] + elif len(vertices) == 3: # 三角形 + uv_coords = [(0, 0), (1, 0), (0.5, 1)] + uv_layer.data[loop_index].uv = uv_coords[i % 3] + else: # 其他多边形 + angle = (i / len(poly.loop_indices)) * 2 * 3.14159 + import math + u = (math.cos(angle) + 1) * 0.5 + v = (math.sin(angle) + 1) * 0.5 + uv_layer.data[loop_index].uv = (u, v) + + print(f"✅ UV坐标在创建时生成完成") + except Exception as uv_error: + print(f"⚠️ UV坐标创建失败: {uv_error}") + + mesh.update() + + # **原子操作3:验证mesh有效性** + if not mesh.polygons: + raise Exception("mesh验证失败:无多边形") + + # **原子操作4:创建对象** + obj_name = f"SafeObj_{abs(timestamp) % 10000}" + obj = bpy.data.objects.new(obj_name, mesh) + + # **原子操作5:添加到集合** + if hasattr(container, 'objects'): + container.objects.link(obj) + else: + bpy.context.scene.collection.objects.link(obj) + + # **原子操作6:设置基本属性** + obj["uid"] = str(uid) if uid else "unknown" + obj["child"] = str(child) if child else "unknown" + obj["stage"] = "stage2_optimized" + obj["area"] = float(area) + obj["creation_index"] = creation_count + + + # **原子操作7:设置材质**# 应用材质 + # 应用材质 - 最简版本 + if color: + try: + material = self.get_texture(color) + if material: + if obj.data.materials: + obj.data.materials[0] = material + else: + obj.data.materials.append(material) + print(f"✅ 材质应用成功: {color}") + else: + self._apply_simple_material(obj, color) + except Exception as e: + print(f"❌ 材质应用失败: {e}") + try: + self._apply_simple_material(obj, color) + except: + pass + print(f"✅ 优化mesh创建成功: {obj.name}, {len(vertices)}顶点") + return obj + + except Exception as e: + print(f"❌ mesh创建失败: {e}") + + # **关键:失败时彻底清理资源** + try: + if obj: + for collection in obj.users_collection: + collection.objects.unlink(obj) + bpy.data.objects.remove(obj, do_unlink=True) + + if mesh: + bpy.data.meshes.remove(mesh, do_unlink=True) + + except Exception as cleanup_error: + print(f"⚠️ 资源清理失败: {cleanup_error}") + + return None + + except Exception as e: + print(f"❌ 优化创建失败: {e}") + return None + + def get_creation_stats(self) -> Dict[str, Any]: + """获取创建统计信息""" + try: + stats = { + "creation_count": getattr(self.__class__, '_mesh_creation_count', 0), + "lock_status": getattr(self.__class__, '_mesh_creation_lock', False), + "stub_objects": len(getattr(self, '_stub_objects', [])), + "blender_available": BLENDER_AVAILABLE + } + + if BLENDER_AVAILABLE: + import bpy + stats["total_objects"] = len(bpy.data.objects) + stats["total_meshes"] = len(bpy.data.meshes) + + # 统计我们创建的对象 + our_objects = [obj for obj in bpy.data.objects if obj.get( + "stage") == "stage2_atomic"] + stats["our_objects"] = len(our_objects) + + return stats + + except Exception as e: + return {"error": str(e)} + + def _apply_simple_material(self, obj: Any, color: str): + """应用最简单的材质""" + try: + import bpy + + # 创建最基本的材质 + mat_name = f"SimpleMat_{color}" if color else "SimpleMat_default" + + # 查找现有材质 + material = bpy.data.materials.get(mat_name) + if not material: + material = bpy.data.materials.new(mat_name) + material.use_nodes = True + + # 设置最基本的颜色 + if material.node_tree and material.node_tree.nodes: + principled = material.node_tree.nodes.get( + "Principled BSDF") + if principled: + if color and len(color) >= 6: + try: + # 简单的颜色解析 + r = int(color[0:2], 16) / 255.0 + g = int(color[2:4], 16) / 255.0 + b = int(color[4:6], 16) / 255.0 + principled.inputs[0].default_value = ( + r, g, b, 1.0) + except: + principled.inputs[0].default_value = ( + 0.8, 0.8, 0.8, 1.0) + else: + principled.inputs[0].default_value = ( + 0.8, 0.8, 0.8, 1.0) + + # 应用材质到对象 + if obj.data: + obj.data.materials.append(material) + + print(f"✅ 简单材质应用成功: {mat_name}") + + except Exception as e: + print(f"❌ 简单材质应用失败: {e}") + + def _create_face_ultra_safe(self, container: Any, surface: Dict[str, Any], + color: str = None, scale: float = None, angle: float = None, + series: List = None, uid: str = None, zid: Any = None, + pid: Any = None, child: Any = None) -> Any: + """超安全的面创建 - 最小化Blender操作""" + try: + print("🛡️ 超安全面创建模式") + + # **验证几何数据但不创建真实面** + segs = surface.get("segs", []) + if len(segs) < 3: + print(f"⚠️ 线段数不足: {len(segs)}") + return None + + # 提取并验证点 + points = [] + for seg in segs: + if isinstance(seg, list) and len(seg) >= 1: + point_str = seg[0] if isinstance( + seg[0], str) else str(seg[0]) + try: + point = Point3d.parse(point_str) + if point: + points.append(point) + except Exception as e: + print(f"⚠️ 点解析失败: {point_str}, {e}") + + if len(points) < 3: + print(f"⚠️ 有效点数不足: {len(points)}") + return None + + # **关键修复:将点数据添加到series参数** + if series is not None: + series.append(points) + print(f"✅ 点数据已添加到series (存根模式): {len(points)} 个点") + + # 计算面积 + try: + area = self._calculate_face_area(points) + if area < 1e-6: + print(f"⚠️ 面积过小: {area}") + return None + print(f"✅ 几何验证通过: {len(points)} 个点,面积: {area:.2f}") + except Exception as e: + print(f"⚠️ 面积计算失败: {e}") + area = 0.01 # 使用默认值 + + # **创建存根对象,不操作Blender** + face_obj = self._create_stub_face_object( + points, surface, color, uid, zid, pid, child, area) + + if face_obj: + print(f"✅ 存根面创建成功: {len(points)} 顶点,面积: {area:.2f}") + + return face_obj + + except Exception as e: + print(f"❌ 超安全面创建失败: {e}") + return None + + def _create_stub_face_object(self, points: List, surface: Dict[str, Any], + color: str, uid: str, zid: Any, pid: Any, + child: Any, area: float) -> Dict[str, Any]: + """创建存根面对象""" + try: + face_obj = { + "type": "face", + "mode": "stub", + "points": [{"x": p.x, "y": p.y, "z": p.z} for p in points], + "surface": surface.copy(), + "area": area, + "deleted": False, + "visible": True, + "created_time": hash(str(points[0]) + str(uid) + str(child)), + + # 属性 + "uid": uid, + "zid": zid, + "pid": pid, + "child": child, + "typ": "face", + "ckey": color, + "vx": surface.get("vx"), + "vz": surface.get("vz"), + + # 几何信息 + "vertex_count": len(points), + "segs_count": len(surface.get("segs", [])), + + # 伪造Blender对象的基本属性 + "name": f"StubFace_{uid}_{child}" if uid and child else "StubFace", + "hide_viewport": False, + "hide_render": False + } + + # 添加到容器记录 + if hasattr(self, '_stub_objects'): + self._stub_objects.append(face_obj) + else: + self._stub_objects = [face_obj] + + return face_obj + + except Exception as e: + print(f"❌ 创建存根对象失败: {e}") + return None + + def get_stub_objects_summary(self) -> Dict[str, Any]: + """获取存根对象摘要""" + try: + stub_objects = getattr(self, '_stub_objects', []) + + summary = { + "total_count": len(stub_objects), + "by_uid": {}, + "by_type": {}, + "total_area": 0 + } + + for obj in stub_objects: + # 按uid统计 + uid = obj.get("uid", "unknown") + if uid not in summary["by_uid"]: + summary["by_uid"][uid] = 0 + summary["by_uid"][uid] += 1 + + # 按类型统计 + obj_type = obj.get("type", "unknown") + if obj_type not in summary["by_type"]: + summary["by_type"][obj_type] = 0 + summary["by_type"][obj_type] += 1 + + # 总面积 + summary["total_area"] += obj.get("area", 0) + + return summary + + except Exception as e: + print(f"❌ 获取存根摘要失败: {e}") + return {"error": str(e)} + + def switch_to_blender_mode(self): + """切换到Blender模式 - 手动调用""" + try: + print("🔄 切换到Blender渲染模式...") + + stub_objects = getattr(self, '_stub_objects', []) + if not stub_objects: + print("📝 没有存根对象需要转换") + return + + print(f"📝 发现{len(stub_objects)}个存根对象") + + # 这里可以实现存根对象到真实Blender对象的转换 + # 但目前保持安全,不实际操作 + + print("✅ 模式切换完成(当前为安全模式)") + + except Exception as e: + print(f"❌ 模式切换失败: {e}") + + def _create_face_blender_mode(self, container: Any, surface: Dict[str, Any], color: str = None, + scale: float = None, angle: float = None, series: List = None, + uid: str = None, zid: Any = None, pid: Any = None, + child: Any = None) -> Any: + """安全的面创建实现""" try: if not surface: print("⚠️ 无有效面数据") @@ -2847,26 +4187,22 @@ class SUWImpl: print("⚠️ 面数据无segs段") return None - # 解析向量数据 - 复现Ruby的vx和vz处理 + # **添加创建间隔,让Blender有时间稳定** + import time + time.sleep(0.01) # 10ms间隔,避免创建过快 + + # 解析向量数据 vx = Vector3d.parse(surface.get( "vx")) if surface.get("vx") else None vz = Vector3d.parse(surface.get( "vz")) if surface.get("vz") else None - # 解析点数据 - 支持直线和弧线 + # 解析点数据 points = [] for seg in segs: if isinstance(seg, list) and len(seg) >= 2: - # 处理弧线段(包含圆心和角度信息) - if len(seg) > 2 and isinstance(seg[2], dict): - # 弧线段,暂时取起点,后续可扩展 - point_str = seg[0] if isinstance( - seg[0], str) else str(seg[0]) - else: - # 直线段,取起点 - point_str = seg[0] if isinstance( - seg[0], str) else str(seg[0]) - + point_str = seg[0] if isinstance( + seg[0], str) else str(seg[0]) point = Point3d.parse(point_str) if point: points.append(point) @@ -2875,7 +4211,7 @@ class SUWImpl: print(f"⚠️ 点数不足以创建面: {len(points)}") return None - # 几何验证 - 添加共面性检查 + # 几何验证 if not self._validate_coplanar(points): print(f"⚠️ 警告: 检测到非共面点,尝试修复") points = self._fix_non_coplanar_points(points) @@ -2891,121 +4227,80 @@ class SUWImpl: print(f"✅ 几何验证通过: {len(points)} 个点,面积: {area:.2f}") - # 创建面对象 + # **关键修复:批量创建模式,一次性完成所有操作** if BLENDER_AVAILABLE: - try: - import bmesh - - # 创建网格数据 - mesh = bpy.data.meshes.new("SuwFace") - - # 转换点为顶点坐标 (保持原始mm尺寸) - vertices = [(point.x, point.y, point.z) - for point in points] - faces = [list(range(len(vertices)))] - - # 使用from_pydata方法创建几何体 - mesh.from_pydata(vertices, [], faces) - mesh.update() - - # 验证几何体有效性 - if not mesh.polygons: - print(f"❌ 面创建失败:无有效多边形") - bpy.data.meshes.remove(mesh) - return None - - # 处理法向量 - 复现Ruby的face.reverse!逻辑 - if vz and mesh.polygons: - face_normal = mesh.polygons[0].normal - target_normal = mathutils.Vector( - (vz.x, vz.y, vz.z)).normalized() - - # 检查是否需要反转面 - if face_normal.dot(target_normal) < 0: - print("🔄 反转面法向量以匹配目标方向") - # 反转面的顶点顺序 - for poly in mesh.polygons: - poly.vertices = list(reversed(poly.vertices)) - mesh.update() - - # 创建对象 - obj = bpy.data.objects.new("SuwFace", mesh) - - # 添加到集合或场景 - if hasattr(container, 'objects'): - container.objects.link(obj) - elif hasattr(bpy.context.scene, 'collection'): - bpy.context.scene.collection.objects.link(obj) - - # 应用材质 - 精确复现Ruby的textured_surf方法 - if color: - self._apply_material_to_face(obj, color, scale, angle) - else: - # 默认材质 - self._apply_material_to_face(obj, "mat_normal") - - # 设置所有属性(包括uid) - self._set_object_attributes( - obj, - uid=uid, - zid=zid, - pid=pid, - child=child, - typ="face", - ckey=color, - scale=scale, - angle=angle, - vx=surface.get("vx"), - vz=surface.get("vz") - ) - - # 添加到系列 - if series is not None: - series.append(points) # Ruby版本存储点集合 - - print(f"✅ Blender面创建成功: {len(points)} 顶点,面积: {area:.2f}") - return obj - - except Exception as e: - print(f"❌ Blender面创建失败: {e}") - import traceback - traceback.print_exc() - - # 存根模式 - 创建字典对象 - face_obj = { - "type": "face", - "points": points, - "surface": surface.copy(), - "deleted": False, - "visible": True, - "area": area, - "normal": vz.to_s() if vz else None - } - - # 设置所有属性 - self._set_object_attributes( - face_obj, - uid=uid, - zid=zid, - pid=pid, - child=child, - typ="face", - ckey=color, - scale=scale, - angle=angle, - vx=surface.get("vx"), - vz=surface.get("vz") - ) - - # 添加到系列 - if series is not None: - series.append(points) - - print(f"✅ 面创建成功 (存根模式): {len(points)} 点,面积: {area:.2f}") - return face_obj + return self._create_blender_face_atomic( + container, points, vz, color, scale, angle, surface, + uid, zid, pid, child, area, series) + else: + return self._create_stub_face( + points, surface, color, uid, zid, pid, child, area, series) except Exception as e: - print(f"❌ 创建面失败: {e}") + print(f"❌ 安全面创建失败: {e}") + return None + + def _create_blender_face_atomic(self, container, points, vz, color, scale, angle, + surface, uid, zid, pid, child, area, series): + """原子性创建Blender面 - 一次性完成,避免中间状态""" + try: + import bpy + import bmesh + import mathutils + + # **原子操作1:创建网格数据** + mesh_name = f"SuwFace_{uid}_{child}" if uid and child else "SuwFace" + mesh = bpy.data.meshes.new(mesh_name) + + # **原子操作2:设置几何数据** + vertices = [(point.x, point.y, point.z) for point in points] + faces = [list(range(len(vertices)))] + mesh.from_pydata(vertices, [], faces) + mesh.update() + + # **原子操作3:处理法向量** + if vz and mesh.polygons: + face_normal = mesh.polygons[0].normal + target_normal = mathutils.Vector( + (vz.x, vz.y, vz.z)).normalized() + if face_normal.dot(target_normal) < 0: + for poly in mesh.polygons: + poly.vertices = list(reversed(poly.vertices)) + mesh.update() + + # **原子操作4:创建对象** + obj_name = f"SuwFace_{uid}_{child}" if uid and child else "SuwFace" + obj = bpy.data.objects.new(obj_name, mesh) + + # **原子操作5:添加到集合(简化)** + if hasattr(container, 'objects'): + container.objects.link(obj) + else: + bpy.context.scene.collection.objects.link(obj) + + # **原子操作6:应用材质** + if color: + self._apply_material_to_face(obj, color, scale, angle) + else: + self._apply_material_to_face(obj, "mat_normal") + + # **原子操作7:设置属性(批量)** + obj["uid"] = uid + obj["zid"] = zid + obj["pid"] = pid + obj["child"] = child + obj["typ"] = "face" + obj["ckey"] = color + obj["area"] = area + obj["creation_mode"] = "atomic" + + # **不进行任何场景更新,让Blender自然处理** + + print(f"✅ Blender面创建成功: {len(points)} 顶点,面积: {area:.2f}") + return obj + + except Exception as e: + print(f"❌ Blender原子面创建失败: {e}") import traceback traceback.print_exc() return None @@ -3512,6 +4807,334 @@ class SUWImpl: # 这个方法可以添加全局错误处理逻辑 pass + def get_blender_part_info(self, uid: str = None) -> Dict[str, Any]: + """获取Blender中的部件信息""" + try: + if not BLENDER_AVAILABLE: + return {"error": "Blender not available"} + + import bpy + part_info = { + "collections": [], + "objects": [], + "total_collections": 0, + "total_objects": 0 + } + + for collection in bpy.data.collections: + if collection.name.startswith("Part_"): + collection_uid = collection.get("uid", "unknown") + if uid is None or collection_uid == uid: + info = { + "name": collection.name, + "uid": collection_uid, + "cp": collection.get("cp", "unknown"), + "objects_count": len(collection.objects), + "children_count": len(collection.children) + } + part_info["collections"].append(info) + part_info["total_objects"] += len(collection.objects) + + part_info["total_collections"] = len(part_info["collections"]) + return part_info + + except Exception as e: + return {"error": str(e)} + + def print_blender_part_summary(self): + """打印Blender部件摘要""" + try: + info = self.get_blender_part_info() + + print("\n" + "="*60) + print("🎨 Blender 部件创建摘要") + print("="*60) + + if "error" in info: + print(f"❌ 错误: {info['error']}") + return + + print(f"总集合数量: {info['total_collections']}") + print(f"总对象数量: {info['total_objects']}") + print("\n部件详情:") + + for collection_info in info["collections"]: + print(f" 📁 {collection_info['name']}") + print(f" UID: {collection_info['uid']}") + print(f" CP: {collection_info['cp']}") + print(f" 对象: {collection_info['objects_count']}") + print(f" 子集合: {collection_info['children_count']}") + + print("="*60 + "\n") + + except Exception as e: + print(f"⚠️ 打印Blender摘要失败: {e}") + + def _create_part_collection_safe(self, uid: str, root: Any, parts: Dict) -> Any: + """安全创建部件集合""" + try: + import bpy + + # 创建集合名称 + collection_name = f"Part_{uid}_{root}" + + # 检查是否已存在 + existing_collection = bpy.data.collections.get(collection_name) + if existing_collection: + print(f"🔄 使用现有集合: {collection_name}") + self._clear_collection_safe(existing_collection) + return existing_collection + + # 创建新集合 + new_collection = bpy.data.collections.new(collection_name) + + # 添加到场景 + bpy.context.scene.collection.children.link(new_collection) + + # 设置集合属性 + new_collection["uid"] = uid + new_collection["cp"] = root + new_collection["collection_type"] = "part" + + print(f"✅ 创建新集合: {collection_name}") + return new_collection + + except Exception as e: + print(f"❌ 创建集合失败: {e}") + return None + + def _clear_collection_safe(self, collection): + """安全清理集合内容""" + try: + if not collection: + return + + # 移除所有对象(不删除,只是unlink) + objects_to_unlink = list(collection.objects) + for obj in objects_to_unlink: + try: + collection.objects.unlink(obj) + except Exception as e: + print(f"⚠️ 无法移除对象 {obj.name}: {e}") + + # 移除子集合 + children_to_unlink = list(collection.children) + for child in children_to_unlink: + try: + collection.children.unlink(child) + except Exception as e: + print(f"⚠️ 无法移除子集合 {child.name}: {e}") + + print(f"🧹 清理集合完成: {collection.name}") + + except Exception as e: + print(f"❌ 清理集合失败: {e}") + + def _set_part_attributes_safe(self, part_collection, data: Dict[str, Any], uid: str, root: Any): + """安全设置部件属性""" + try: + if not part_collection: + return + + # 基础属性 + part_collection["uid"] = uid + part_collection["cp"] = root + part_collection["typ"] = data.get("typ", "0") + + # 门抽屉属性 + drawer = data.get("drawer", 0) + door = data.get("door", 0) + part_collection["drawer"] = drawer + part_collection["door"] = door + + print(f"✅ 部件属性设置完成") + print(f"🔧 门抽屉功能设置完成: drawer={drawer}, door={door}") + + except Exception as e: + print(f"❌ 设置部件属性失败: {e}") + + def _set_part_layer_safe(self, part_collection, data: Dict[str, Any]): + """安全设置部件图层""" + try: + if not part_collection: + return + + # 获取抽屉和门属性 + drawer = data.get("drawer", 0) + door = data.get("door", 0) + + # 根据属性设置图层 + if drawer > 0 and self.drawer_layer: + # 抽屉图层 + try: + self.drawer_layer.children.link(part_collection) + print(f"📂 添加到抽屉图层") + except: + print(f"⚠️ 无法添加到抽屉图层") + + elif door > 0 and self.door_layer: + # 门板图层 + try: + self.door_layer.children.link(part_collection) + print(f"📂 添加到门板图层") + except: + print(f"⚠️ 无法添加到门板图层") + + except Exception as e: + print(f"❌ 设置图层失败: {e}") + + def _apply_unit_transform_safe(self, part_collection, data: Dict[str, Any], uid: str): + """安全应用单元变换""" + try: + if uid in self.unit_trans: + trans = self.unit_trans[uid] + print(f"🔄 应用单元变换: {uid}") + + # 构建变换矩阵 + if BLENDER_AVAILABLE: + import mathutils + trans_matrix = mathutils.Matrix(( + (trans.x_axis.x, trans.y_axis.x, + trans.z_axis.x, trans.origin.x), + (trans.x_axis.y, trans.y_axis.y, + trans.z_axis.y, trans.origin.y), + (trans.x_axis.z, trans.y_axis.z, + trans.z_axis.z, trans.origin.z), + (0, 0, 0, 1) + )) + + # 递归应用变换到所有对象 + self._apply_transform_recursive( + part_collection, trans_matrix) + else: + print("🔄 存根模式:变换已记录") + + except Exception as e: + print(f"⚠️ 应用单元变换失败: {e}") + + def _apply_transform_recursive(self, collection, transform_matrix): + """递归应用变换到集合中的所有对象""" + try: + # 应用到直接对象 + for obj in collection.objects: + try: + obj.matrix_world = transform_matrix @ obj.matrix_world + except Exception as e: + print(f"⚠️ 对象变换失败: {obj.name}, {e}") + + # 递归应用到子集合 + for child_collection in collection.children: + self._apply_transform_recursive( + child_collection, transform_matrix) + + except Exception as e: + print(f"⚠️ 递归变换失败: {e}") + + def diagnose_system_state(self): + """诊断系统状态""" + try: + import gc + import sys + + print("🔍 系统诊断开始...") + print(f" Python版本: {sys.version}") + print(f" 对象计数: {len(gc.get_objects())}") + print(f" 内存引用: {sys.getrefcount(self)}") + + if BLENDER_AVAILABLE: + import bpy + print(f" Blender版本: {bpy.app.version}") + print(f" 场景对象数: {len(bpy.context.scene.objects)}") + print(f" 数据块数: {len(bpy.data.objects)}") + + # 检查实例状态 + print(f" zones数量: {len(getattr(self, 'zones', {}))}") + print(f" parts数量: {len(getattr(self, 'parts', {}))}") + + print("✅ 系统诊断完成") + + except Exception as e: + print(f"❌ 系统诊断失败: {e}") + + def force_cleanup(self): + """强制清理,释放资源""" + try: + import gc + + print("🧹 强制清理开始...") + + # 清理Python对象 + collected = gc.collect() + print(f" 回收对象: {collected}") + + if BLENDER_AVAILABLE: + import bpy + + # 清理孤立数据块 + bpy.ops.outliner.orphans_purge() + print(" 清理孤立数据块") + + print("✅ 强制清理完成") + + except Exception as e: + print(f"❌ 强制清理失败: {e}") + + def _c09_progressive_delete(self, uid: str, typ: str, oid: Any, options: Dict[str, bool]): + """渐进式删除(备用方法)""" + try: + print(f"🛡️ 渐进式删除备用: uid={uid}, typ={typ}") + print(f"🔧 删除选项: {options}") + + # 第一步:数据删除 + if options.get("data_delete", True): + self._safe_data_cleanup(uid, typ, oid) + + # 第二步:Blender对象隐藏(更安全) + if options.get("blender_hide", False) and BLENDER_AVAILABLE: + self._safe_blender_hide(uid, typ, oid) + + # 第三步:实际删除(如果启用) + if options.get("blender_delete", False) and BLENDER_AVAILABLE: + self._safe_blender_delete(uid, typ, oid) + + except Exception as e: + print(f"❌ 渐进式删除失败: {e}") + + def _safe_blender_hide(self, uid: str, typ: str, oid: Any): + """安全的Blender对象隐藏(备用方法)""" + try: + import bpy + + print("👻 安全隐藏Blender对象...") + + hidden_count = 0 + + # **使用集合名称查找,避免遍历所有对象** + if typ == "uid": + # 根据命名规律查找集合 + target_patterns = [ + f"Zone_{uid}_", + f"Part_{uid}_", + f"Hardware_{uid}_" + ] + + for pattern in target_patterns: + # 查找匹配的集合 + for collection in list(bpy.data.collections): + if collection.name.startswith(pattern): + try: + collection.hide_viewport = True + collection.hide_render = True + hidden_count += 1 + print(f" 👻 隐藏集合: {collection.name}") + except Exception as e: + print(f" ⚠️ 隐藏集合失败: {collection.name}, {e}") + + print(f"✅ 安全隐藏完成: {hidden_count} 个对象") + + except Exception as e: + print(f"❌ 安全隐藏失败: {e}") + # 翻译进度统计 TRANSLATED_METHODS = [ diff --git a/ruby/ruby/SUWImpl.rb b/ruby/ruby/SUWImpl.rb index 31ba03f..7902b35 100644 --- a/ruby/ruby/SUWImpl.rb +++ b/ruby/ruby/SUWImpl.rb @@ -995,7 +995,7 @@ module SUWood oid = data.fetch("oid") if typ == "wall" - zones = get_zones(data) + zones = get_zones(data) zone = zones.fetch(oid, nil) wall = data.fetch("wall") zone.entities.each{|entity| diff --git a/test/blender_suw_client.py b/test/blender_suw_client.py index 9ca6927..dbd128c 100644 --- a/test/blender_suw_client.py +++ b/test/blender_suw_client.py @@ -59,9 +59,10 @@ try: start_time = time.time() try: if hasattr(suw_impl, actual_cmd_type): - print(f"🔍 查找到命令方法: {actual_cmd_type}") + print( + f"🔍 [{time.strftime('%H:%M:%S')}] 查找到命令方法: {actual_cmd_type}") method = getattr(suw_impl, actual_cmd_type) - print(f"⚡ 开始执行命令...") + print(f"⚡ [{time.strftime('%H:%M:%S')}] 开始执行命令...") result = method(actual_data) @@ -70,7 +71,8 @@ try: f"✅ [{time.strftime('%H:%M:%S')}] 命令 '{actual_cmd_type}' 执行成功!") print(f"⏱️ 执行耗时: {elapsed_time:.3f}秒") if result is not None: - print(f"📋 执行结果: {result}") + print( + f"📋 [{time.strftime('%H:%M:%S')}] 执行结果: {result}") else: print(f"📋 执行结果: 无返回值")