code: def test_save_obj_with_normal(self):
      verts = torch.tensor(
          [
              [0.01, 0.2, 0.301],
              [0.2, 0.03, 0.408],
              [0.3, 0.4, 0.05],
              [0.6, 0.7, 0.8],
          ],
          dtype=torch.float32,
      )
      faces = torch.tensor(
          [[0, 2, 1], [0, 1, 2], [3, 2, 1], [3, 1, 0]],
          dtype=torch.int64,
      )
      normals = torch.tensor(
          [
              [0.02, 0.5, 0.73],
              [0.3, 0.03, 0.361],
              [0.32, 0.12, 0.47],
              [0.36, 0.17, 0.9],
              [0.40, 0.7, 0.19],
              [1.0, 0.00, 0.000],
              [0.00, 1.00, 0.00],
              [0.00, 0.00, 1.0],
          ],
          dtype=torch.float32,
      )
      faces_normals_idx = torch.tensor(
          [[0, 1, 2], [2, 3, 4], [4, 5, 6], [6, 7, 0]],
          dtype=torch.int64,
      )
  
      with TemporaryDirectory() as temp_dir:
          obj_file = os.path.join(temp_dir, "mesh.obj")
          save_obj(
              obj_file,
              verts,
              faces,
              decimal_places=2,
              normals=normals,
              faces_normals_idx=faces_normals_idx,
          )
  
          expected_obj_file = "\n".join(
              [
                  "v 0.01 0.20 0.30",
                  "v 0.20 0.03 0.41",
                  "v 0.30 0.40 0.05",
                  "v 0.60 0.70 0.80",
                  "vn 0.02 0.50 0.73",
                  "vn 0.30 0.03 0.36",
                  "vn 0.32 0.12 0.47",
                  "vn 0.36 0.17 0.90",
                  "vn 0.40 0.70 0.19",
                  "vn 1.00 0.00 0.00",
                  "vn 0.00 1.00 0.00",
                  "vn 0.00 0.00 1.00",
                  "f 1//1 3//2 2//3",
                  "f 1//3 2//4 3//5",
                  "f 4//5 3//6 2//7",
                  "f 4//7 2//8 1//1",
              ]
          )
  
          # Check the obj file is saved correctly
          with open(obj_file, "r") as actual_file:
              self.assertEqual(actual_file.read(), expected_obj_file)
  
imports: import os
  import unittest
  import warnings
  from collections import Counter
  from io import StringIO
  from pathlib import Path
  from tempfile import NamedTemporaryFile, TemporaryDirectory
  import torch
  from iopath.common.file_io import PathManager
  from pytorch3d.io import IO, load_obj, load_objs_as_meshes, save_obj
  from pytorch3d.io.mtl_io import _bilinear_interpolation_grid_sample, _bilinear_interpolation_vectorized, _parse_mtl
  from pytorch3d.renderer import TexturesAtlas, TexturesUV, TexturesVertex
  from pytorch3d.structures import join_meshes_as_batch, Meshes
  from pytorch3d.utils import torus
  from .common_testing import get_pytorch3d_dir, get_tests_dir, load_rgb_image, TestCaseMixin
label_desc: Use image node_id 31 for calling the TestMeshObjIO obj's underlying member method code with example usage: obj.test_save_obj_with_normal() without return types