Repository: mikedh/trimesh

Issue:
AttributeError: 'PointCloud' object has no attribute 'apply_obb'
When i use 
“mesh = trimesh.load(folder + "registeredScene.ply")
Tform = mesh.apply_obb()”
 Statement，system hint AttributeError: 'PointCloud' object has no attribute 'apply_obb'，How can i modify it？Thanks。

Discussion:

Patch:
diff --git a/trimesh/base.py b/trimesh/base.py
index 29b1a6bc..d309124d 100644
--- a/trimesh/base.py
+++ b/trimesh/base.py
@@ -285,7 +285,7 @@ class Trimesh(Geometry):
 
         Returns
         ----------
-        faces : (n,3) int
+        faces : (n, 3) int
           Representing triangles which reference self.vertices
         """
         return self._data.get('faces', np.empty(shape=(0, 3), dtype=int))
@@ -439,9 +439,8 @@ class Trimesh(Geometry):
         values : (n, 3) float
           Points in space
         """
-        self._data['vertices'] = np.asanyarray(values,
-                                               order='C',
-                                               dtype=np.float64)
+        self._data['vertices'] = np.asanyarray(
+            values, order='C', dtype=np.float64)
 
     @caching.cache_decorator
     def vertex_normals(self):
@@ -455,7 +454,7 @@ class Trimesh(Geometry):
 
         Returns
         ----------
-        vertex_normals : (n,3) float
+        vertex_normals : (n, 3) float
           Represents the surface normal at each vertex.
           Where n == len(self.vertices)
         """
@@ -1288,7 +1287,7 @@ class Trimesh(Geometry):
 
         Returns
         ----------
-        adjacency : (n,2) int
+        adjacency : (n, 2) int
           Pairs of faces which share an edge
 
         Examples
@@ -1460,7 +1459,7 @@ class Trimesh(Geometry):
         mesh = trimesh.primitives.Box()
         graph = mesh.vertex_adjacency_graph
         graph.neighbors(0)
-        > [1,2,3,4]
+        > [1, 2, 3, 4]
         """
 
         adjacency_g = graph.vertex_adjacency_graph(mesh=self)
@@ -1485,7 +1484,7 @@ class Trimesh(Geometry):
 
         >>> mesh = trimesh.primitives.Box()
         >>> mesh.vertex_neighbors[0]
-        [1,2,3,4]
+        [1, 2, 3, 4]
         """
         graph = self.vertex_adjacency_graph
         neighbors = [list(graph.neighbors(i)) for
@@ -1674,9 +1673,9 @@ class Trimesh(Geometry):
         # the face index of the largest face in each facet
         index = np.array([i[area_faces[i].argmax()]
                           for i in self.facets])
-        # (n,3) float, unit normal vectors of facet plane
+        # (n, 3) float, unit normal vectors of facet plane
         normals = self.face_normals[index]
-        # (n,3) float, points on facet plane
+        # (n, 3) float, points on facet plane
         origins = self.vertices[self.faces[:, 0][index]]
         # save origins in cache
         self._cache['facets_origin'] = origins
@@ -1732,7 +1731,7 @@ class Trimesh(Geometry):
         normals = self.facets_normal
         origins = self.facets_origin
 
-        # (n,3) convex hull vertices
+        # (n, 3) convex hull vertices
         convex = self.convex_hull.vertices.view(np.ndarray).copy()
 
         # boolean mask for which facets are on convex hull
@@ -2155,24 +2154,6 @@ class Trimesh(Geometry):
         # keep face normals as the haven't changed
         self._cache.clear(exclude=['face_normals'])
 
-    def apply_obb(self):
-        """
-        Apply the oriented bounding box transform to the current mesh.
-
-        This will result in a mesh with an AABB centered at the
-        origin and the same dimensions as the OBB.
-
-        Returns
-        ----------
-        matrix : (4, 4) float
-          Transformation matrix that was applied
-          to mesh to move it into OBB frame
-        """
-        matrix = self.bounding_box_oriented.primitive.transform
-        matrix = np.linalg.inv(matrix)
-        self.apply_transform(matrix)
-        return matrix
-
     def apply_transform(self, matrix):
         """
         Transform mesh by a homogeneous transformation matrix.
@@ -2187,13 +2168,12 @@ class Trimesh(Geometry):
           Homogeneous transformation matrix
         """
         # get c-order float64 matrix
-        matrix = np.asanyarray(matrix,
-                               order='C',
-                               dtype=np.float64)
+        matrix = np.asanyarray(
+            matrix, order='C', dtype=np.float64)
 
         # only support homogeneous transformations
         if matrix.shape != (4, 4):
-            raise ValueError('Transformation matrix must be (4,4)!')
+            raise ValueError('Transformation matrix must be (4, 4)!')
 
         # exit early if we've been passed an identity matrix
         # np.allclose is surprisingly slow so do this test
@@ -2804,7 +2784,7 @@ class Trimesh(Geometry):
 
         Examples
         -----------
-        r = mesh.eval_cached('np.dot(self.vertices, args[0])', [0,0,1])
+        r = mesh.eval_cached('np.dot(self.vertices, args[0])', [0, 0, 1])
         """
 
         statement = str(statement)
diff --git a/trimesh/parent.py b/trimesh/parent.py
index 86a75d3f..39d7b941 100644
--- a/trimesh/parent.py
+++ b/trimesh/parent.py
@@ -95,6 +95,24 @@ class Geometry(ABC):
         # apply_transform will work nicely even on negative scales
         return self.apply_transform(matrix)
 
+    def apply_obb(self):
+        """
+        Apply the oriented bounding box transform to the current mesh.
+
+        This will result in a mesh with an AABB centered at the
+        origin and the same dimensions as the OBB.
+
+        Returns
+        ----------
+        matrix : (4, 4) float
+          Transformation matrix that was applied
+          to mesh to move it into OBB frame
+        """
+        matrix = self.bounding_box_oriented.primitive.transform
+        matrix = np.linalg.inv(matrix)
+        self.apply_transform(matrix)
+        return matrix
+
     @abc.abstractmethod
     def copy(self):
         pass
diff --git a/trimesh/path/segments.py b/trimesh/path/segments.py
index 8b510d68..b1ef6109 100644
--- a/trimesh/path/segments.py
+++ b/trimesh/path/segments.py
@@ -437,6 +437,10 @@ def resample(segments,
     # save index of original segment
     index = []
 
+    tile = np.tile
+    # generate the line indexes ahead of time
+    stacks = util.stack_lines(np.arange(splits.max() + 1))
+
     # loop through each count of unique splits needed
     for split in np.unique(splits):
         # get a mask of which segments need to be split
@@ -444,20 +448,17 @@ def resample(segments,
         # the vector for each incremental length
         increment = vec[mask] / split
         # stack the increment vector into the shape needed
-        v = np.tile(increment, split + 1).reshape((-1, 3))
-        # apply integer multiples of the increment
-        v *= np.tile(np.arange(split + 1),
-                     len(increment)).reshape((-1, 1))
+        v = (tile(increment, split + 1).reshape((-1, 3)) *
+             tile(np.arange(split + 1),
+                  len(increment)).reshape((-1, 1)))
         # stack the origin points correctly
-        o = np.tile(pt1[mask], split + 1).reshape((-1, 3))
+        o = tile(pt1[mask], split + 1).reshape((-1, 3))
         # now get each segment as an (split, 3) polyline
         poly = (o + v).reshape((-1, split + 1, 3))
-        # get indexes to stack polyline into segments
-        stack = util.stack_lines(np.arange(split + 1))
         # save the resulting segments
         # magical slicing is equivalent to:
         # > [p[stack] for p in poly]
-        result.extend(poly[:, stack])
+        result.extend(poly[:, stacks[:split]])
 
         if return_index:
             # get the original index from the mask
@@ -466,13 +467,16 @@ def resample(segments,
             index.append((np.ones((len(poly), split),
                                   dtype=np.int64) *
                           index_original).ravel())
-
         if tol.strict:
             # check to make sure every start and end point
             # from the reconstructed result corresponds
             for original, recon in zip(segments[mask], poly):
                 assert np.allclose(original[0], recon[0])
                 assert np.allclose(original[-1], recon[-1])
+            # make sure stack slicing was OK
+            assert np.allclose(
+                util.stack_lines(np.arange(split + 1)),
+                stacks[:split])
 
     # stack into (n, 2, 3) segments
     result = [np.concatenate(result)]
diff --git a/trimesh/version.py b/trimesh/version.py
index b363ca01..f59ba749 100644
--- a/trimesh/version.py
+++ b/trimesh/version.py
@@ -1,1 +1,1 @@
-__version__ = '3.5.5'
+__version__ = '3.5.6'


Test patch:
diff --git a/tests/test_points.py b/tests/test_points.py
index 3c407a1f..fd8975e6 100644
--- a/tests/test_points.py
+++ b/tests/test_points.py
@@ -190,6 +190,13 @@ class PointsTest(g.unittest.TestCase):
         assert p.vertices.shape == (100, 3)
         assert p.colors.shape == (100, 4)
 
+    def test_obb(self):
+        p = g.get_mesh('points_agisoft.xyz')
+        original = p.bounds.copy()
+        matrix = p.apply_obb()
+        assert matrix.shape == (4, 4)
+        assert not g.np.allclose(p.bounds, original)
+
     def test_remove_close(self):
         # create 100 unique points
         p = g.np.arange(300).reshape((100, 3))