diff --git a/README.md b/README.md
index d6d197a6..861fa6a0 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 [![trimesh](https://trimsh.org/images/logotype-a.svg)](http://trimsh.org)
 
 -----------
-[![Github Actions](https://github.com/mikedh/trimesh/workflows/Run%20Tests/badge.svg)](https://github.com/mikedh/trimesh/actions) [![Coverage Status](https://coveralls.io/repos/github/mikedh/trimesh/badge.svg)](https://coveralls.io/github/mikedh/trimesh) [![PyPI version](https://badge.fury.io/py/trimesh.svg)](https://badge.fury.io/py/trimesh) [![Join the chat at https://gitter.im/trimsh/Lobby](https://badges.gitter.im/trimsh/Lobby.svg)](https://gitter.im/trimsh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+[![Github Actions](https://github.com/mikedh/trimesh/workflows/Release%20Trimesh/badge.svg)](https://github.com/mikedh/trimesh/actions) [![Coverage Status](https://coveralls.io/repos/github/mikedh/trimesh/badge.svg)](https://coveralls.io/github/mikedh/trimesh) [![PyPI version](https://badge.fury.io/py/trimesh.svg)](https://badge.fury.io/py/trimesh) [![Join the chat at https://gitter.im/trimsh/Lobby](https://badges.gitter.im/trimsh/Lobby.svg)](https://gitter.im/trimsh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 
 
 
diff --git a/examples/dockerRender/render.supervisor.conf b/examples/dockerRender/render.supervisor.conf
index 541e1ddc..15063c51 100644
--- a/examples/dockerRender/render.supervisor.conf
+++ b/examples/dockerRender/render.supervisor.conf
@@ -2,7 +2,7 @@
 nodaemon=true ;
 
 [program:render_worker]
-command=/home/user/conda/bin/python -u /home/user/render.py
+command=python -u /home/user/render.py
 stdout_logfile=/dev/stdout
 stdout_logfile_maxbytes=0
 stderr_logfile=/dev/stderr
diff --git a/trimesh/parent.py b/trimesh/parent.py
index 39d7b941..12472914 100644
--- a/trimesh/parent.py
+++ b/trimesh/parent.py
@@ -40,6 +40,14 @@ class Geometry(ABC):
     def is_empty(self):
         pass
 
+    @abc.abstractmethod
+    def crc(self):
+        pass
+
+    @abc.abstractmethod
+    def md5(self):
+        pass
+
     def __repr__(self):
         """
         Print quick summary of the current geometry without
diff --git a/trimesh/points.py b/trimesh/points.py
index 07bcdb9b..c12a4106 100644
--- a/trimesh/points.py
+++ b/trimesh/points.py
@@ -475,6 +475,17 @@ class PointCloud(Geometry):
         """
         return self._data.md5()
 
+    def crc(self):
+        """
+        Get a CRC hash of the current vertices.
+
+        Returns
+        ----------
+        crc : int
+          Hash of self.vertices
+        """
+        return self._data.crc()
+
     def merge_vertices(self):
         """
         Merge vertices closer than tol.merge (default: 1e-8)
diff --git a/trimesh/scene/scene.py b/trimesh/scene/scene.py
index a12cfc5c..ece0440b 100644
--- a/trimesh/scene/scene.py
+++ b/trimesh/scene/scene.py
@@ -234,6 +234,12 @@ class Scene(Geometry):
           MD5 hash of scene
         """
         # start with transforms hash
+        return util.md5_object(self._hashable())
+
+    def crc(self):
+        return caching.crc32(self._hashable())
+
+    def _hashable(self):
         hashes = [self.graph.md5()]
         for g in self.geometry.values():
             if hasattr(g, 'md5'):
@@ -244,10 +250,8 @@ class Scene(Geometry):
                 # try to just straight up hash
                 # this may raise errors
                 hashes.append(str(hash(g)))
-
-        md5 = util.md5_object(''.join(hashes))
-
-        return md5
+        hashable = ''.join(sorted(hashes)).encode('utf-8')
+        return hashable
 
     @property
     def is_empty(self):
diff --git a/trimesh/version.py b/trimesh/version.py
index 4ec5c6fa..f460593b 100644
--- a/trimesh/version.py
+++ b/trimesh/version.py
@@ -1,1 +1,1 @@
-__version__ = '3.6.24'
+__version__ = '3.6.25'
diff --git a/trimesh/visual/base.py b/trimesh/visual/base.py
index 20f34ffd..37424c41 100644
--- a/trimesh/visual/base.py
+++ b/trimesh/visual/base.py
@@ -30,6 +30,10 @@ class Visuals(ABC):
     def concatenate(self, other):
         pass
 
+    @abc.abstractmethod
+    def crc(self):
+        pass
+
     @abc.abstractmethod
     def copy(self):
         pass
diff --git a/trimesh/visual/color.py b/trimesh/visual/color.py
index 0e6e1d0e..fd418db9 100644
--- a/trimesh/visual/color.py
+++ b/trimesh/visual/color.py
@@ -490,6 +490,9 @@ class VertexColor(Visuals):
     def kind(self):
         return 'vertex'
 
+    def crc(self):
+        return self._colors.crc()
+
     def update_vertices(self, mask):
         if self._colors is not None:
             self._colors = self._colors[mask]
@@ -504,13 +507,14 @@ class VertexColor(Visuals):
     @vertex_colors.setter
     def vertex_colors(self, data):
         if data is None:
-            self._colors = None
+            self._colors = caching.tracked_array(None)
         else:
             # tile single color into color array
             data = np.asanyarray(data)
             if data.shape in [(3,), (4,)]:
                 data = np.tile(data, (len(self.obj.vertices), 1))
-            self._colors = data
+            # track changes in colors and convert to RGBA
+            self._colors = caching.tracked_array(to_rgba(data))
 
     def copy(self):
         """

Error in Version 3.6.24 - AttributeError: 'VertexColor' object has no attribute 'crc'
This is a regression introduced recently. It happens with v3.6.24 and not on v3.6.20.

```
AttributeError                            Traceback (most recent call last)
<ipython-input-6-88efc28c4028> in <module>
      7 scene.add_geometry(.....)
      8 
----> 9 scene.show(viewer='gl')
     10 Image(scene.save_image())

~/venv3.7/lib/python3.7/site-packages/trimesh/scene/scene.py in show(self, viewer, **kwargs)
   1088             # if pyglet is not available
   1089             from ..viewer import SceneViewer
-> 1090             return SceneViewer(self, **kwargs)
   1091         elif viewer == 'notebook':
   1092             from ..viewer import scene_to_notebook

~/venv3.7/lib/python3.7/site-packages/trimesh/viewer/windowed.py in __init__(self, scene, smooth, flags, visible, resolution, start_loop, callback, callback_period, caption, fixed, offset_lines, background, window_conf, profile, **kwargs)
    168 
    169         # add scene geometry to viewer geometry
--> 170         self._update_vertex_list()
    171 
    172         # call after geometry is added

~/venv3.7/lib/python3.7/site-packages/trimesh/viewer/windowed.py in _update_vertex_list(self)
    197             if geom.is_empty:
    198                 continue
--> 199             if geometry_hash(geom) == self.vertex_list_hash.get(name):
    200                 continue
    201             self.add_geometry(name=name,

~/venv3.7/lib/python3.7/site-packages/trimesh/viewer/windowed.py in geometry_hash(geometry)
    815     if hasattr(geometry, 'visual'):
    816         # if visual properties are defined
--> 817         md5 += str(geometry.visual.crc())
    818     return md5
    819 

AttributeError: 'VertexColor' object has no attribute 'crc'
```

diff --git a/tests/generic.py b/tests/generic.py
index 1737c08a..e3861220 100644
--- a/tests/generic.py
+++ b/tests/generic.py
@@ -3,6 +3,12 @@
 Module which contains most imports and data unit tests
 might need, to reduce the amount of boilerplate.
 """
+from trimesh.base import Trimesh
+from trimesh.constants import tol, tol_path
+from collections import deque
+from copy import deepcopy
+import collections
+import trimesh
 from distutils.spawn import find_executable
 import os
 import sys
@@ -34,18 +40,20 @@ except ImportError:
 
 import numpy as np
 
+# should we require all soft dependencies
+# this is set in the docker images to catch missing packages
+all_dep = 'alldep' in ''.join(sys.argv)
+
+if all_dep:
+    # make sure pyembree is importable
+    from pyembree import rtcore_scene
+
 try:
     import sympy as sp
-except ImportError:
-    pass
-
-import trimesh
-import collections
+except ImportError as E:
+    if all_dep:
+        raise E
 
-from copy import deepcopy
-from collections import deque
-from trimesh.constants import tol, tol_path
-from trimesh.base import Trimesh
 
 # make sure functions know they should run additional
 # potentially slow validation checks and raise exceptions
@@ -53,20 +61,21 @@ trimesh.util._STRICT = True
 trimesh.constants.tol.strict = True
 trimesh.constants.tol_path.strict = True
 
-# should we require all soft dependencies
-# this is set in the docker images to catch missing packages
-all_dep = 'alldep' in ''.join(sys.argv)
-
-if all_dep:
-    # make sure pyembree is importable
-    from pyembree import rtcore_scene
 
 try:
     from shapely.geometry import Point, Polygon, LineString
     has_path = True
-except ImportError:
+except ImportError as E:
+    if all_dep:
+        raise E
     has_path = False
 
+try:
+    from scipy import spatial, csgraph
+except BaseException as E:
+    if all_dep:
+        raise E
+
 # find_executable for binvox
 has_binvox = trimesh.exchange.binvox.binvox_encoder is not None
 
diff --git a/tests/test_points.py b/tests/test_points.py
index 6ca16bbd..a2663324 100644
--- a/tests/test_points.py
+++ b/tests/test_points.py
@@ -66,14 +66,6 @@ class PointsTest(g.unittest.TestCase):
         assert g.np.allclose(cloud.vertices,
                              cloud.copy().vertices)
 
-    def test_init_arguments(self):
-        vertices = [[0, 0, 0], [1, 1, 1]]
-        colors = [[0, 0, 1.0], [1.0, 0, 0]]
-
-        # introduced colors
-        p = g.trimesh.PointCloud(vertices=vertices, colors=colors)
-        g.np.testing.assert_allclose(p.colors, colors)
-
     def test_empty(self):
         p = g.trimesh.PointCloud(None)
         assert p.is_empty
@@ -201,14 +193,22 @@ class PointsTest(g.unittest.TestCase):
         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)
+
+        # initial color CRC
+        initial = p.visual.crc()
+        # set to random colors
+        p.colors = g.np.random.random(
+            (len(p.vertices), 4))
+        # visual CRC should have changed
+        assert p.visual.crc() != initial
+
         # 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):
diff --git a/tests/test_scene.py b/tests/test_scene.py
index 5d13ca3e..0cb72103 100644
--- a/tests/test_scene.py
+++ b/tests/test_scene.py
@@ -28,6 +28,8 @@ class SceneTests(g.unittest.TestCase):
             assert scene_split.md5() == pre[0]
             assert scene_base.md5() == pre[1]
 
+            assert isinstance(scene_base.crc(), int)
+
             # try out scene appending
             concat = scene_split + scene_base
 

pip install -e .[all]
parse_log_pytest
requirements.txt
pytest pytest-cov setuptools
apt-get update apt-get install -y gcc libspatialindex-dev libgeos-dev
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==25.3.0
autoprop==4.1.0
certifi==2025.1.31
chardet==5.2.0
charset-normalizer==3.4.1
colorlog==6.9.0
coverage==7.8.0
Cython==3.0.12
exceptiongroup==1.2.2
glooey==0.3.6
idna==3.10
imageio==2.37.0
importlib_metadata==8.6.1
iniconfig==2.1.0
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
lazy_loader==0.4
lxml==5.3.1
markdown-it-py==3.0.0
mdurl==0.1.2
meshio==5.3.5
more-itertools==10.6.0
mpmath==1.3.0
msgpack==1.1.0
networkx==3.2.1
numpy==2.0.2
packaging==24.2
pillow==11.1.0
pluggy==1.5.0
psutil==7.0.0
pycollada==0.9
pyglet==2.1.3
Pygments==2.19.1
pytest==8.3.5
pytest-cov==6.0.0
python-dateutil==2.9.0.post0
python-fcl==0.7.0.8
PyYAML==6.0.2
referencing==0.36.2
requests==2.32.3
rich==14.0.0
rpds-py==0.24.0
rtree==1.4.0
scikit-image==0.24.0
scipy==1.13.1
shapely==2.0.7
signature_dispatch==1.0.1
six==1.17.0
svg.path==6.3
sympy==1.13.3
tifffile==2024.8.30
tomli==2.2.1
triangle==20250106
-e git+https://github.com/mikedh/trimesh.git@d5039e11fca9c3038014d4f6d078163b64650e89#egg=trimesh
typeguard==3.0.2
typing_extensions==4.13.0
urllib3==2.3.0
vecrec==0.3.1
xxhash==3.5.0
zipp==3.21.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
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - 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=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==25.3.0
      - autoprop==4.1.0
      - certifi==2025.1.31
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - colorlog==6.9.0
      - coverage==7.8.0
      - cython==3.0.12
      - exceptiongroup==1.2.2
      - glooey==0.3.6
      - idna==3.10
      - imageio==2.37.0
      - importlib-metadata==8.6.1
      - iniconfig==2.1.0
      - jsonschema==4.23.0
      - jsonschema-specifications==2024.10.1
      - lazy-loader==0.4
      - lxml==5.3.1
      - markdown-it-py==3.0.0
      - mdurl==0.1.2
      - meshio==5.3.5
      - more-itertools==10.6.0
      - mpmath==1.3.0
      - msgpack==1.1.0
      - networkx==3.2.1
      - numpy==2.0.2
      - packaging==24.2
      - pillow==11.1.0
      - pluggy==1.5.0
      - psutil==7.0.0
      - pycollada==0.9
      - pyglet==2.1.3
      - pygments==2.19.1
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - python-dateutil==2.9.0.post0
      - python-fcl==0.7.0.8
      - pyyaml==6.0.2
      - referencing==0.36.2
      - requests==2.32.3
      - rich==14.0.0
      - rpds-py==0.24.0
      - rtree==1.4.0
      - scikit-image==0.24.0
      - scipy==1.13.1
      - shapely==2.0.7
      - signature-dispatch==1.0.1
      - six==1.17.0
      - svg-path==6.3
      - sympy==1.13.3
      - tifffile==2024.8.30
      - tomli==2.2.1
      - triangle==20250106
      - typeguard==3.0.2
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - vecrec==0.3.1
      - xxhash==3.5.0
      - zipp==3.21.0
prefix: /opt/conda/envs/trimesh

tests/test_points.py::PointsTest::test_ply
tests/test_points.py::PointsTest::test_kmeans tests/test_points.py::PointsTest::test_obb tests/test_points.py::PointsTest::test_pointcloud tests/test_scene.py::SceneTests::test_3DXML tests/test_scene.py::SceneTests::test_add_concat tests/test_scene.py::SceneTests::test_add_geometry tests/test_scene.py::SceneTests::test_dedupe tests/test_scene.py::SceneTests::test_doubling tests/test_scene.py::SceneTests::test_dupe tests/test_scene.py::SceneTests::test_empty tests/test_scene.py::SceneTests::test_scaling tests/test_scene.py::SceneTests::test_scene tests/test_scene.py::SceneTests::test_transform tests/test_scene.py::SceneTests::test_tri tests/test_scene.py::SceneTests::test_zipped tests/test_scene.py::GraphTests::test_cache
tests/test_points.py::PointsTest::test_empty tests/test_points.py::PointsTest::test_plane 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 tests/test_scene.py::SceneTests::test_delete tests/test_scene.py::SceneTests::test_empty_scene tests/test_scene.py::GraphTests::test_forest
trimesh/scene/scene.py trimesh/parent.py examples/dockerRender/render.supervisor.conf trimesh/visual/base.py README.md trimesh/version.py trimesh/points.py trimesh/visual/color.py
trimesh/scene/scene.py trimesh/parent.py examples/dockerRender/render.supervisor.conf trimesh/visual/base.py README.md trimesh/version.py trimesh/points.py trimesh/visual/color.py