import pyrender from tqdm import tqdm import trimesh import numpy as np import os import imageio def save_gif(gif_path, gif_frames, duration=0.01): if gif_frames: print(f"Saving GIF with {len(gif_frames)} frames to {gif_path}") imageio.mimsave(uri=gif_path, ims=gif_frames, duration=duration) else: print("No frames to save.") def render_meshes(model, output, should_save_gif=False, gif_path=None): should_display = not should_save_gif vertices_list = output.vertices.detach().cpu().numpy().squeeze() joints_list = output.joints.detach().cpu().numpy().squeeze() if len(vertices_list.shape) == 2: vertices_list = [vertices_list] joints_list = [joints_list] scene = pyrender.Scene() if should_display: viewer = pyrender.Viewer(scene, run_in_thread=True) mesh_node = None joints_node = None # Rotation matrix (90 degrees around the X-axis) rot = trimesh.transformations.rotation_matrix(np.radians(90), [1, 0, 0]) gif_frames = [] if should_save_gif: os.makedirs(os.path.dirname(gif_path), exist_ok=True) try: for i in tqdm(range(len(vertices_list))): vertices = vertices_list[i] joints = joints_list[i] # print("Vertices shape =", vertices.shape) # print("Joints shape =", joints.shape) # from their demo script plotting_module = "pyrender" plot_joints = False if plotting_module == "pyrender": vertex_colors = np.ones([vertices.shape[0], 4]) * [0.3, 0.3, 0.3, 0.8] tri_mesh = trimesh.Trimesh(vertices, model.faces, vertex_colors=vertex_colors) # Apply rotation tri_mesh.apply_transform(rot) ##### RENDER LOCK ##### if should_display: viewer.render_lock.acquire() if mesh_node: scene.remove_node(mesh_node) mesh = pyrender.Mesh.from_trimesh(tri_mesh) mesh_node = scene.add(mesh) camera = pyrender.PerspectiveCamera(yfov=np.pi / 3.0, aspectRatio=1.0) min_bound, max_bound = mesh.bounds # Calculate the center of the bounding box center = (min_bound + max_bound) / 2 # Calculate the extents (the dimensions of the bounding box) extents = max_bound - min_bound # Estimate a suitable distance distance = max(extents) * 2 # Adjust the multiplier as needed # Create a camera pose matrix cam_pose = np.array( [ [1.0, 0, 0, center[0]], [0, 1.0, 0, center[1]-1.0], [0, 0, 1.0, center[2] + distance + 0.5], [0, 0, 0, 1], ] ) # Rotate around X-axis angle = np.radians(80) cos_angle = np.cos(angle) sin_angle = np.sin(angle) rot_x_10_deg = np.array([ [1, 0, 0, 0], [0, cos_angle, -sin_angle, 0], [0, sin_angle, cos_angle, 0], [0, 0, 0, 1] ]) # rotate cam_pose with rot_x_10_deg cam_pose = np.matmul(cam_pose, rot_x_10_deg) cam_pose[:3, 3] += np.array([0, -2.2, -3.0]) scene.add(camera, pose=cam_pose) # Add light for better visualization light = pyrender.DirectionalLight(color=np.ones(3), intensity=2.0) scene.add(light, pose=cam_pose) # TODO: rotation doesn't work here, so appears sideways if plot_joints: sm = trimesh.creation.uv_sphere(radius=0.005) sm.visual.vertex_colors = [0.9, 0.1, 0.1, 1.0] tfs = np.tile(np.eye(4), (len(joints), 1, 1)) # tfs[:, :3, 3] = joints for i, joint in enumerate(joints): tfs[i, :3, :3] = rot[:3, :3] tfs[i, :3, 3] = joint joints_pcl = pyrender.Mesh.from_trimesh(sm, poses=tfs) if joints_node: scene.remove_node(joints_node) joints_node = scene.add(joints_pcl) if should_save_gif: r = pyrender.OffscreenRenderer(viewport_width=640, viewport_height=480) color, _ = r.render(scene) gif_frames.append(color) r.delete() # Free up the resources ###### RENDER LOCK RELEASE ##### if should_display: viewer.render_lock.release() except KeyboardInterrupt: if should_display: viewer.close_external() save_gif(gif_path, gif_frames) finally: save_gif(gif_path, gif_frames)