update
This commit is contained in:
@@ -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']
|
||||
|
||||
@@ -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
@@ -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更好地识别
|
||||
|
||||
Reference in New Issue
Block a user