Repository: mikedh/trimesh

Issue:
Support negative indices in OBJ files
Loading OBJ files with negative indices results in an error

```
>>> trimesh.load("negative_indices.obj")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "...\lib\site-packages\trimesh\exchange\load.py", line 127, in load
    loaded = load_mesh(file_obj,
  File "...\lib\site-packages\trimesh\constants.py", line 146, in timed
    result = method(*args, **kwargs)
  File "...\lib\site-packages\trimesh\exchange\load.py", line 209, in load_mesh
    results = mesh_loaders[file_type](file_obj,
  File "...\lib\site-packages\trimesh\exchange\obj.py", line 220, in load_obj
    mask_v[faces] = True
IndexError: index -9 is out of bounds for axis 0 with size 8
```

eg for this OBJ file:
```obj
# Blender v3.0.0 OBJ File: ''
# www.blender.org
o Cube
v 1.000000 1.000000 -1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 1.000000
v -1.000000 1.000000 -1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 1.000000
vt 0.625000 0.500000
vt 0.875000 0.500000
vt 0.875000 0.750000
vt 0.625000 0.750000
vt 0.375000 0.750000
vt 0.625000 1.000000
vt 0.375000 1.000000
vt 0.375000 0.000000
vt 0.625000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.125000 0.500000
vt 0.375000 0.500000
vt 0.125000 0.750000
vn 0.0000 1.0000 0.0000
vn 0.0000 0.0000 1.0000
vn -1.0000 0.0000 0.0000
vn 0.0000 -1.0000 0.0000
vn 1.0000 0.0000 0.0000
vn 0.0000 0.0000 -1.0000
s off
f -8/-13/-6 -4/-12/-6 -2/-12/-6 -6/-11/-6
f -5/-10/-5 -6/-10/-5 -2/-9/-5 -1/-8/-5
f -1/-7/-4 -2/-6/-4 -4/-5/-4 -3/-4/-4
f -3/-3/-3 -7/-2/-3 -5/-10/-3 -1/-1/-3
f -7/-2/-2 -8/-13/-2 -6/-11/-2 -5/-10/-2
f -3/-4/-1 -4/-5/-1 -8/-13/-1 -7/-2/-1
```

Some info here: http://www.paulbourke.net/dataformats/obj/

The fix for this seems quite simple, I'll open a PR imminently

Discussion:

Patch:
diff --git a/models/negative_indices.obj b/models/negative_indices.obj
new file mode 100644
index 00000000..464fe452
--- /dev/null
+++ b/models/negative_indices.obj
@@ -0,0 +1,35 @@
+v 1.000000 1.000000 -1.000000
+v 1.000000 -1.000000 -1.000000
+v 1.000000 1.000000 1.000000
+v 1.000000 -1.000000 1.000000
+v -1.000000 1.000000 -1.000000
+v -1.000000 -1.000000 -1.000000
+v -1.000000 1.000000 1.000000
+v -1.000000 -1.000000 1.000000
+vt 0.625000 0.500000
+vt 0.875000 0.500000
+vt 0.875000 0.750000
+vt 0.625000 0.750000
+vt 0.375000 0.750000
+vt 0.625000 1.000000
+vt 0.375000 1.000000
+vt 0.375000 0.000000
+vt 0.625000 0.000000
+vt 0.625000 0.250000
+vt 0.375000 0.250000
+vt 0.125000 0.500000
+vt 0.375000 0.500000
+vt 0.125000 0.750000
+vn 0.0000 1.0000 0.0000
+vn 0.0000 0.0000 1.0000
+vn -1.0000 0.0000 0.0000
+vn 0.0000 -1.0000 0.0000
+vn 1.0000 0.0000 0.0000
+vn 0.0000 0.0000 -1.0000
+s off
+f -8/-13/-6 -4/-12/-6 -2/-12/-6 -6/-11/-6
+f -5/-10/-5 -6/-10/-5 -2/-9/-5 -1/-8/-5
+f -1/-7/-4 -2/-6/-4 -4/-5/-4 -3/-4/-4
+f -3/-3/-3 -7/-2/-3 -5/-10/-3 -1/-1/-3
+f -7/-2/-2 -8/-13/-2 -6/-11/-2 -5/-10/-2
+f -3/-4/-1 -4/-5/-1 -8/-13/-1 -7/-2/-1
diff --git a/trimesh/exchange/obj.py b/trimesh/exchange/obj.py
index 194e0c0c..0ecb9fbd 100644
--- a/trimesh/exchange/obj.py
+++ b/trimesh/exchange/obj.py
@@ -140,8 +140,10 @@ def load_obj(file_obj,
 
         # the fastest way to get to a numpy array
         # processes the whole string at once into a 1D array
+        array = np.fromstring(joined, sep=' ', dtype=np.int64)
         # also wavefront is 1-indexed (vs 0-indexed) so offset
-        array = np.fromstring(joined, sep=' ', dtype=np.int64) - 1
+        # only applies to positive indices
+        array[array > 0] -= 1
 
         # get the number of raw 2D columns in a sample line
         columns = len(face_lines[0].strip().replace('/', ' ').split())
diff --git a/trimesh/version.py b/trimesh/version.py
index ac839cc1..ae9fee26 100644
--- a/trimesh/version.py
+++ b/trimesh/version.py
@@ -1,1 +1,1 @@
-__version__ = '3.9.40'
+__version__ = '3.9.41'


Test patch:
diff --git a/tests/test_obj.py b/tests/test_obj.py
index 9e074328..2f7ebecb 100644
--- a/tests/test_obj.py
+++ b/tests/test_obj.py
@@ -54,6 +54,14 @@ class OBJTest(g.unittest.TestCase):
         # check to make sure there is signal not just zeros
         # assert mesh.metadata['face_groups'].ptp() > 0
 
+    def test_obj_negative_indices(self):
+        # a wavefront file with negative indices
+        mesh = g.get_mesh('negative_indices.obj')
+
+        # make sure some data got loaded
+        assert g.trimesh.util.is_shape(mesh.faces, (12, 3))
+        assert g.trimesh.util.is_shape(mesh.vertices, (8, 3))
+
     def test_obj_quad(self):
         mesh = g.get_mesh('quadknot.obj')
         # make sure some data got loaded