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.
497 lines
16 KiB
497 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_factory_type_dir |
|
from config import print_data_dir |
|
|
|
from general import mesh_transform_by_matrix |
|
from general import read_mesh |
|
from general import get_blank_path |
|
|
|
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 (index > 80): |
|
# 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 |
|
|
|
print("set_orthographic 1") |
|
#for _ in range(2): |
|
# vis.poll_events() |
|
# vis.update_renderer() |
|
|
|
# 设置正交投影 |
|
try: |
|
set_orthographic_camera(view_control) |
|
print("set_orthographic 2") |
|
except AttributeError: |
|
set_orthographic_projection(view_control, ortho_scale=15.0) |
|
print("set_orthographic 3") |
|
|
|
#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) |
|
|