//  [python]
def get_art_score(trans, pose, objs, obj_traj):
    hand_verts = get_hand_seq(trans, pose)
    if len(objs) == 1:
        return 1.0
    fix, axis = calculate_fix(obj_traj[0], obj_traj[-1])
    axis = axis / torch.norm(axis)
    verts = torch.from_numpy(objs[0].vertices)
    para = torch.sum((verts - fix) * axis, dim=1)
    p_min, p_max = torch.min(para), torch.max(para)
    axis_1, axis_2 = fix + p_min * axis, fix + p_max * axis
    tot_frame, pass_frame = 0, 0
    sdf = []
    for (obj_i, obj) in enumerate(objs):
        sdf.append(SDF(obj.vertices, obj.faces))
    table_top = 100
    for (obj_i, obj) in enumerate(objs):
        cur_verts = torch.from_numpy(R.from_rotvec(obj_traj[0, obj_i, 3:]).apply(obj.vertices)) + obj_traj[0, obj_i, :3]
        table_top = min(table_top, torch.min(cur_verts[:,2]))
    for frame_i in range(obj_traj.shape[0] - 1):
        mat1_0, mat1_1 = torch.eye(4), torch.eye(4)
        mat2_0, mat2_1 = torch.eye(4), torch.eye(4)
        mat1_0[:3, :3], mat1_0[:3, 3] = torch.from_numpy(R.from_rotvec(obj_traj[frame_i, 0, 3:]).as_matrix()), obj_traj[frame_i, 0, :3]
        mat1_1[:3, :3], mat1_1[:3, 3] = torch.from_numpy(R.from_rotvec(obj_traj[frame_i, 1, 3:]).as_matrix()), obj_traj[frame_i, 1, :3]
        mat2_0[:3, :3], mat2_0[:3, 3] = torch.from_numpy(R.from_rotvec(obj_traj[frame_i+1, 0, 3:]).as_matrix()), obj_traj[frame_i+1, 0, :3]
        mat2_1[:3, :3], mat2_1[:3, 3] = torch.from_numpy(R.from_rotvec(obj_traj[frame_i+1, 1, 3:]).as_matrix()), obj_traj[frame_i+1, 1, :3]
        mat1 = torch.matmul(mat1_1, torch.linalg.inv(mat1_0))
        mat2 = torch.matmul(mat2_1, torch.linalg.inv(mat2_0))
        d_mat = mat2.matmul(torch.inverse(mat1))
        rot_vec = torch.from_numpy(R.from_matrix(d_mat[:3, :3]).as_rotvec())
        if torch.norm(rot_vec) > 1e-6:
            tot_frame += 1
            axis_1_p = torch.from_numpy(R.from_rotvec(obj_traj[frame_i, 0, 3:]).apply(axis_1)).float() + obj_traj[frame_i, 0, :3]
            axis_2_p = torch.from_numpy(R.from_rotvec(obj_traj[frame_i, 0, 3:]).apply(axis_2)).float() + obj_traj[frame_i, 0, :3]
            u0, u1 = 0, 0
            for (obj_i, obj) in enumerate(objs):
                axis = axis_2_p - axis_1_p
                axis = axis / torch.norm(axis)
                max_torque = 0
                min_torque = 0
                cur_h = R.from_rotvec(obj_traj[frame_i, obj_i, 3:]).inv().apply(hand_verts[frame_i] - obj_traj[frame_i, obj_i, :3])
                dist, nn = sdf[obj_i](cur_h), sdf[obj_i].nn(cur_h)
                contact_idx = torch.from_numpy(nn[np.where(dist > -0.002)])
                cur_verts = torch.from_numpy(R.from_rotvec(obj_traj[frame_i, obj_i, 3:]).apply(obj.vertices)) + obj_traj[frame_i, obj_i, :3]
                cur_norms = torch.from_numpy(R.from_rotvec(obj_traj[frame_i, obj_i, 3:]).apply(obj.vertex_normals))
                table_top_idx = torch.where(cur_verts[:,2] < table_top + 0.005)[0]
                if table_top_idx.shape[0] > 0:
                    samples = torch.randint(0, table_top_idx.shape[0], [50])
                    table_top_idx = table_top_idx[samples]
                tmp = (cur_verts - axis_1_p) - torch.sum((cur_verts - axis_1_p) * axis, dim=1).unsqueeze(-1).repeat(1, 3) * axis
                avg_d = torch.mean(torch.sqrt(torch.sum(torch.square(tmp), dim=1)))
                def update(c, n):
                    c = (c - axis_1_p) - torch.sum((c - axis_1_p) * axis) * axis
                    n = n - torch.sum(n * axis) * axis
                    tor = torch.cross(n, c)
                    return torch.sum(tor * axis)
                for idx_i in range(contact_idx.shape[0]):
                    idx = contact_idx[idx_i]
                    g = update(cur_verts[idx].float(), -cur_norms[idx].float())
                    max_torque,min_torque=max(max_torque,g), min(min_torque,g)
                g = update(torch.mean(cur_verts, dim=0).float(), torch.tensor((0, 0, -1), dtype=torch.float32))
                max_torque,min_torque=max(max_torque,g), min(min_torque,g)
                for idx_i in range(table_top_idx.shape[0]):
                    idx = table_top_idx[idx_i]
                    g = update(cur_verts[idx].float(), torch.tensor([0, 0, 1]))
                    max_torque,min_torque=max(max_torque,g), min(min_torque,g)
                if obj_i == 0:
                    u0 = max_torque / avg_d
                else:
                    u1 = -min_torque / avg_d
            # print(u0, u1)
            if u0 > 0.3 and u1 > 0.3:
                pass_frame += 1
    return pass_frame / tot_frame