Skip to content

Commit 04a6cbb

Browse files
committed
cache and enclosure speedups
1 parent da911f1 commit 04a6cbb

10 files changed

Lines changed: 114 additions & 76 deletions

File tree

tests/test_cache.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ def test_hash(self):
4242
assert ct < mt
4343

4444
# xxhash should be faster than CRC and MD5
45-
if g.trimesh.caching.hasX:
45+
# it is sometimes slower on Windows/Appveyor TODO: figure out why
46+
if g.trimesh.caching.hasX and g.platform.system() == 'Linux':
4647
assert xt < mt
4748
assert xt < ct
4849

tests/test_transformations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class TransformTest(g.unittest.TestCase):
99
def test_doctest(self):
1010
"""
1111
Run doctests on transformations, which checks docstrings
12-
for interactive sessions and then verifies they execute
12+
for interactive sessions and then verifies they execute
1313
correctly.
1414
1515
This is how the upstream transformations unit tests,

trimesh/caching.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,14 +374,25 @@ def verify(self):
374374
value of id_function and delete all stored items if
375375
the value of id_function has changed.
376376
"""
377+
# if we are in a lock don't check anything
378+
if self._lock != 0:
379+
return
380+
381+
# check the hash of our data
377382
id_new = self._id_function()
378-
if (self._lock == 0) and (id_new != self.id_current):
383+
384+
# things changed
385+
if id_new != self.id_current:
379386
if len(self.cache) > 0:
380387
log.debug('%d items cleared from cache: %s',
381388
len(self.cache),
382389
str(list(self.cache.keys())))
383-
self.clear()
384-
self.id_set()
390+
# hash changed, so dump the cache
391+
# do it manually rather than calling clear()
392+
# as we are internal logic and can avoid function calls
393+
self.cache = {}
394+
# set the id to the new data hash
395+
self.id_current = id_new
385396

386397
def clear(self, exclude=None):
387398
"""

trimesh/comparison.py

Lines changed: 60 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -32,61 +32,67 @@ def identifier_simple(mesh):
3232
----------
3333
identifier: (6,) float, identifying values of the mesh
3434
"""
35-
36-
# pre-allocate identifier so indexes of values can't move around
37-
# like they might if we used hstack or something else
38-
identifier = np.zeros(6, dtype=np.float64)
39-
# avoid thrashing the cache unnecessarily
40-
mesh_area = mesh.area
41-
# start with properties that are valid regardless of watertightness
42-
# note that we're going to try to make all parameters relative
43-
# to area so other values don't get blown up at weird scales
44-
identifier[0] = mesh_area
45-
# topological constant and the only thing we can really
46-
# trust in this fallen world
47-
identifier[1] = mesh.euler_number
48-
# if we have a watertight mesh include volume and inertia
49-
if mesh.is_volume:
50-
# side length of a cube ratio
51-
# 1.0 for cubes, different values for other things
52-
identifier[2] = (((mesh_area / 6.0) ** (1.0 / 2.0)) /
53-
(mesh.volume ** (1.0 / 3.0)))
54-
# save vertices for radius calculation
55-
vertices = mesh.vertices - mesh.center_mass
56-
# we are going to special case radially symmetric meshes
57-
# to replace their surface area with ratio of their
58-
# surface area to a primitive sphere or cylinder surface area
59-
# this is because tessellated curved surfaces are really rough
60-
# to reliably hash as they are very sensitive to floating point
61-
# and tessellation error. By making area proportionate to a fit
62-
# primitive area we are able to reliably hash at more sigfigs
63-
if mesh.symmetry == 'radial':
64-
# cylinder height
65-
h = np.dot(vertices, mesh.symmetry_axis).ptp()
66-
# section radius
67-
R2 = (np.dot(vertices, mesh.symmetry_section.T)**2).sum(axis=1).max()
68-
# area of a cylinder primitive
69-
area = (2 * np.pi * (R2**.5) * h) + (2 * np.pi * R2)
70-
# replace area in this case with area ratio
71-
identifier[0] = mesh_area / area
72-
elif mesh.symmetry == 'spherical':
73-
# handle a spherically symmetric mesh
35+
# verify the cache once
36+
mesh._cache.verify()
37+
38+
# don't check hashes during identifier as we aren't
39+
# changing any data values of the mesh inside block
40+
# if we did change values in cache block things would break
41+
with mesh._cache:
42+
# pre-allocate identifier so indexes of values can't move around
43+
# like they might if we used hstack or something else
44+
identifier = np.zeros(6, dtype=np.float64)
45+
# avoid thrashing the cache unnecessarily
46+
mesh_area = mesh.area
47+
# start with properties that are valid regardless of watertightness
48+
# note that we're going to try to make all parameters relative
49+
# to area so other values don't get blown up at weird scales
50+
identifier[0] = mesh_area
51+
# topological constant and the only thing we can really
52+
# trust in this fallen world
53+
identifier[1] = mesh.euler_number
54+
# if we have a watertight mesh include volume and inertia
55+
if mesh.is_volume:
56+
# side length of a cube ratio
57+
# 1.0 for cubes, different values for other things
58+
identifier[2] = (((mesh_area / 6.0) ** (1.0 / 2.0)) /
59+
(mesh.volume ** (1.0 / 3.0)))
60+
# save vertices for radius calculation
61+
vertices = mesh.vertices - mesh.center_mass
62+
# we are going to special case radially symmetric meshes
63+
# to replace their surface area with ratio of their
64+
# surface area to a primitive sphere or cylinder surface area
65+
# this is because tessellated curved surfaces are really rough
66+
# to reliably hash as they are very sensitive to floating point
67+
# and tessellation error. By making area proportionate to a fit
68+
# primitive area we are able to reliably hash at more sigfigs
69+
if mesh.symmetry == 'radial':
70+
# cylinder height
71+
h = np.dot(vertices, mesh.symmetry_axis).ptp()
72+
# section radius
73+
R2 = (np.dot(vertices, mesh.symmetry_section.T)**2).sum(axis=1).max()
74+
# area of a cylinder primitive
75+
area = (2 * np.pi * (R2**.5) * h) + (2 * np.pi * R2)
76+
# replace area in this case with area ratio
77+
identifier[0] = mesh_area / area
78+
elif mesh.symmetry == 'spherical':
79+
# handle a spherically symmetric mesh
80+
R2 = (vertices ** 2).sum(axis=1).max()
81+
area = 4 * np.pi * R2
82+
identifier[0] = mesh_area / area
83+
else:
84+
# if we don't have a watertight mesh add information about the
85+
# convex hull, which is slow to compute and unreliable
86+
# just what we're looking for in a hash but hey
87+
identifier[3] = mesh_area / mesh.convex_hull.area
88+
# cube side length ratio for the hull
89+
identifier[4] = (((mesh.convex_hull.area / 6.0) ** (1.0 / 2.0)) /
90+
(mesh.convex_hull.volume ** (1.0 / 3.0)))
91+
vertices = mesh.vertices - mesh.centroid
92+
93+
# add in max radius^2 to area ratio
7494
R2 = (vertices ** 2).sum(axis=1).max()
75-
area = 4 * np.pi * R2
76-
identifier[0] = mesh_area / area
77-
else:
78-
# if we don't have a watertight mesh add information about the
79-
# convex hull, which is slow to compute and unreliable
80-
# just what we're looking for in a hash but hey
81-
identifier[3] = mesh_area / mesh.convex_hull.area
82-
# cube side length ratio for the hull
83-
identifier[4] = (((mesh.convex_hull.area / 6.0) ** (1.0 / 2.0)) /
84-
(mesh.convex_hull.volume ** (1.0 / 3.0)))
85-
vertices = mesh.vertices - mesh.centroid
86-
87-
# add in max radius^2 to area ratio
88-
R2 = (vertices ** 2).sum(axis=1).max()
89-
identifier[5] = R2 / mesh_area
95+
identifier[5] = R2 / mesh_area
9096

9197
return identifier
9298

trimesh/path/io/dxf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ def convert_bspline(e):
333333
# create a single Line entity
334334
entities.append(Line(points=len(vertices) +
335335
np.arange(2),
336-
**info(e)))
336+
**info(e)))
337337
# add the vertices to our collection
338338
vertices.extend(points)
339339
return

trimesh/path/polygons.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,16 @@ def enclosure_tree(polygons):
8484
# if there are multiple nested polygons split the graph
8585
# so the contains logic returns the individual polygons
8686
if len(degrees) > 0 and degrees.max() > 1:
87-
# this could also be done by removing edges but
88-
# the bookkeeping is a lot easier to comprehend
89-
# with subgraphs
90-
subgraphs = []
87+
# collect new edges for graph
88+
edges = []
89+
# find edges of subgraph for each root and children
9190
for root in roots:
9291
children = indexes[degrees == degree[root] + 1]
93-
subgraphs.append(contains.subgraph(np.append(children, root)))
94-
contains = nx.compose_all(subgraphs)
92+
edges.extend(contains.subgraph(np.append(children, root)).edges())
93+
# stack edges into new directed graph
94+
contains = nx.from_edgelist(edges, nx.DiGraph())
95+
# if roots have no children add them anyway
96+
contains.add_nodes_from(roots)
9597

9698
return roots, contains
9799

trimesh/path/traversal.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -359,17 +359,36 @@ def resample_path(points,
359359

360360

361361
def split(self):
362+
"""
363+
Split a Path2D into multiple Path2D objects where each
364+
one has exactly one root curve.
362365
363-
# if self.root is None or len(self.root) == 0:
364-
# return np.array([])
366+
Parameters
367+
--------------
368+
self : trimesh.path.Path2D
369+
Input geometry
365370
371+
Returns
372+
-------------
373+
split : list of trimesh.path.Path2D
374+
Original geometry as separate paths
375+
"""
376+
# avoid a circular import by referencing class of self
366377
Path2D = type(self)
367378

379+
# save the results of the split to an array
368380
split = []
369381

382+
# get objects from cache to avoid a bajillion
383+
# cache checks inside the tight loop
384+
paths = self.paths
385+
discrete = self.discrete
386+
polygons_closed = self.polygons_closed
387+
enclosure_directed = self.enclosure_directed
388+
370389
for root_index, root in enumerate(self.root):
371390
# get a list of the root curve's children
372-
connected = list(self.enclosure_directed[root].keys())
391+
connected = list(enclosure_directed[root].keys())
373392
# add the root node to the list
374393
connected.append(root)
375394

@@ -378,7 +397,7 @@ def split(self):
378397
new_entities = []
379398

380399
for index in connected:
381-
path = self.paths[index]
400+
path = paths[index]
382401
# add a path which is just sequential indexes
383402
new_paths.append(np.arange(len(path)) +
384403
len(new_entities))
@@ -401,8 +420,8 @@ def split(self):
401420
# add back expensive things to the cache
402421
split[-1]._cache.update(
403422
{'paths': new_paths,
404-
'polygons_closed': self.polygons_closed[connected],
405-
'discrete': self.discrete[connected],
423+
'polygons_closed': polygons_closed[connected],
424+
'discrete': discrete[connected],
406425
'root': new_root})
407426
# set the cache ID
408427
split[-1]._cache.id_set()

trimesh/ray/ray_pyembree.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ def _scene(self):
7070
"""
7171
A cached version of the pyembree scene.
7272
"""
73-
7473
return _EmbreeWrap(vertices=self.mesh.vertices,
7574
faces=self.mesh.faces,
7675
scale=self._scale)
@@ -296,7 +295,7 @@ class _EmbreeWrap(object):
296295
"""
297296
A light wrapper for PyEmbree scene objects which
298297
allows queries to be scaled to help with precision
299-
issues, as well as selectring the correct dtypes.
298+
issues, as well as selecting the correct dtypes.
300299
"""
301300

302301
def __init__(self, vertices, faces, scale):

trimesh/registration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ def mesh_other(mesh,
5555
def key_points(m, count):
5656
"""
5757
Return a combination of mesh vertices and surface samples
58-
with vertices chosen by likelyhood to be important
58+
with vertices chosen by likelyhood to be important
5959
to registation.
6060
"""
6161
if len(m.vertices) < (count / 2):

trimesh/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.33.42'
1+
__version__ = '2.33.43'

0 commit comments

Comments
 (0)