diff --git a/trimesh/creation.py b/trimesh/creation.py
index 7602b406..4589b78b 100644
--- a/trimesh/creation.py
+++ b/trimesh/creation.py
@@ -1170,7 +1170,7 @@ def truncated_prisms(tris, origin=None, normal=None):
     f_seq = np.tile(f, (len(transformed), 1)).reshape((-1, len(f), 3))
     # if the normal of the triangle was positive flip the winding
     f_seq[cross > 0] = np.fliplr(f)
-    # offet stacked faces to create correct indices
+    # offset stacked faces to create correct indices
     faces = (f_seq + (np.arange(len(f_seq)) * 6).reshape((-1, 1, 1))).reshape((-1, 3))
 
     # create a mesh from the data
diff --git a/trimesh/exchange/assimp.py b/trimesh/exchange/assimp.py
index b77a3383..22fa0e92 100644
--- a/trimesh/exchange/assimp.py
+++ b/trimesh/exchange/assimp.py
@@ -46,8 +46,9 @@ def load_pyassimp(file_obj,
         # add data from the LP_Mesh
         mesh_kwargs.update({'vertices': lp.vertices,
                             'vertex_normals': lp.normals,
-                            'faces': lp.faces,
-                            'vertex_colors': colors})
+                            'vertex_colors': colors,
+                            'faces': lp.faces})
+
         return mesh_kwargs
 
     # did we open the file inside this function
diff --git a/trimesh/exchange/dae.py b/trimesh/exchange/dae.py
index 7c0537b2..2a217e9c 100644
--- a/trimesh/exchange/dae.py
+++ b/trimesh/exchange/dae.py
@@ -52,7 +52,6 @@ def load_collada(file_obj, resolver=None, **kwargs):
     meshes = {}
     # list of dict
     graph = []
-
     for node in c.scene.nodes:
         _parse_node(node=node,
                     parent_matrix=np.eye(4),
diff --git a/trimesh/exchange/ply.py b/trimesh/exchange/ply.py
index 833c3532..39576cff 100644
--- a/trimesh/exchange/ply.py
+++ b/trimesh/exchange/ply.py
@@ -160,34 +160,37 @@ def export_ply(mesh,
     if mesh.visual.kind == 'vertex':
         vertex['rgba'] = mesh.visual.vertex_colors
 
-    header += templates['face']
-    if mesh.visual.kind == 'face' and encoding != 'ascii':
-        header += templates['color']
-        dtype_face.append(dtype_color)
-
-    # put mesh face data into custom dtype to export
-    faces = np.zeros(len(mesh.faces), dtype=dtype_face)
-    faces['count'] = 3
-    faces['index'] = mesh.faces
-    if mesh.visual.kind == 'face' and encoding != 'ascii':
-        faces['rgba'] = mesh.visual.face_colors
-
-    header += templates['outro']
-
     header_params = {'vertex_count': len(mesh.vertices),
-                     'face_count': len(mesh.faces),
                      'encoding': encoding}
 
+    if hasattr(mesh, 'faces'):
+        header += templates['face']
+        if mesh.visual.kind == 'face' and encoding != 'ascii':
+            header += templates['color']
+            dtype_face.append(dtype_color)
+        # put mesh face data into custom dtype to export
+        faces = np.zeros(len(mesh.faces), dtype=dtype_face)
+        faces['count'] = 3
+        faces['index'] = mesh.faces
+        if mesh.visual.kind == 'face' and encoding != 'ascii':
+            faces['rgba'] = mesh.visual.face_colors
+        header_params['face_count'] = len(mesh.faces)
+
+    header += templates['outro']
     export = Template(header).substitute(header_params).encode('utf-8')
 
     if encoding == 'binary_little_endian':
         export += vertex.tostring()
-        export += faces.tostring()
+        if hasattr(mesh, 'faces'):
+            export += faces.tostring()
     elif encoding == 'ascii':
-        # ply format is: (face count, v0, v1, v2)
-        fstack = np.column_stack((np.ones(len(mesh.faces),
-                                          dtype=np.int64) * 3,
-                                  mesh.faces))
+        if hasattr(mesh, 'faces'):
+            # ply format is: (face count, v0, v1, v2)
+            fstack = np.column_stack((np.ones(len(mesh.faces),
+                                              dtype=np.int64) * 3,
+                                      mesh.faces))
+        else:
+            fstack = []
 
         # if we're exporting vertex normals they get stacked
         if vertex_normal:
@@ -446,7 +449,7 @@ def elements_to_kwargs(elements,
             # face colors are preferred and defined
             kwargs['face_colors'] = f_color
     else:
-        kwargs['colors'] = element_colors(elements['vertex'])
+        kwargs['colors'] = element_colors(elements['vertex'])[0]
 
     return kwargs
 
diff --git a/trimesh/points.py b/trimesh/points.py
index 7405c1fa..07bcdb9b 100644
--- a/trimesh/points.py
+++ b/trimesh/points.py
@@ -11,6 +11,7 @@ import numpy as np
 from .parent import Geometry
 from .geometry import plane_transform
 from .constants import tol
+from .visual.color import VertexColor
 
 from . import util
 from . import caching
@@ -26,13 +27,17 @@ def point_plane_distance(points,
 
     Parameters
     -----------
-    points:       (n, 3) float, points in space
-    plane_normal: (3,) float, normal vector
-    plane_origin: (3,) float, plane origin in space
+    points : (n, 3) float
+      Points in space
+    plane_normal : (3,) float
+      Unit normal vector
+    plane_origin : (3,) float
+      Plane origin in space
 
     Returns
     ------------
-    distances:     (n,) float, distance from point to plane
+    distances : (n,) float
+      Distance from point to plane
     """
     points = np.asanyarray(points, dtype=np.float64)
     w = points - plane_origin
@@ -42,15 +47,18 @@ def point_plane_distance(points,
 
 def major_axis(points):
     """
-    Returns an approximate vector representing the major axis of points
+    Returns an approximate vector representing the major
+    axis of the passed points.
 
     Parameters
     -------------
-    points: (n, dimension) float, points in space
+    points : (n, dimension) float
+      Points in space
 
     Returns
     -------------
-    axis: (dimension,) float, vector along approximate major axis
+    axis : (dimension,) float
+      Vector along approximate major axis
     """
     U, S, V = np.linalg.svd(points)
     axis = util.unitize(np.dot(S, V))
@@ -59,19 +67,19 @@ def major_axis(points):
 
 def plane_fit(points):
     """
-    Given a set of points, find an origin and normal using SVD.
+    Fit a plane to points using SVD.
 
     Parameters
     ---------
-    points : (n,3) float
-        Points in 3D space
+    points : (n, 3) float
+      3D points in space
 
     Returns
     ---------
     C : (3,) float
-        Point on the plane
+      Point on the plane
     N : (3,) float
-        Normal vector of plane
+      Unit normal vector of plane
     """
     # make sure input is numpy array
     points = np.asanyarray(points, dtype=np.float64)
@@ -92,17 +100,21 @@ def radial_sort(points,
                 normal):
     """
     Sorts a set of points radially (by angle) around an
-    origin/normal.
+    an axis specified by origin and normal vector.
 
     Parameters
     --------------
-    points: (n,3) float, points in space
-    origin: (3,)  float, origin to sort around
-    normal: (3,)  float, vector to sort around
+    points : (n, 3) float
+      Points in space
+    origin : (3,)  float
+      Origin to sort around
+    normal : (3,)  float
+      Vector to sort around
 
     Returns
     --------------
-    ordered: (n,3) flot, re- ordered points in space
+    ordered : (n, 3) float
+      Same as input points but reordered
     """
 
     # create two axis perpendicular to each other and the normal,
@@ -127,18 +139,22 @@ def project_to_plane(points,
                      return_transform=False,
                      return_planar=True):
     """
-    Projects a set of (n,3) points onto a plane.
+    Project (n, 3) points onto a plane.
 
     Parameters
-    ---------
-    points:           (n,3) array of points
-    plane_normal:     (3) normal vector of plane
-    plane_origin:     (3) point on plane
-    transform:        None or (4,4) matrix. If specified, normal/origin are ignored
-    return_transform: bool, if true returns the (4,4) matrix used to project points
-                      onto a plane
-    return_planar:    bool, if True, returns (n,2) points. If False, returns
-                      (n,3), where the Z column consists of zeros
+    -----------
+    points : (n, 3) float
+      Points in space.
+    plane_normal : (3,) float
+      Unit normal vector of plane
+    plane_origin : (3,)
+      Origin point of plane
+    transform : None or (4, 4) float
+       Homogeneous transform, if specified, normal+origin are overridden
+    return_transform : bool
+      Returns the (4, 4) matrix used or not
+    return_planar : bool
+      Return (n, 2) points rather than (n, 3) points
     """
 
     if np.all(np.abs(plane_normal) < tol.zero):
@@ -215,18 +231,18 @@ def k_means(points, k, **kwargs):
     Parameters
     ----------
     points:  (n, d) float
-        Points in a space
+      Points in space
     k : int
-         Number of centroids to compute
+      Number of centroids to compute
     **kwargs : dict
-        Passed directly to scipy.cluster.vq.kmeans
+      Passed directly to scipy.cluster.vq.kmeans
 
     Returns
     ----------
     centroids : (k, d) float
-         Points in some space
+      Points in some space
     labels: (n) int
-        Indexes for which points belong to which centroid
+      Indexes for which points belong to which centroid
     """
     from scipy.cluster.vq import kmeans
     from scipy.spatial import cKDTree
@@ -255,7 +271,8 @@ def tsp(points, start=0):
     i.e. the travelling salesman problem on a fully connected
     graph. It is not a MINIMUM traversal; rather it is a
     "not totally goofy traversal, quickly." On random points
-    this traversal is often ~20x shorter than random ordering.
+    this traversal is often ~20x shorter than random ordering,
+    and executes on 1000 points in around 29ms on a 2014 i7.
 
     Parameters
     ---------------
@@ -327,7 +344,7 @@ def tsp(points, start=0):
 
 def plot_points(points, show=True):
     """
-    Plot an (n,3) list of points using matplotlib
+    Plot an (n, 3) list of points using matplotlib
 
     Parameters
     -------------
@@ -387,8 +404,8 @@ class PointCloud(Geometry):
         # load vertices
         self.vertices = vertices
 
-        if colors is not None:
-            self.colors = colors
+        # save visual data to vertex color object
+        self.visual = VertexColor(colors=colors, obj=self)
 
     def __setitem__(self, *args, **kwargs):
         return self.vertices.__setitem__(*args, **kwargs)
@@ -556,17 +573,11 @@ class PointCloud(Geometry):
         colors : (len(self.vertices), 4) np.uint8
           Per- point RGBA color
         """
-        return self._data.get('colors', None)
+        return self.visual.vertex_colors
 
     @colors.setter
     def colors(self, data):
-        if data is None:
-            if 'colors' in self._data:
-                del self._data['colors']
-        data = np.asanyarray(data)
-        if data.shape in [(3,), (4,)]:
-            data = np.tile(data, (len(self.vertices), 1))
-        self._data['colors'] = data
+        self.visual.vertex_colors = data
 
     @caching.cache_decorator
     def convex_hull(self):
diff --git a/trimesh/scene/scene.py b/trimesh/scene/scene.py
index 928b75ad..a12cfc5c 100644
--- a/trimesh/scene/scene.py
+++ b/trimesh/scene/scene.py
@@ -552,7 +552,7 @@ class Scene(Geometry):
         # if no geometry nothing to set camera to
         if len(self.geometry) == 0:
             self._camera = cameras.Camera(fov=fov)
-            self.graph[self._camera.name] = None
+            self.graph[self._camera.name] = np.eye(4)
             return self._camera
         # set with no rotation by default
         if angles is None:
diff --git a/trimesh/scene/transforms.py b/trimesh/scene/transforms.py
index 39c9c62f..e9e85dd9 100644
--- a/trimesh/scene/transforms.py
+++ b/trimesh/scene/transforms.py
@@ -555,7 +555,7 @@ def kwargs_to_matrix(**kwargs):
         matrix = transformations.rotation_matrix(kwargs['angle'],
                                                  kwargs['axis'])
     else:
-        raise ValueError('Couldn\'t update transform!')
+        matrix = np.eye(4)
 
     if 'translation' in kwargs:
         # translation can be used in conjunction with any of the methods of
diff --git a/trimesh/version.py b/trimesh/version.py
index e478d70a..c91eaea4 100644
--- a/trimesh/version.py
+++ b/trimesh/version.py
@@ -1,1 +1,1 @@
-__version__ = '3.6.20'
+__version__ = '3.6.21'
diff --git a/trimesh/visual/base.py b/trimesh/visual/base.py
new file mode 100644
index 00000000..20f34ffd
--- /dev/null
+++ b/trimesh/visual/base.py
@@ -0,0 +1,52 @@
+"""
+base.py
+-------------
+
+The base class for `Visual` objects
+"""
+import abc
+from ..util import ABC
+
+
+class Visuals(ABC):
+
+    """
+    Parent of Visual classes.
+    """
+
+    @abc.abstractproperty
+    def kind(self):
+        pass
+
+    @abc.abstractmethod
+    def update_vertices(self):
+        pass
+
+    @abc.abstractmethod
+    def update_faces(self):
+        pass
+
+    @abc.abstractmethod
+    def concatenate(self, other):
+        pass
+
+    @abc.abstractmethod
+    def copy(self):
+        pass
+
+    def __add__(self, other):
+        """
+        Concatenate two ColorVisuals objects into a single object.
+
+        Parameters
+        -----------
+        other : Visuals
+          Other visual to concatenate
+
+        Returns
+        -----------
+        result : Visuals
+          Object containing information from current
+          object and other in the order (self, other)
+        """
+        return self.concatenate(other)
diff --git a/trimesh/visual/color.py b/trimesh/visual/color.py
index fca7afc1..1b6fb966 100644
--- a/trimesh/visual/color.py
+++ b/trimesh/visual/color.py
@@ -27,12 +27,14 @@ import numpy as np
 import copy
 import colorsys
 
+from .base import Visuals
+
 from .. import util
 from .. import caching
 from .. import grouping
 
 
-class ColorVisuals(object):
+class ColorVisuals(Visuals):
     """
     Store color information about a mesh.
     """
@@ -98,7 +100,8 @@ class ColorVisuals(object):
 
         Returns
         ---------
-        defined: bool, are colors defined or not.
+        defined : bool
+          Are colors defined or not.
         """
         return self.kind is not None
 
@@ -109,7 +112,8 @@ class ColorVisuals(object):
 
         Returns
         ----------
-        mode: 'face', 'vertex', or None
+        mode : str or None
+          One of ('face', 'vertex', None)
         """
         self._verify_crc()
         if 'vertex_colors' in self._data:
@@ -127,7 +131,8 @@ class ColorVisuals(object):
 
         Returns
         ----------
-        crc: int, checksum of data in visual object and its parent mesh
+        crc : int
+          Checksum of data in visual object and its parent mesh
         """
         # will make sure everything has been transferred
         # to datastore that needs to be before returning crc
@@ -453,21 +458,6 @@ class ColorVisuals(object):
         result = objects.concatenate(self, other, *args)
         return result
 
-    def __add__(self, other):
-        """
-        Concatenate two ColorVisuals objects into a single object.
-
-        Parameters
-        -----------
-        other: ColorVisuals object
-
-        Returns
-        -----------
-        result: ColorVisuals object containing information from current
-                object and other in the order (self, other)
-        """
-        return self.concatenate(other)
-
     def _update_key(self, mask, key):
         """
         Mask the value contained in the DataStore at a specified key.
@@ -483,6 +473,70 @@ class ColorVisuals(object):
             self._data[key] = self._data[key][mask]
 
 
+class VertexColor(Visuals):
+    """
+    Create a simple visual object to hold just vertex colors
+    for objects such as PointClouds.
+    """
+
+    def __init__(self, colors=None, obj=None):
+        """
+        Create a vertex color visual
+        """
+        self.obj = obj
+        self.vertex_colors = colors
+
+    @property
+    def kind(self):
+        return 'vertex'
+
+    def update_vertices(self, mask):
+        if self._colors is not None:
+            self._colors = self._colors[mask]
+
+    def update_faces(self, mask):
+        pass
+
+    @property
+    def vertex_colors(self):
+        return self._colors
+
+    @vertex_colors.setter
+    def vertex_colors(self, data):
+        if data is None:
+            self._colors = None
+        else:
+            print(data)
+            data = np.asanyarray(data)
+            if data.shape in [(3,), (4,)]:
+                data = np.tile(data, (len(self.obj.vertices), 1))
+            self._colors = data
+
+    def copy(self):
+        """
+        Return a copy of the current visuals
+        """
+        return copy.deepcopy(self)
+
+    def concatenate(self, other):
+        """
+        Concatenate this visual object with another VertexVisuals.
+
+        Parameters
+        -----------
+        other : VertexColors or ColorVisuals
+          Other object to concatenate
+
+        Returns
+        ------------
+        concate : VertexColor
+          Object with both colors
+        """
+        return VertexColor(colors=np.vstack(
+            self.vertex_colors,
+            other.vertex_colors))
+
+
 def to_rgba(colors, dtype=np.uint8):
     """
     Convert a single or multiple RGB colors to RGBA colors.
diff --git a/trimesh/visual/texture.py b/trimesh/visual/texture.py
index 15c83ea0..e3f9dc1d 100644
--- a/trimesh/visual/texture.py
+++ b/trimesh/visual/texture.py
@@ -2,15 +2,15 @@ import numpy as np
 
 import copy
 
+from .base import Visuals
 from . import color
-
 from .. import caching
 from .. import grouping
 
 from .material import SimpleMaterial, PBRMaterial  # NOQA
 
 
-class TextureVisuals(object):
+class TextureVisuals(Visuals):
     def __init__(self,
                  uv=None,
                  material=None,
@@ -172,6 +172,9 @@ class TextureVisuals(object):
         """
         pass
 
+    def concatenate(self, *args, **kwargs):
+        raise NotImplementedError('concatentate not implemented yet for texture!')
+
 
 def unmerge_faces(faces, *args):
     """

point cloud export problem
Hi  there,

Is there an easy way to export point cloud? I found there existed some support in the point cloud class. However, when I wanted to export my point cloud object as .ply, it showed " 'PointCloud' object has no attribute 'visual'".

My code is like:
```
mesh = trimesh.load('models/XX.obj')
cloud=trimesh.PointCloud(mesh.sample(10000))
cloud.export('demo.ply')
```

Do I miss anything here? Thanks a lot.
diff --git a/tests/test_points.py b/tests/test_points.py
index d010efd6..6ca16bbd 100644
--- a/tests/test_points.py
+++ b/tests/test_points.py
@@ -197,6 +197,20 @@ class PointsTest(g.unittest.TestCase):
         assert matrix.shape == (4, 4)
         assert not g.np.allclose(p.bounds, original)
 
+    def test_ply(self):
+        p = g.get_mesh('points_agisoft.xyz')
+        assert isinstance(p, g.trimesh.PointCloud)
+        assert len(p.vertices) > 0
+        p.visual.colors = (g.np.random.random(
+            (len(p.vertices), 4)) * 255).astype(g.np.uint8)
+        # test exporting a pointcloud to a PLY file
+        r = g.trimesh.load(g.trimesh.util.wrap_as_stream(
+            p.export(file_type='ply')),
+            file_type='ply')
+        assert r.vertices.shape == p.vertices.shape
+        # make sure colors survived the round trip
+        assert g.np.allclose(r.colors, p.colors)
+
     def test_remove_close(self):
         # create 100 unique points
         p = g.np.arange(300).reshape((100, 3))

pip install -e .[all]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-xdist pytest-mock pytest-asyncio
apt-get update apt-get install -y gcc libspatialindex-dev libgeos-dev
3.6
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
autoprop==4.1.0
backports.cached-property==1.0.2
certifi==2021.5.30
chardet==5.0.0
charset-normalizer==2.0.12
colorlog==6.9.0
coverage==6.2
cycler==0.11.0
Cython==3.0.12
decorator==4.4.2
execnet==1.9.0
glooey==0.3.6
idna==3.10
imageio==2.15.0
importlib-metadata==4.8.3
iniconfig==1.1.1
jsonschema==3.2.0
kiwisolver==1.3.1
lxml==5.3.1
matplotlib==3.3.4
meshio==4.4.6
more-itertools==8.14.0
mpmath==1.3.0
msgpack==1.0.5
networkx==2.5.1
numpy==1.19.5
packaging==21.3
Pillow==8.4.0
pluggy==1.0.0
psutil==7.0.0
py==1.11.0
pycollada==0.8
pyglet==2.0.10
pyparsing==3.1.4
pyrsistent==0.18.0
pytest==7.0.1
pytest-asyncio==0.16.0
pytest-cov==4.0.0
pytest-mock==3.6.1
pytest-xdist==3.0.2
python-dateutil==2.9.0.post0
python-fcl==0.7.0.5
PyWavelets==1.1.1
PyYAML==6.0.1
requests==2.27.1
Rtree==0.9.7
scikit-image==0.17.2
scipy==1.5.4
Shapely==1.8.5.post1
signature_dispatch==1.0.0
six==1.17.0
svg.path==6.2
sympy==1.9
tifffile==2020.9.3
tomli==1.2.3
triangle==20220202
-e git+https://github.com/mikedh/trimesh.git@cced3d777f882ac8dcca45acba397c5cedbe9726#egg=trimesh
typeguard==2.13.3
typing==3.7.4.3
typing_extensions==4.1.1
urllib3==1.26.20
vecrec==0.3.1
xxhash==3.2.0
zipp==3.6.0

name: trimesh
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - autoprop==4.1.0
      - backports-cached-property==1.0.2
      - chardet==5.0.0
      - charset-normalizer==2.0.12
      - colorlog==6.9.0
      - coverage==6.2
      - cycler==0.11.0
      - cython==3.0.12
      - decorator==4.4.2
      - execnet==1.9.0
      - glooey==0.3.6
      - idna==3.10
      - imageio==2.15.0
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - jsonschema==3.2.0
      - kiwisolver==1.3.1
      - lxml==5.3.1
      - matplotlib==3.3.4
      - meshio==4.4.6
      - more-itertools==8.14.0
      - mpmath==1.3.0
      - msgpack==1.0.5
      - networkx==2.5.1
      - numpy==1.19.5
      - packaging==21.3
      - pillow==8.4.0
      - pluggy==1.0.0
      - psutil==7.0.0
      - py==1.11.0
      - pycollada==0.8
      - pyglet==2.0.10
      - pyparsing==3.1.4
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - pytest-asyncio==0.16.0
      - pytest-cov==4.0.0
      - pytest-mock==3.6.1
      - pytest-xdist==3.0.2
      - python-dateutil==2.9.0.post0
      - python-fcl==0.7.0.5
      - pywavelets==1.1.1
      - pyyaml==6.0.1
      - requests==2.27.1
      - rtree==0.9.7
      - scikit-image==0.17.2
      - scipy==1.5.4
      - shapely==1.8.5.post1
      - signature-dispatch==1.0.0
      - six==1.17.0
      - svg-path==6.2
      - sympy==1.9
      - tifffile==2020.9.3
      - tomli==1.2.3
      - triangle==20220202
      - typeguard==2.13.3
      - typing==3.7.4.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - vecrec==0.3.1
      - xxhash==3.2.0
      - zipp==3.6.0
prefix: /opt/conda/envs/trimesh

tests/test_points.py::PointsTest::test_ply
tests/test_points.py::PointsTest::test_empty tests/test_points.py::PointsTest::test_init_arguments tests/test_points.py::PointsTest::test_kmeans tests/test_points.py::PointsTest::test_obb tests/test_points.py::PointsTest::test_plane tests/test_points.py::PointsTest::test_pointcloud tests/test_points.py::PointsTest::test_remove_close tests/test_points.py::PointsTest::test_tsp tests/test_points.py::PointsTest::test_vertex_only tests/test_points.py::PointsTest::test_xyz
trimesh/scene/scene.py trimesh/visual/base.py trimesh/exchange/dae.py trimesh/exchange/ply.py trimesh/creation.py trimesh/visual/texture.py trimesh/version.py trimesh/points.py trimesh/scene/transforms.py trimesh/exchange/assimp.py trimesh/visual/color.py
trimesh/scene/scene.py trimesh/visual/base.py trimesh/exchange/dae.py trimesh/exchange/ply.py trimesh/creation.py trimesh/visual/texture.py trimesh/version.py trimesh/points.py trimesh/scene/transforms.py trimesh/exchange/assimp.py trimesh/visual/color.py