You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

496 lines
16 KiB

import open3d as o3d
import numpy as np
import json
import os
from PIL import Image
import argparse
import gc
from config import print_data_dir
from config import test_print_max
from general import mesh_transform_by_matrix
from general import get_blank_path
from compute_print_net_out import read_mesh
def load_and_transform_models(base_path, dict_origin, json_name):
meshes = [] # 存储所有变换后的网格
json_path = os.path.join(base_path, json_name)
"""
加载JSON文件,读取所有模型信息,应用变换后返回模型列表
"""
# 检查JSON文件是否存在
if not os.path.exists(json_path):
print(f"错误: JSON文件不存在 - {json_path}")
return []
# 读取JSON文件
try:
with open(json_path, 'r') as f:
data = json.load(f)
except Exception as e:
print(f"读取JSON文件失败: {e}")
return []
selected_machine = data.get('summary')['selected_machine']
print(f"选择机型={selected_machine}")
is_small_machine = True if selected_machine=="小机型" else False
blank_path = get_blank_path(is_small_machine)
index = 0
# 处理每个模型
for model in data.get('models', []):
obj_name = model.get('file_name', '')
obj_path = os.path.join(base_path, obj_name)
# 检查文件路径是否存在
if not obj_path:
print("警告: 跳过缺少文件路径的模型")
continue
# print("load path", obj_path)
# 检查文件是否存在
if not os.path.exists(obj_path):
print(f"警告: 模型文件不存在 - {obj_path}")
continue
# 加载网格
try:
# print("dict_origin obj_path=", obj_path)
key = obj_path
if key in dict_origin:
# print("key in dict_origin")
mesh_obj = dict_origin[key]
else :
# print("key not in dict_origin")
mesh_obj = read_mesh(obj_path,True)
if not mesh_obj.has_vertices():
print(f"警告: 网格无有效顶点 - {obj_path}")
continue
except Exception as e:
print(f"加载模型失败: {obj_path} - {e}")
continue
transform = model.get('transform', {})
homo_matrix = transform["homo_matrix"] # 获取存储的列表
reconstructed_matrix = np.array(homo_matrix, dtype=np.float64)
# 手动变换顶点
original_vertices = np.asarray(mesh_obj.vertices)
transformed_vertices = mesh_transform_by_matrix(original_vertices, reconstructed_matrix)
mesh_obj.vertices = o3d.utility.Vector3dVector(transformed_vertices)
del original_vertices, transformed_vertices, reconstructed_matrix
# 添加到列表
meshes.append(mesh_obj)
del mesh_obj
gc.collect()
print(f"已加载并变换: {index} {os.path.basename(obj_path)}")
index = index + 1
if test_print_max > 0:
if (index > test_print_max):
break
add_plank = True
if add_plank:
obj_path = blank_path
print("add_plank",obj_path)
try:
mesh = read_mesh(obj_path, True)
if not mesh.has_vertices():
print(f"警告: 网格无有效顶点 - {obj_path}")
except Exception as e:
print(f"加载模型失败: {obj_path} - {e}")
rotation = [0.0, 0.0, 0.0]
mesh_center = compute_mesh_center(mesh.vertices)
R_x = mesh.get_rotation_matrix_from_axis_angle(np.array([1, 0, 0]) * np.radians(rotation[0]))
mesh.rotate(R_x,center=mesh_center)
R_y = mesh.get_rotation_matrix_from_axis_angle(np.array([0, 1, 0]) * np.radians(rotation[1]))
mesh.rotate(R_y,center=mesh_center)
R_z = mesh.get_rotation_matrix_from_axis_angle(np.array([0, 0, 1]) * np.radians(rotation[2]))
mesh.rotate(R_z,center=mesh_center)
displacement = [0.0, 0.0, 0.0]
displacement = np.asarray(displacement)
mesh.translate(displacement)
meshes.append(mesh)
# print(f"已加载并变换: {obj_path}")
return meshes
def compute_mesh_center(vertices):
if len(vertices) == 0:
raise ValueError("顶点数组不能为空")
n = len(vertices) # 顶点数量
# 初始化坐标累加器
sum_x, sum_y, sum_z = 0.0, 0.0, 0.0
# 遍历所有顶点累加坐标值
for vertex in vertices:
sum_x += vertex[0]
sum_y += vertex[1]
sum_z += vertex[2]
# 计算各坐标轴的平均值
centroid = np.array([sum_x / n, sum_y / n, sum_z / n])
return centroid
def load_and_show(base_path, json_name="3DPrintLayout.json"):
# 加载并变换所有模型
transformed_meshes = load_and_transform_models(base_path, {}, json_name)
if not transformed_meshes:
print("没有加载到任何模型,请检查错误信息")
else:
print(f"成功加载并变换了 {len(transformed_meshes)} 个模型")
# 可视化所有模型
print("显示所有模型... (按'Q'退出)")
try:
from packaging import version
o3d_version = version.parse(o3d.__version__)
# 新版本 draw_geometries 参数
if o3d_version >= version.parse("0.13.0"):
o3d.visualization.draw_geometries(
transformed_meshes,
window_name="模型展示",
mesh_show_back_face=True,
mesh_show_wireframe=False
)
# 旧版本 draw_geometries 参数
else:
o3d.visualization.draw_geometries(
transformed_meshes,
window_name="模型展示",
point_show_normal=False,
mesh_show_back_face=True
)
except Exception as e:
print(f"使用 draw_geometries 可视化失败: {e}")
def setup_orthographic_camera(vis, meshes, ortho_width=15.0, camera_height=20.0):
"""
设置精确的正交投影相机
"""
view_control = vis.get_view_control()
# 计算场景边界框以确定合适的正交参数
all_points = []
for mesh in meshes:
if hasattr(mesh, 'vertices'):
points = np.asarray(mesh.vertices)
else:
points = np.asarray(mesh.points)
all_points.append(points)
if all_points:
all_points = np.vstack(all_points)
bbox_min = np.min(all_points, axis=0)
bbox_max = np.max(all_points, axis=0)
scene_center = (bbox_min + bbox_max) / 2
scene_size = np.max(bbox_max - bbox_min)
# 设置观察点为场景中心
view_control.set_lookat(scene_center)
# 设置相机在场景上方,俯视场景
view_control.set_front([0, 0, -1]) # 看向负Z轴(从上向下)
view_control.set_up([0, 1, 0]) # Y轴向上
try:
# 启用正交投影
view_control.set_orthogonal(True)
# 根据场景大小设置正交投影宽度
view_control.set_orthogonal_width(max(scene_size * 1.2, ortho_width))
print(f"正交投影已设置: 宽度={max(scene_size * 1.2, ortho_width):.2f}")
except AttributeError:
# 回退到透视投影模拟
view_control.set_zoom(0.1)
print("使用透视投影模拟正交效果")
return True
def auto_fit_to_view(vis, meshes):
"""
自动调整视图以显示所有模型
"""
view_control = vis.get_view_control()
# 方法1: 使用 Open3D 的自动适配功能
try:
view_control.fit_to_geometry(meshes)
print("已自动适配视图以显示所有模型")
return True
except:
pass
# 方法2: 手动计算并设置
all_points = []
for mesh in meshes:
if hasattr(mesh, 'vertices'):
points = np.asarray(mesh.vertices)
elif hasattr(mesh, 'points'):
points = np.asarray(mesh.points)
else:
continue
all_points.append(points)
if all_points:
all_points = np.vstack(all_points)
bbox_min = np.min(all_points, axis=0)
bbox_max = np.max(all_points, axis=0)
scene_center = (bbox_min + bbox_max) / 2
scene_size = np.max(bbox_max - bbox_min)
# 设置合适的视角和缩放
view_control.set_lookat(scene_center)
# 根据场景大小设置 zoom
zoom_level = max(0.05, min(1.0, 10.0 / scene_size))
zoom_level = 0.5
view_control.set_zoom(zoom_level)
print(f"手动适配视图: 场景大小 {scene_size:.2f}, zoom {zoom_level:.3f}")
return True
return False
def set_orthographic_camera(view_control, desired_width=1920, desired_height=1080):
"""
通过相机参数设置正交投影,并确保尺寸匹配
"""
# 更可靠的方式:使用你创建窗口时已知的尺寸
# 假设你创建窗口时使用的是 desired_width 和 desired_height
param = view_control.convert_to_pinhole_camera_parameters()
# 修改内参,确保使用与创建窗口时一致的尺寸
param.intrinsic.set_intrinsics(
width=desired_width, # 必须与create_window的width一致
height=desired_height, # 必须与create_window的height一致
fx=1000.0, # 对于正交投影,此值意义不同,但仍需合理设置
fy=1000.0, # 避免使用1.0这样的极端值
cx=desired_width / 2,
cy=desired_height / 2
)
# 同时,仍需通过ViewControl启用正交投影
view_control.set_orthogonal(True)
view_control.convert_from_pinhole_camera_parameters(param)
def set_orthographic_projection(view_control, ortho_scale=10.0):
"""
尝试使用 ViewControl 接口配置正交投影效果。
参数:
vis: Open3D 可视化器实例
ortho_scale: 控制正交投影的“视野”大小,值越大,场景中的物体看起来越小。
"""
try:
# 1. 尝试设置一个非常小的视野(Field of View)来减少透视感
# 注意:此方法可能在某些版本中效果不明显,但它是可用的最接近正交投影的直接控制
view_control.change_field_of_view(step=-5) # 尝试将FOV调到极小[1](@ref)
# 2. 设置固定的近、远裁剪平面,避免因自动计算带来的透视变形
# 这对于保持缩放时物体大小一致很重要
view_control.set_constant_z_near(0.1) # 设置一个固定的近平面
view_control.set_constant_z_far(1000.0) # 设置一个固定的远平面[1](@ref)
# 3. 使用一个较小的缩放值(Zoom),这有助于让视角更“平行”
# 在正交投影的模拟中,通常使用较大的zoom值(>1)来拉远相机,减少透视效果。
# 但根据实际测试,您可能需要尝试一个具体的值,例如 0.5 或 0.1
view_control.set_zoom(0.46) # 这个值需要根据你的场景进行调整[6,7](@ref)
# print("已尝试通过 ViewControl 配置正交投影参数。")
return True
except Exception as e:
print(f"在配置 ViewControl 参数时出错: {e}")
return False
def set_orthographic(meshes, width=1920, height=1080,
background_color=[1, 1, 1], camera_position=None,
ortho_width=None, zoom=1.0):
vis = o3d.visualization.Visualizer()
vis.create_window(width=width, height=height, visible=False)
# 设置渲染选项
render_opt = vis.get_render_option()
render_opt.background_color = np.asarray(background_color)
render_opt.mesh_show_back_face = True
render_opt.mesh_show_wireframe = False
render_opt.point_size = 3.0
all_points = []
index = 0
# 添加几何体
for mesh in meshes:
vis.add_geometry(mesh)
if hasattr(mesh, 'vertices'):
points = np.asarray(mesh.vertices)
elif hasattr(mesh, 'points'):
points = np.asarray(mesh.points)
else:
continue
if len(points) > 0:
all_points.append(points)
del mesh
del points
gc.collect()
# print(f"set_orthographic {index}")
index = index + 1
del meshes
gc.collect()
# 视角控制
view_control = vis.get_view_control()
if len(all_points) > 0:
all_points_v = np.vstack(all_points)
del all_points
gc.collect()
bbox_min = np.min(all_points_v, axis=0)
bbox_max = np.max(all_points_v, axis=0)
bbox_center = (bbox_min + bbox_max) / 2.0
bbox_size = np.max(bbox_max - bbox_min)
# 设置相机视角,看向场景中心
if not camera_position:
# 默认顶视图
view_control.set_front([0, 0, 1])
view_control.set_lookat(bbox_center)
view_control.set_up([0, 1, 0])
else:
view_control.set_front(camera_position['front'])
view_control.set_lookat(camera_position['lookat'])
view_control.set_up(camera_position['up'])
# 自适应设置正交投影宽度,考虑屏幕宽高比
if ortho_width is None:
aspect_ratio = width / height
ortho_width = bbox_size * 1.5
# 根据宽高比调整,确保场景适合窗口
if aspect_ratio > 1:
ortho_width *= aspect_ratio
if ortho_width <= 0:
ortho_width = 10.0
del all_points_v
gc.collect()
else:
# 如果没有顶点,使用默认值
if not camera_position:
view_control.set_front([0, 0, 1])
view_control.set_lookat([0, 0, 0])
view_control.set_up([0, 1, 0])
ortho_width = ortho_width or 10.0
#for _ in range(2):
# vis.poll_events()
# vis.update_renderer()
# 设置正交投影
try:
set_orthographic_camera(view_control)
except AttributeError:
set_orthographic_projection(view_control, ortho_scale=15.0)
#for _ in range(5):
# vis.poll_events()
# vis.update_renderer()
print("set_orthographic End")
return vis
def render_to_texture(meshes, output_image_path):
vis = set_orthographic(meshes)
# 渲染并保存
vis.capture_screen_image(output_image_path, do_render=True)
print(f"高级渲染图片已保存到: {output_image_path}")
return vis
def load_show_save(base_path, dict_origin, batch_id, is_show=False):
folder_name = batch_id
json_name = f"{batch_id}.json"
output_image_path = os.path.join(base_path, f"{folder_name}.jpg")
# 加载并变换所有模型
transformed_meshes = load_and_transform_models(base_path, dict_origin, json_name)
if not transformed_meshes:
print("没有加载到任何模型,请检查错误信息")
else:
print(f"成功加载并变换了 {len(transformed_meshes)} 个模型")
render_to_texture(transformed_meshes, output_image_path)
if is_show:
# 可视化所有模型
print("显示所有模型... (按'Q'退出)")
vis = o3d.visualization.Visualizer()
vis.create_window(window_name="正交投影模型展示")
# 添加所有网格到可视化器
for mesh in transformed_meshes:
vis.add_geometry(mesh)
vis.poll_events()
vis.update_renderer()
# 获取渲染选项并设置
render_option = vis.get_render_option()
render_option.background_color = np.array([0.9, 0.9, 0.9]) # 浅灰色背景
render_option.mesh_show_back_face = True
render_option.mesh_show_wireframe = False
# 更新渲染器
vis.update_renderer()
# 运行可视化
vis.run()
vis.destroy_window()
# 主程序
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--batch_id", type=str, required=False, help="batch_id")
args = parser.parse_args()
# batch_id = args.batch_id
batch_id = "9910032"
base_path = f"{print_data_dir}{batch_id}/"
load_show_save(base_path, {}, batch_id, False)