Skip to content

Commit

Permalink
added edges_unique_length and some notebook examples
Browse files Browse the repository at this point in the history
  • Loading branch information
mikedh committed Sep 8, 2018
1 parent c66809f commit 2d0f29f
Show file tree
Hide file tree
Showing 13 changed files with 851 additions and 53 deletions.
9 changes: 6 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ before_deploy:
- python docs/build.py

# extract the current trimesh version from the version file
- export TMVERSION=`python -c "f = open('trimesh/version.py', 'r'); exec(f.read()); print(__version__)"`
- export TMVERSION=`python -c "exec(open('trimesh/version.py','r').read()); print(__version__)"`

# tag the release and if it's already tagged chill
- git tag $TMVERSION || true
Expand Down Expand Up @@ -75,17 +75,20 @@ install:

script:

# try simple tests with only 3 minimal deps
- python -c "import trimesh"
- conda install scikit-image rtree shapely
- pytest tests/test_inertia.py

# install most deps here
- conda install scikit-image rtree shapely
# eat exit codes for these two packages
# pyembree and python-fcl not available everywhere
- conda install pyembree || true
- pip install python-fcl || true
- pip install .[easy]
- pip install triangle xxhash

# run tests
# run main tests
- pytest --cov=trimesh tests/

# downgrade networkx from 2.x to 1.x to make sure our
Expand Down
6 changes: 6 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ How to slice a mesh using trimesh:

How do do signed distance and proximity queries:
`Nearest point queries <examples/nearest.html>`__

Find a path from one mesh vertex to another, travelling along edges of the mesh:
`Edge graph traversal <examples/shortest.html>`__

Do simple ray- mesh queries, including finding the indexes of triangles hit by rays, the locations of points hit on the mesh surface, etc. Ray queries have the same API with two available backends, one implemented in pure numpy and one that requires pyembree but is 50x faster.
`Ray-mesh queries <examples/ray.html>`__
398 changes: 398 additions & 0 deletions examples/ray.ipynb

Large diffs are not rendered by default.

354 changes: 354 additions & 0 deletions examples/shortest.ipynb

Large diffs are not rendered by default.

21 changes: 12 additions & 9 deletions examples/shortest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@
edges = mesh.edges_unique

# the actual length of each unique edge
length = np.linalg.norm(mesh.vertices[edges[:, 0]] -
mesh.vertices[edges[:, 1]],
axis=1)
length = mesh.edges_unique_length

# create the graph with edge attributes for length
g = nx.Graph()
Expand All @@ -49,10 +47,15 @@
# VISUALIZE RESULT
# make the sphere transparent-ish
mesh.visual.face_colors = [100, 100, 100, 100]
# make a scene with the two points, the path between them
# and the original mesh we were working on
# Path3D with the path between the points
path_visual = trimesh.load_path(mesh.vertices[path])
# visualizable two points
points_visual = trimesh.points.PointCloud(mesh.vertices[[start, end]])

# create a scene with the mesh, path, and points
scene = trimesh.Scene([
trimesh.points.PointCloud(mesh.vertices[[start, end]]),
mesh,
trimesh.load_path(mesh.vertices[path])])
scene.show()
points_visual,
path_visual,
mesh])

scene.show(smooth=False)
15 changes: 6 additions & 9 deletions tests/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@
import subprocess

import numpy as np
import sympy as sp

try:
import sympy as sp
except ImportError:
pass

import trimesh
import collections
Expand Down Expand Up @@ -79,14 +83,7 @@
break
"""


def io_wrap(item):
if isinstance(item, str):
return StringIO(item)
if _PY3 and isinstance(item, bytes):
return BytesIO(item)
return item

io_wrap = trimesh.util.wrap_as_stream

def _load_data():
data = {}
Expand Down
9 changes: 8 additions & 1 deletion tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ def test_export(self):

g.log.info('Export/import testing on %s',
mesh.metadata['file_name'])
loaded = g.trimesh.load(file_obj=g.io_wrap(export),

# if export is string or bytes wrap as pseudo file object
if isinstance(export, str) or isinstance(export, bytes):
file_obj = g.io_wrap(export)
else:
file_obj = export

loaded = g.trimesh.load(file_obj=file_obj,
file_type=file_type)

if (not g.trimesh.util.is_shape(loaded._data['faces'], (-1, 3)) or
Expand Down
22 changes: 11 additions & 11 deletions tests/test_mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ def test_meshes(self):

for mesh in meshes:
g.log.info('Testing %s', mesh.metadata['file_name'])
self.assertTrue(len(mesh.faces) > 0)
self.assertTrue(len(mesh.vertices) > 0)
assert len(mesh.faces) > 0
assert len(mesh.vertices) > 0

self.assertTrue(len(mesh.edges) > 0)
self.assertTrue(len(mesh.edges_unique) > 0)
self.assertTrue(len(mesh.edges_sorted) > 0)
self.assertTrue(len(mesh.edges_face) > 0)
self.assertTrue(isinstance(mesh.euler_number, int))
assert len(mesh.edges) > 0
assert len(mesh.edges_unique) > 0
assert len(mesh.edges_sorted) > 0
assert len(mesh.edges_face) > 0
assert isinstance(mesh.euler_number, int)

mesh.process()

Expand Down Expand Up @@ -59,10 +59,10 @@ def test_meshes(self):
# make sure vertex kdtree and triangles rtree exist

t = mesh.kdtree
self.assertTrue(hasattr(t, 'query'))
assert hasattr(t, 'query')
g.log.info('Creating triangles tree')
r = mesh.triangles_tree
self.assertTrue(hasattr(r, 'intersection'))
assert hasattr(r, 'intersection')
g.log.info('Triangles tree ok')

# some memory issues only show up when you copy the mesh a bunch
Expand All @@ -88,12 +88,12 @@ def test_meshes(self):
def test_vertex_neighbors(self):
m = g.trimesh.primitives.Box()
neighbors = m.vertex_neighbors
self.assertTrue(len(neighbors) == len(m.vertices))
assert len(neighbors) == len(m.vertices)
elist = m.edges_unique.tolist()

for v_i, neighs in enumerate(neighbors):
for n in neighs:
self.assertTrue(([v_i, n] in elist or [n, v_i] in elist))
assert ([v_i, n] in elist or [n, v_i] in elist)


if __name__ == '__main__':
Expand Down
25 changes: 20 additions & 5 deletions trimesh/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,20 @@ def edges_unique(self):
self._cache['edges_unique_inv'] = inverse
return edges_unique

@caching.cache_decorator
def edges_unique_length(self):
"""
How long is each unique edge.
Returns
----------
length : (len(self.edges_unique), ) float
Length of each unique edge
"""
vector = np.subtract(*self.vertices[self.edges_unique.T])
length = np.linalg.norm(vector, axis=1)
return length

@caching.cache_decorator
def edges_sorted(self):
"""
Expand All @@ -914,14 +928,16 @@ def edges_sorted(self):
@caching.cache_decorator
def edges_sparse(self):
"""
Edges in sparse COO graph format.
Edges in sparse bool COO graph format where connected
vertices are True.
Returns
----------
sparse: (len(self.vertices), len(self.vertices)) bool
Sparse graph in COO format
"""
sparse = graph.edges_to_coo(self.edges)
sparse = graph.edges_to_coo(self.edges,
count=len(self.vertices))
return sparse

@caching.cache_decorator
Expand Down Expand Up @@ -1622,9 +1638,8 @@ def facets_on_hull(self):
# a facet plane is on the convex hull if every vertex
# of the convex hull is behind that plane
# which we are checking with dot products
on_hull[i] = (np.dot(
normal,
(convex - origin).T) < tol.merge).all()
dot = np.dot(normal, (convex - origin).T)
on_hull[i] = (dot < tol.merge).all()

return on_hull

Expand Down
32 changes: 21 additions & 11 deletions trimesh/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,35 +683,45 @@ def traversals(edges, mode='bfs'):
return traversals


def edges_to_coo(edges, count=None):
def edges_to_coo(edges, count=None, data=None):
"""
Given an edge list, return a boolean scipy.sparse.coo_matrix
representing the edges in matrix form.
Parameters
------------
edges: (n,2) int, edges of a graph
count: int, the number of nodes.
if None: count = edges.max() + 1
edges : (n,2) int
Edges of a graph
count : int
The total number of nodes in the graph
if None: count = edges.max() + 1
data : (n,) any
Assign data to each edge, if None will
be bool True for each specified edge
Returns
------------
matrix: (count, count) bool, scipy.sparse.coo_matrix
matrix: (count, count) scipy.sparse.coo_matrix
Sparse COO
"""
edges = np.asanyarray(edges, dtype=np.int64)
if not (len(edges) == 0 or
util.is_shape(edges, (-1, 2))):
raise ValueError('edges must be (n,2)!')

# if count isn't specified just set it to largest
# value referenced in edges
if count is None:
count = edges.max() + 1
else:
count = int(count)
count = int(count)

# if no data is specified set every specified edge
# to True
if data is None:
data = np.ones(len(edges), dtype=np.bool)

matrix = coo_matrix((np.ones(len(edges),
dtype=np.bool),
(edges[:, 0], edges[:, 1])),
dtype=np.bool,
matrix = coo_matrix((data, edges.T),
dtype=data.dtype,
shape=(count, count))
return matrix

Expand Down
1 change: 1 addition & 0 deletions trimesh/scene/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def add_geometry(self,

if geometry is None:
return
# PointCloud objects will look like a sequence
elif util.is_sequence(geometry):
# if passed a sequence add all elements
return [self.add_geometry(i) for i in geometry]
Expand Down
10 changes: 7 additions & 3 deletions trimesh/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ def is_sequence(obj):
set,
basestring))

# PointCloud objects can look like an array but are not
seq = seq and type(obj).__name__ not in ['PointCloud']

# numpy sometimes returns objects that are single float64 values
# but sure look like sequences, so we check the shape
if hasattr(obj, 'shape'):
Expand Down Expand Up @@ -1454,11 +1457,12 @@ def bounds_tree(bounds):

def wrap_as_stream(item):
"""
Wrap a string or bytes object as a file object
Wrap a string or bytes object as a file object.
Parameters
----------
item: str or bytes: item to be wrapped
item: str or bytes
Item to be wrapped
Returns
---------
Expand All @@ -1470,7 +1474,7 @@ def wrap_as_stream(item):
return StringIO(item)
elif isinstance(item, bytes):
return BytesIO(item)
raise ValueError('Not a wrappable item!')
raise ValueError('{} is not wrappable!'.format(type(item).__name__))


def sigfig_round(values, sigfig=1):
Expand Down
2 changes: 1 addition & 1 deletion trimesh/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '2.33.25'
__version__ = '2.33.26'

0 comments on commit 2d0f29f

Please sign in to comment.