From 0dc115fd233b5de086bc9bdb376d4cae499cdb96 Mon Sep 17 00:00:00 2001 From: wTechArtist Date: Wed, 18 Jun 2025 15:02:10 +0800 Subject: [PATCH] update --- __init__.py | 2 + comfyui_vggt_nodes.py | 2 +- glb_point_cloud_processor.py | 572 ++++++++++++++++++++++++++++------- 3 files changed, 466 insertions(+), 110 deletions(-) diff --git a/__init__.py b/__init__.py index 5f43652..a7a4c7f 100644 --- a/__init__.py +++ b/__init__.py @@ -13,6 +13,7 @@ NODE_CLASS_MAPPINGS = { "VGGTMultiInputNode": VGGTMultiInputNode, "GLBPointCloudProcessor": GLBPointCloudProcessor, "GLBPointCloudBounds": GLBPointCloudBounds, + "GLBPointCloudOriginAdjuster": GLBPointCloudOriginAdjuster, } # 节点显示名称映射 @@ -21,6 +22,7 @@ NODE_DISPLAY_NAME_MAPPINGS = { "VGGTMultiInputNode": "VVL VGGT 多输入重建", "GLBPointCloudProcessor": "VVL GLB 点云黑色点清理", "GLBPointCloudBounds": "VVL GLB 点云包围盒计算", + "GLBPointCloudOriginAdjuster": "VVL GLB 点云原点调整", } __all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS'] diff --git a/comfyui_vggt_nodes.py b/comfyui_vggt_nodes.py index 729ec47..5d80f91 100644 --- a/comfyui_vggt_nodes.py +++ b/comfyui_vggt_nodes.py @@ -1094,7 +1094,7 @@ class VGGTMultiInputNode: }, "optional": { "confidence_threshold": ("FLOAT", { - "default": 50.0, "min": 0.0, "max": 100.0, "step": 0.1, + "default": 90.0, "min": 0.0, "max": 100.0, "step": 0.1, "tooltip": "置信度阈值(%),用于过滤3D点云中的低置信度点" }), "show_cameras": ("BOOLEAN", { diff --git a/glb_point_cloud_processor.py b/glb_point_cloud_processor.py index f6d4c7d..644b6a6 100755 --- a/glb_point_cloud_processor.py +++ b/glb_point_cloud_processor.py @@ -54,18 +54,12 @@ class GLBPointCloudProcessor: RETURN_TYPES = ( "STRING", # 处理后的GLB文件路径 - "STRING", # 处理统计信息(JSON格式) - "STRING", # 处理日志信息 ) RETURN_NAMES = ( "processed_glb_path", - "processing_stats", - "processing_log", ) OUTPUT_TOOLTIPS = [ "处理后的GLB文件完整路径", - "处理统计信息(JSON格式)- 包含删除的点数、剩余点数等", - "详细的处理日志信息" ] OUTPUT_NODE = True FUNCTION = "process_point_cloud" @@ -87,14 +81,14 @@ class GLBPointCloudProcessor: error_msg = "trimesh库不可用,无法处理GLB文件" logger.error(error_msg) processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + return ("",) # 验证输入文件路径 if not glb_file_path or not glb_file_path.strip(): error_msg = "GLB文件路径为空" logger.error(error_msg) processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + return ("",) # 处理文件路径 input_path = self._resolve_file_path(glb_file_path.strip()) @@ -104,7 +98,7 @@ class GLBPointCloudProcessor: error_msg = f"GLB文件不存在: {input_path}" logger.error(error_msg) processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + return ("",) try: # 加载GLB文件 @@ -135,12 +129,12 @@ class GLBPointCloudProcessor: else: error_msg = "GLB文件中未找到点云数据" processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + return ("",) if not point_clouds: error_msg = "GLB文件中没有点云数据" processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + return ("",) # 处理每个点云 processed_point_clouds = [] @@ -214,31 +208,18 @@ class GLBPointCloudProcessor: file_size = os.path.getsize(output_path) processing_log.append(f"GLB文件保存成功,文件大小: {file_size} bytes") - # 生成统计信息 - stats = { - "original_points": total_original_points, - "remaining_points": total_original_points - total_removed_points, - "removed_points": total_removed_points, - "removal_percentage": (total_removed_points / total_original_points * 100) if total_original_points > 0 else 0, - "point_clouds_processed": len(point_clouds), - "other_geometries_preserved": len(other_geometries), - "output_file_size": file_size, - "settings": { - "black_threshold": black_threshold, - "remove_dark_points": True - } - } + processing_log.append("GLB点云黑色点清理完成!") - return output_path, json.dumps(stats, indent=2), "\n".join(processing_log) + return (output_path,) else: error_msg = "GLB文件保存失败" processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + return ("",) else: error_msg = "处理后没有剩余的点云数据" processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + return ("",) except Exception as e: error_msg = f"处理GLB文件时发生错误: {str(e)}" @@ -246,7 +227,7 @@ class GLBPointCloudProcessor: processing_log.append(f"错误: {error_msg}") import traceback traceback.print_exc() - return "", json.dumps({"error": error_msg}), "\n".join(processing_log) + return ("",) def _resolve_file_path(self, file_path: str) -> str: """解析文件路径,支持绝对路径和相对路径""" @@ -342,7 +323,7 @@ class GLBPointCloudBounds: "tooltip": "添加包围盒可视化:是否在输出的点云文件中添加红色包围盒线框点云,便于直接预览验证" }), "add_coordinate_axes": ("BOOLEAN", { - "default": True, + "default": False, "tooltip": "添加坐标轴:是否添加RGB坐标轴(X=红色,Y=绿色,Z=蓝色)到输出点云中" }), "wireframe_density": ("INT", { @@ -361,22 +342,16 @@ class GLBPointCloudBounds: } RETURN_TYPES = ( - "STRING", # JSON格式的scale数组 - "STRING", # 包围盒详细信息 "STRING", # 输出的GLB文件路径 - "STRING", # 处理日志 + "STRING", # JSON格式的scale数组 ) RETURN_NAMES = ( - "scale_json", - "bounds_info", "output_glb_path", - "processing_log", + "scale_json", ) OUTPUT_TOOLTIPS = [ - "JSON格式的scale数组: {\"scale\": [长, 宽, 高]} - 可直接用于3D引擎缩放", - "包围盒详细信息(JSON格式) - 包含中心点、最小点、最大点、体积等", "输出GLB文件路径 - 包含原始点云+包围盒可视化+坐标轴的完整点云文件", - "详细的处理日志信息" + "纯净的scale数组JSON: {\"scale\": [长, 宽, 高]} - 仅包含scale信息,可直接用于3D引擎缩放", ] OUTPUT_NODE = True FUNCTION = "calculate_bounds_and_visualize" @@ -403,14 +378,14 @@ class GLBPointCloudBounds: error_msg = "trimesh库不可用,无法处理GLB文件" logger.error(error_msg) processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "", "\n".join(processing_log) + return ("", "") # 验证输入文件路径 if not glb_file_path or not glb_file_path.strip(): error_msg = "GLB文件路径为空" logger.error(error_msg) processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "", "\n".join(processing_log) + return ("", "") # 处理文件路径 input_path = self._resolve_file_path(glb_file_path.strip()) @@ -421,7 +396,7 @@ class GLBPointCloudBounds: error_msg = f"GLB文件不存在: {input_path}" logger.error(error_msg) processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "", "\n".join(processing_log) + return ("", "") try: # 加载GLB文件 @@ -454,12 +429,12 @@ class GLBPointCloudBounds: else: error_msg = "GLB文件中未找到点云或几何体数据" processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "", "\n".join(processing_log) + return ("", "") if not all_vertices: error_msg = "GLB文件中没有可用的顶点数据" processing_log.append(f"错误: {error_msg}") - return "", json.dumps({"error": error_msg}), "", "\n".join(processing_log) + return ("", "") # 合并所有顶点 combined_vertices = np.vstack(all_vertices) @@ -476,16 +451,7 @@ class GLBPointCloudBounds: extents = max_point - min_point center = (min_point + max_point) / 2 - bounds_info = { - "type": "axis_aligned_bounding_box", - "min_point": min_point.tolist(), - "max_point": max_point.tolist(), - "center": center.tolist(), - "extents": extents.tolist(), - "volume": float(np.prod(extents)), - "total_points": int(total_points), - "point_clouds_count": int(point_cloud_count) - } + processing_log.append(f"AABB计算完成:") processing_log.append(f" 最小点: [{min_point[0]:.6f}, {min_point[1]:.6f}, {min_point[2]:.6f}]") @@ -502,15 +468,7 @@ class GLBPointCloudBounds: # 计算OBB的中心点和角点 obb_center = -to_origin[:3, 3] # 变换矩阵的平移部分的负值 - bounds_info = { - "type": "oriented_bounding_box", - "center": obb_center.tolist(), - "extents": obb_extents.tolist(), - "transform_matrix": to_origin.tolist(), - "volume": float(np.prod(obb_extents)), - "total_points": int(total_points), - "point_clouds_count": int(point_cloud_count) - } + extents = obb_extents @@ -527,16 +485,7 @@ class GLBPointCloudBounds: extents = max_point - min_point center = (min_point + max_point) / 2 - bounds_info = { - "type": "axis_aligned_bounding_box_fallback", - "min_point": min_point.tolist(), - "max_point": max_point.tolist(), - "center": center.tolist(), - "extents": extents.tolist(), - "volume": float(np.prod(extents)), - "total_points": int(total_points), - "point_clouds_count": int(point_cloud_count) - } + # 应用单位转换 unit_scale = {"meters": 1.0, "centimeters": 100.0, "millimeters": 1000.0} @@ -547,16 +496,10 @@ class GLBPointCloudBounds: # 生成scale JSON (按照 [长, 宽, 高] 的顺序,通常是 [X, Y, Z]) scale_array = [float(scaled_extents[0]), float(scaled_extents[1]), float(scaled_extents[2])] scale_json = { - "scale": scale_array, - "units": units, - "original_extents": extents.tolist(), - "scale_factor": scale_factor + "scale": scale_array } - # 添加单位信息到bounds_info - bounds_info["units"] = units - bounds_info["scale_factor"] = scale_factor - bounds_info["scaled_extents"] = scaled_extents.tolist() + processing_log.append(f"") processing_log.append(f"输出结果 (单位: {units}):") @@ -584,10 +527,8 @@ class GLBPointCloudBounds: processing_log.append("GLB点云包围盒计算和可视化完成!") return ( - json.dumps(scale_json, indent=2), - json.dumps(bounds_info, indent=2), output_glb_path, - "\n".join(processing_log) + json.dumps(scale_json, indent=2), ) except Exception as e: @@ -596,7 +537,7 @@ class GLBPointCloudBounds: processing_log.append(f"错误: {error_msg}") import traceback traceback.print_exc() - return "", json.dumps({"error": error_msg}), "", "\n".join(processing_log) + return ("", "") def _resolve_file_path(self, file_path: str) -> str: """解析文件路径,支持绝对路径和相对路径""" @@ -731,7 +672,7 @@ class GLBPointCloudBounds: # 添加坐标轴点云 if add_coordinate_axes: axes_vertices, axes_colors = self._create_coordinate_axes_pointcloud( - extents, wireframe_density, processing_log + extents, center, wireframe_density, processing_log ) if len(axes_vertices) > 0: all_vertices.append(axes_vertices) @@ -868,7 +809,7 @@ class GLBPointCloudBounds: processing_log.append(f"创建包围盒点云失败: {str(e)}") return np.array([]), np.array([]) - def _create_coordinate_axes_pointcloud(self, extents, wireframe_density, processing_log): + def _create_coordinate_axes_pointcloud(self, extents, origin_center, wireframe_density, processing_log): """创建坐标轴的点云表示""" try: axis_length = np.max(extents) * 0.4 @@ -880,10 +821,10 @@ class GLBPointCloudBounds: axis_line_density = wireframe_density axis_thickness_points = max(3, wireframe_density // 15) # 厚度方向的点数 - # X轴 - 红色(粗线条) + # X轴 - 红色(粗线条)- 从点云中心开始 for i in range(axis_line_density): t = i / (axis_line_density - 1) if axis_line_density > 1 else 0 - base_point = [t * axis_length, 0, 0] + base_point = [origin_center[0] + t * axis_length, origin_center[1], origin_center[2]] # 主轴线 axes_points.append(base_point) @@ -904,19 +845,19 @@ class GLBPointCloudBounds: for i in range(axis_line_density // 2): t = i / (axis_line_density // 2 - 1) if axis_line_density > 2 else 0 # 箭头点 - arrow_x = arrow_base + t * arrow_length + arrow_x = origin_center[0] + arrow_base + t * arrow_length arrow_offset = (1 - t) * axis_thickness * 2 - axes_points.append([arrow_x, arrow_offset, 0]) - axes_points.append([arrow_x, -arrow_offset, 0]) - axes_points.append([arrow_x, 0, arrow_offset]) - axes_points.append([arrow_x, 0, -arrow_offset]) + axes_points.append([arrow_x, origin_center[1] + arrow_offset, origin_center[2]]) + axes_points.append([arrow_x, origin_center[1] - arrow_offset, origin_center[2]]) + axes_points.append([arrow_x, origin_center[1], origin_center[2] + arrow_offset]) + axes_points.append([arrow_x, origin_center[1], origin_center[2] - arrow_offset]) axes_colors.extend([[255, 0, 0, 255]] * 4) - # Y轴 - 绿色(粗线条) + # Y轴 - 绿色(粗线条)- 从点云中心开始 for i in range(axis_line_density): t = i / (axis_line_density - 1) if axis_line_density > 1 else 0 - base_point = [0, t * axis_length, 0] + base_point = [origin_center[0], origin_center[1] + t * axis_length, origin_center[2]] # 主轴线 axes_points.append(base_point) @@ -934,19 +875,19 @@ class GLBPointCloudBounds: # Y轴箭头头部 for i in range(axis_line_density // 2): t = i / (axis_line_density // 2 - 1) if axis_line_density > 2 else 0 - arrow_y = arrow_base + t * arrow_length + arrow_y = origin_center[1] + arrow_base + t * arrow_length arrow_offset = (1 - t) * axis_thickness * 2 - axes_points.append([arrow_offset, arrow_y, 0]) - axes_points.append([-arrow_offset, arrow_y, 0]) - axes_points.append([0, arrow_y, arrow_offset]) - axes_points.append([0, arrow_y, -arrow_offset]) + axes_points.append([origin_center[0] + arrow_offset, arrow_y, origin_center[2]]) + axes_points.append([origin_center[0] - arrow_offset, arrow_y, origin_center[2]]) + axes_points.append([origin_center[0], arrow_y, origin_center[2] + arrow_offset]) + axes_points.append([origin_center[0], arrow_y, origin_center[2] - arrow_offset]) axes_colors.extend([[0, 255, 0, 255]] * 4) - # Z轴 - 蓝色(粗线条) + # Z轴 - 蓝色(粗线条)- 从点云中心开始 for i in range(axis_line_density): t = i / (axis_line_density - 1) if axis_line_density > 1 else 0 - base_point = [0, 0, t * axis_length] + base_point = [origin_center[0], origin_center[1], origin_center[2] + t * axis_length] # 主轴线 axes_points.append(base_point) @@ -964,19 +905,19 @@ class GLBPointCloudBounds: # Z轴箭头头部 for i in range(axis_line_density // 2): t = i / (axis_line_density // 2 - 1) if axis_line_density > 2 else 0 - arrow_z = arrow_base + t * arrow_length + arrow_z = origin_center[2] + arrow_base + t * arrow_length arrow_offset = (1 - t) * axis_thickness * 2 - axes_points.append([arrow_offset, 0, arrow_z]) - axes_points.append([-arrow_offset, 0, arrow_z]) - axes_points.append([0, arrow_offset, arrow_z]) - axes_points.append([0, -arrow_offset, arrow_z]) + axes_points.append([origin_center[0] + arrow_offset, origin_center[1], arrow_z]) + axes_points.append([origin_center[0] - arrow_offset, origin_center[1], arrow_z]) + axes_points.append([origin_center[0], origin_center[1] + arrow_offset, arrow_z]) + axes_points.append([origin_center[0], origin_center[1] - arrow_offset, arrow_z]) axes_colors.extend([[0, 0, 255, 255]] * 4) axes_vertices = np.array(axes_points) axes_colors_array = np.array(axes_colors) - processing_log.append(f"坐标轴生成: {len(axes_vertices):,} 个点 (长度={axis_length:.3f})") + processing_log.append(f"坐标轴生成: {len(axes_vertices):,} 个点 (长度={axis_length:.3f}, 原点=[{origin_center[0]:.3f}, {origin_center[1]:.3f}, {origin_center[2]:.3f}])") return axes_vertices, axes_colors_array @@ -1006,7 +947,418 @@ class GLBPointCloudBounds: return os.path.join(output_dir, timestamped_filename) +class GLBPointCloudOriginAdjuster: + """GLB点云原点调整器 - 重新设置点云的原点位置并输出变换信息""" + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "glb_file_path": ("STRING", { + "default": "", + "tooltip": "GLB点云文件路径:输入需要调整原点的GLB格式点云文件" + }), + }, + "optional": { + "origin_mode": (["center", "bottom_center"], { + "default": "bottom_center", + "tooltip": "原点模式:center=点云几何中心;bottom_center=点云底部中心(脚底)" + }), + "output_units": (["meters", "centimeters", "millimeters"], { + "default": "centimeters", + "tooltip": "输出单位:变换信息的输出单位" + }), + "add_coordinate_axes": ("BOOLEAN", { + "default": True, + "tooltip": "添加坐标轴:是否添加RGB坐标轴(X=红色,Y=绿色,Z=蓝色)到输出点云中,显示调整后的原点位置" + }), + "wireframe_density": ("INT", { + "default": 100, "min": 20, "max": 200, "step": 10, + "tooltip": "坐标轴密度:每条坐标轴的点数,越高坐标轴越清晰但点数越多。推荐80-120获得清晰效果" + }), + "output_filename": ("STRING", { + "default": "adjusted_pointcloud.glb", + "tooltip": "输出文件名:调整后的点云文件名" + }), + } + } + + RETURN_TYPES = ( + "STRING", # 处理后的GLB文件路径 + "STRING", # 变换信息JSON (position) + ) + RETURN_NAMES = ( + "adjusted_glb_path", + "transform_info", + ) + OUTPUT_TOOLTIPS = [ + "调整后的GLB文件完整路径", + "纯净的position信息JSON: {\"position\": [x, y, z]} - 仅包含位置信息,可直接用于UE等引擎", + ] + OUTPUT_NODE = True + FUNCTION = "adjust_origin" + CATEGORY = "💃VVL/Point Cloud Transform" + + def adjust_origin(self, + glb_file_path: str, + origin_mode: str = "bottom_center", + output_units: str = "centimeters", + add_coordinate_axes: bool = False, + wireframe_density: int = 100, + output_filename: str = "adjusted_pointcloud.glb"): + """ + 调整GLB点云的原点位置和旋转 + """ + + processing_log = [] + processing_log.append("开始GLB点云原点调整...") + + # 检查依赖 + if not TRIMESH_AVAILABLE: + error_msg = "trimesh库不可用,无法处理GLB文件" + logger.error(error_msg) + processing_log.append(f"错误: {error_msg}") + return ("", "") + + # 验证输入文件路径 + if not glb_file_path or not glb_file_path.strip(): + error_msg = "GLB文件路径为空" + logger.error(error_msg) + processing_log.append(f"错误: {error_msg}") + return ("", "") + + # 处理文件路径 + input_path = self._resolve_file_path(glb_file_path.strip()) + processing_log.append(f"输入文件路径: {input_path}") + + if not os.path.exists(input_path): + error_msg = f"GLB文件不存在: {input_path}" + logger.error(error_msg) + processing_log.append(f"错误: {error_msg}") + return ("", "") + + try: + # 加载GLB文件 + processing_log.append("正在加载GLB文件...") + scene = trimesh.load(input_path) + + # 收集所有几何体和点云 + geometries_to_transform = [] + all_vertices = [] + total_points = 0 + + if isinstance(scene, trimesh.Scene): + for name, geometry in scene.geometry.items(): + geometries_to_transform.append((name, geometry)) + if hasattr(geometry, 'vertices') and geometry.vertices is not None: + all_vertices.append(geometry.vertices) + total_points += len(geometry.vertices) + processing_log.append(f"发现几何体: {name}, 顶点数: {len(geometry.vertices)}") + else: + # 单个几何体 + geometries_to_transform.append(("main_geometry", scene)) + if hasattr(scene, 'vertices') and scene.vertices is not None: + all_vertices.append(scene.vertices) + total_points = len(scene.vertices) + processing_log.append(f"发现几何体: main_geometry, 顶点数: {total_points}") + + if not all_vertices: + error_msg = "GLB文件中没有可用的顶点数据" + processing_log.append(f"错误: {error_msg}") + return ("", "") + + # 合并所有顶点计算包围盒 + combined_vertices = np.vstack(all_vertices) + processing_log.append(f"总顶点数: {total_points:,}") + + # 计算原始包围盒 + original_min = np.min(combined_vertices, axis=0) + original_max = np.max(combined_vertices, axis=0) + original_center = (original_min + original_max) / 2 + original_size = original_max - original_min + + processing_log.append(f"原始包围盒:") + processing_log.append(f" 最小点: [{original_min[0]:.6f}, {original_min[1]:.6f}, {original_min[2]:.6f}]") + processing_log.append(f" 最大点: [{original_max[0]:.6f}, {original_max[1]:.6f}, {original_max[2]:.6f}]") + processing_log.append(f" 中心点: [{original_center[0]:.6f}, {original_center[1]:.6f}, {original_center[2]:.6f}]") + processing_log.append(f" 尺寸: [{original_size[0]:.6f}, {original_size[1]:.6f}, {original_size[2]:.6f}]") + + # 确定新的原点位置 + if origin_mode == "center": + new_origin = original_center.copy() + processing_log.append("原点模式: 几何中心") + else: # bottom_center + new_origin = np.array([original_center[0], original_center[1], original_min[2]]) + processing_log.append("原点模式: 底部中心(脚底)") + + # 计算平移向量 + translation = -new_origin + processing_log.append(f"平移向量: [{translation[0]:.6f}, {translation[1]:.6f}, {translation[2]:.6f}]") + + # 创建平移变换矩阵(仅平移,无旋转) + transform_matrix = np.eye(4) + transform_matrix[:3, 3] = translation + + # 应用变换到所有几何体 + new_scene = trimesh.Scene() + transformed_vertices_list = [] + + for name, geometry in geometries_to_transform: + # 复制几何体 + new_geometry = geometry.copy() + + # 应用变换 + new_geometry.apply_transform(transform_matrix) + + # 添加到新场景 + new_scene.add_geometry(new_geometry, node_name=name) + + # 收集变换后的顶点用于统计 + if hasattr(new_geometry, 'vertices') and new_geometry.vertices is not None: + transformed_vertices_list.append(new_geometry.vertices) + + processing_log.append(f"已变换几何体: {name}") + + # 添加坐标轴预览(在变换后的原点位置,即(0,0,0)) + if add_coordinate_axes: + try: + # 计算变换后的包围盒来确定坐标轴长度 + if transformed_vertices_list: + all_transformed_vertices = np.vstack(transformed_vertices_list) + transformed_extents = np.max(all_transformed_vertices, axis=0) - np.min(all_transformed_vertices, axis=0) + axis_length = np.max(transformed_extents) * 0.4 + else: + axis_length = 1.0 # 默认长度 + + # 在新的原点(0,0,0)处生成坐标轴 + axes_vertices, axes_colors = self._create_coordinate_axes_pointcloud_at_origin( + axis_length, wireframe_density, processing_log + ) + + if len(axes_vertices) > 0: + # 创建坐标轴点云 + axes_pointcloud = trimesh.PointCloud(vertices=axes_vertices, colors=axes_colors) + new_scene.add_geometry(axes_pointcloud, node_name="coordinate_axes") + processing_log.append(f"坐标轴预览: {len(axes_vertices):,} 个点 (显示新的原点位置)") + + except Exception as e: + processing_log.append(f"坐标轴生成失败: {str(e)}") + + # 计算变换后的包围盒 + if transformed_vertices_list: + transformed_vertices = np.vstack(transformed_vertices_list) + new_min = np.min(transformed_vertices, axis=0) + new_max = np.max(transformed_vertices, axis=0) + new_center = (new_min + new_max) / 2 + new_size = new_max - new_min + + processing_log.append(f"变换后包围盒:") + processing_log.append(f" 最小点: [{new_min[0]:.6f}, {new_min[1]:.6f}, {new_min[2]:.6f}]") + processing_log.append(f" 最大点: [{new_max[0]:.6f}, {new_max[1]:.6f}, {new_max[2]:.6f}]") + processing_log.append(f" 中心点: [{new_center[0]:.6f}, {new_center[1]:.6f}, {new_center[2]:.6f}]") + processing_log.append(f" 尺寸: [{new_size[0]:.6f}, {new_size[1]:.6f}, {new_size[2]:.6f}]") + + # 生成输出文件 + output_path = self._generate_output_path(output_filename) + processing_log.append(f"输出文件路径: {output_path}") + + # 保存变换后的GLB文件 + processing_log.append("正在保存变换后的GLB文件...") + new_scene.export(output_path) + + if os.path.exists(output_path): + file_size = os.path.getsize(output_path) + processing_log.append(f"GLB文件保存成功,文件大小: {file_size} bytes") + else: + error_msg = "GLB文件保存失败" + processing_log.append(f"错误: {error_msg}") + return ("", "") + + # 应用单位转换 + unit_scale = {"meters": 1.0, "centimeters": 100.0, "millimeters": 1000.0} + scale_factor = unit_scale.get(output_units, 1.0) + + # 生成变换信息(UE格式) + final_position = new_origin * scale_factor # 相对于原始坐标系的位置 + transform_info = { + "position": [float(final_position[0]), float(final_position[1]), float(final_position[2])] + } + + + + processing_log.append("") + processing_log.append(f"变换信息 (单位: {output_units}):") + processing_log.append(f" Position: [{transform_info['position'][0]:.2f}, {transform_info['position'][1]:.2f}, {transform_info['position'][2]:.2f}]") + processing_log.append("GLB点云原点调整完成!") + + return ( + output_path, + json.dumps(transform_info, indent=2), + ) + + except Exception as e: + error_msg = f"调整原点时发生错误: {str(e)}" + logger.error(error_msg) + processing_log.append(f"错误: {error_msg}") + import traceback + traceback.print_exc() + return ("", "") + + def _resolve_file_path(self, file_path: str) -> str: + """解析文件路径,支持绝对路径和相对路径""" + if os.path.isabs(file_path): + return file_path + + # 尝试相对于ComfyUI输出目录 + if FOLDER_PATHS_AVAILABLE: + output_dir = folder_paths.get_output_directory() + candidate_path = os.path.join(output_dir, file_path) + if os.path.exists(candidate_path): + return candidate_path + + # 尝试相对于当前工作目录 + if os.path.exists(file_path): + return os.path.abspath(file_path) + + # 返回原始路径(让后续检查处理错误) + return file_path + + def _generate_output_path(self, filename: str) -> str: + """生成输出文件路径""" + # 确保文件名有正确的扩展名 + if not filename.lower().endswith('.glb'): + filename += '.glb' + + # 生成输出路径 + output_dir = folder_paths.get_output_directory() if FOLDER_PATHS_AVAILABLE else "output" + os.makedirs(output_dir, exist_ok=True) + + # 添加时间戳避免文件名冲突 + timestamp = str(int(time.time())) + name_parts = filename.rsplit('.', 1) + if len(name_parts) == 2: + timestamped_filename = f"{name_parts[0]}_{timestamp}.{name_parts[1]}" + else: + timestamped_filename = f"{filename}_{timestamp}" + + return os.path.join(output_dir, timestamped_filename) + + + + def _create_coordinate_axes_pointcloud_at_origin(self, axis_length, wireframe_density, processing_log): + """在原点(0,0,0)创建坐标轴的点云表示""" + try: + origin_center = np.array([0.0, 0.0, 0.0]) # 新的原点位置 + axis_thickness = axis_length * 0.01 # 坐标轴厚度 + axes_points = [] + axes_colors = [] + + # 计算坐标轴密度 + axis_line_density = wireframe_density # 使用参数控制密度 + axis_thickness_points = max(3, axis_line_density // 15) # 厚度方向的点数 + + # X轴 - 红色(从原点开始) + for i in range(axis_line_density): + t = i / (axis_line_density - 1) if axis_line_density > 1 else 0 + base_point = [origin_center[0] + t * axis_length, origin_center[1], origin_center[2]] + + # 主轴线 + axes_points.append(base_point) + axes_colors.append([255, 0, 0, 255]) + + # 增加厚度(在YZ平面上添加点) + for j in range(axis_thickness_points): + for k in range(axis_thickness_points): + offset_y = (j - axis_thickness_points//2) * axis_thickness / axis_thickness_points + offset_z = (k - axis_thickness_points//2) * axis_thickness / axis_thickness_points + thick_point = [base_point[0], base_point[1] + offset_y, base_point[2] + offset_z] + axes_points.append(thick_point) + axes_colors.append([255, 0, 0, 255]) + + # X轴箭头头部 + arrow_length = axis_length * 0.1 + arrow_base = axis_length * 0.9 + for i in range(axis_line_density // 2): + t = i / (axis_line_density // 2 - 1) if axis_line_density > 2 else 0 + arrow_x = origin_center[0] + arrow_base + t * arrow_length + arrow_offset = (1 - t) * axis_thickness * 2 + + axes_points.append([arrow_x, origin_center[1] + arrow_offset, origin_center[2]]) + axes_points.append([arrow_x, origin_center[1] - arrow_offset, origin_center[2]]) + axes_points.append([arrow_x, origin_center[1], origin_center[2] + arrow_offset]) + axes_points.append([arrow_x, origin_center[1], origin_center[2] - arrow_offset]) + axes_colors.extend([[255, 0, 0, 255]] * 4) + + # Y轴 - 绿色(从原点开始) + for i in range(axis_line_density): + t = i / (axis_line_density - 1) if axis_line_density > 1 else 0 + base_point = [origin_center[0], origin_center[1] + t * axis_length, origin_center[2]] + + # 主轴线 + axes_points.append(base_point) + axes_colors.append([0, 255, 0, 255]) + + # 增加厚度(在XZ平面上添加点) + for j in range(axis_thickness_points): + for k in range(axis_thickness_points): + offset_x = (j - axis_thickness_points//2) * axis_thickness / axis_thickness_points + offset_z = (k - axis_thickness_points//2) * axis_thickness / axis_thickness_points + thick_point = [base_point[0] + offset_x, base_point[1], base_point[2] + offset_z] + axes_points.append(thick_point) + axes_colors.append([0, 255, 0, 255]) + + # Y轴箭头头部 + for i in range(axis_line_density // 2): + t = i / (axis_line_density // 2 - 1) if axis_line_density > 2 else 0 + arrow_y = origin_center[1] + arrow_base + t * arrow_length + arrow_offset = (1 - t) * axis_thickness * 2 + + axes_points.append([origin_center[0] + arrow_offset, arrow_y, origin_center[2]]) + axes_points.append([origin_center[0] - arrow_offset, arrow_y, origin_center[2]]) + axes_points.append([origin_center[0], arrow_y, origin_center[2] + arrow_offset]) + axes_points.append([origin_center[0], arrow_y, origin_center[2] - arrow_offset]) + axes_colors.extend([[0, 255, 0, 255]] * 4) + + # Z轴 - 蓝色(从原点开始) + for i in range(axis_line_density): + t = i / (axis_line_density - 1) if axis_line_density > 1 else 0 + base_point = [origin_center[0], origin_center[1], origin_center[2] + t * axis_length] + + # 主轴线 + axes_points.append(base_point) + axes_colors.append([0, 0, 255, 255]) + + # 增加厚度(在XY平面上添加点) + for j in range(axis_thickness_points): + for k in range(axis_thickness_points): + offset_x = (j - axis_thickness_points//2) * axis_thickness / axis_thickness_points + offset_y = (k - axis_thickness_points//2) * axis_thickness / axis_thickness_points + thick_point = [base_point[0] + offset_x, base_point[1] + offset_y, base_point[2]] + axes_points.append(thick_point) + axes_colors.append([0, 0, 255, 255]) + + # Z轴箭头头部 + for i in range(axis_line_density // 2): + t = i / (axis_line_density // 2 - 1) if axis_line_density > 2 else 0 + arrow_z = origin_center[2] + arrow_base + t * arrow_length + arrow_offset = (1 - t) * axis_thickness * 2 + + axes_points.append([origin_center[0] + arrow_offset, origin_center[1], arrow_z]) + axes_points.append([origin_center[0] - arrow_offset, origin_center[1], arrow_z]) + axes_points.append([origin_center[0], origin_center[1] + arrow_offset, arrow_z]) + axes_points.append([origin_center[0], origin_center[1] - arrow_offset, arrow_z]) + axes_colors.extend([[0, 0, 255, 255]] * 4) + + axes_vertices = np.array(axes_points) + axes_colors_array = np.array(axes_colors) + + processing_log.append(f"坐标轴生成: {len(axes_vertices):,} 个点 (长度={axis_length:.3f}, 密度={wireframe_density}, 原点=[0.000, 0.000, 0.000])") + + return axes_vertices, axes_colors_array + + except Exception as e: + processing_log.append(f"创建坐标轴点云失败: {str(e)}") + return np.array([]), np.array([]) # ----------------------------------------------------------------------------- @@ -1016,11 +1368,13 @@ class GLBPointCloudBounds: NODE_CLASS_MAPPINGS = { "GLBPointCloudProcessor": GLBPointCloudProcessor, "GLBPointCloudBounds": GLBPointCloudBounds, + "GLBPointCloudOriginAdjuster": GLBPointCloudOriginAdjuster, } NODE_DISPLAY_NAME_MAPPINGS = { "GLBPointCloudProcessor": "VVL GLB Point Cloud Processor", "GLBPointCloudBounds": "VVL GLB Point Cloud Bounds Visualizer", + "GLBPointCloudOriginAdjuster": "VVL GLB Point Cloud Origin Adjuster", } # # 添加节点信息,帮助ComfyUI更好地识别