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 config import version 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 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 from PIL import Image, ImageDraw, ImageFont def render_to_texture(meshes, output_image_path): vis = set_orthographic(meshes) # 渲染并保存 vis.capture_screen_image(output_image_path, do_render=True) text = f"v{version}" # 使用PIL添加文字 try: image = Image.open(output_image_path) draw = ImageDraw.Draw(image) font = ImageFont.load_default() font_families = [ 'DejaVu Sans', # 许多 Linux 发行版的标准配置[2,5](@ref) 'Liberation Sans', # 作为 Arial 等字体的开源替代,预装可能性高[1](@ref) 'Ubuntu', # Ubuntu 系统默认字体之一[3,5](@ref) 'FreeSans', # 另一个开源无衬线字体 'Source Sans Pro' # 一款清晰易读的开源字体 ] for font_family in font_families: try: font = ImageFont.truetype(font_family, 30) break except IOError: continue bbox = draw.textbbox((0, 0), text, font=font) text_width = bbox[2] - bbox[0] text_height = bbox[3] - bbox[1] # 设置位置:左下角,左边距50像素,下边距10像素[1,3](@ref) width, height = image.size x = 50 # 左边距 y = height - text_height - 50 # 图片高度减去文字高度再减去10像素边距 fill_color = (255, 0, 0) # 添加文字 draw.text((x, y), text, fill=fill_color, font=font) # 保存带文字的图片 image.save(output_image_path) except Exception as e: print(f"添加文字时出错: {e}") 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__": is_use_parser = False if is_use_parser: parser = argparse.ArgumentParser() parser.add_argument("--batch_id", type=str, required=False, help="batch_id") args = parser.parse_args() batch_id = args.batch_id else: batch_id = "9910032" base_path = f"{print_data_dir}{batch_id}/" load_show_save(base_path, {}, batch_id, False)