This commit is contained in:
wTechArtist
2025-06-18 15:02:10 +08:00
parent 1e5b620e92
commit 0dc115fd23
3 changed files with 466 additions and 110 deletions
+2
View File
@@ -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']
+1 -1
View File
@@ -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", {
+463 -109
View File
@@ -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更好地识别