Skip to content

Commit a069626

Browse files
committed
voxel fix and remote loading
1 parent 5ed1ea4 commit a069626

8 files changed

Lines changed: 163 additions & 60 deletions

File tree

.travis.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
language: python
22
sudo: required
33
dist: trusty
4+
45
python:
56
- '2.7'
67
- '3.4'
78
- '3.5'
89
- '3.6'
10+
911
before_deploy:
1012
- sudo apt-get install pandoc -y
1113
- pip install pypandoc
@@ -38,14 +40,14 @@ deploy:
3840
api_key: $GITHUB_TOKEN
3941
skip_cleanup: true
4042
file: "../trimesh-$TMVERSION.tar.gz"
41-
on:
42-
tags: true
43+
4344
before_install:
4445
- sudo apt-get update
4546
- sudo apt-get install -y openscad blender meshlab xvfb
4647
- sudo wget https://github.com/mikedh/v-hacd-1/raw/master/bin/linux/testVHACD --quiet
4748
-P /usr/bin
4849
- sudo chmod +x /usr/bin/testVHACD
50+
4951
install:
5052
- if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then wget --quiet https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh
5153
-O miniconda.sh; else wget --quiet https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
@@ -61,6 +63,7 @@ install:
6163
- source activate test-environment
6264
- pip install .
6365
- pip install pytest pytest-cov coveralls
66+
6467
script:
6568
- python -c "import trimesh"
6669
- conda install scikit-image rtree shapely

setup.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
'sympy',
5656
'msgpack',
5757
'pillow',
58+
'requests',
5859
'colorlog'],
5960
'all': ['lxml',
6061
'pyglet',
@@ -67,6 +68,7 @@
6768
'python-fcl',
6869
'colorlog',
6970
'xxhash',
71+
'requests',
7072
'pillow',
7173
'setuptools']}
7274
)

tests/test_collision.py

Lines changed: 37 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -28,50 +28,51 @@ def test_collision(self):
2828
m.add_object('cube1', cube, tf1)
2929

3030
ret = m.in_collision_single(cube)
31-
self.assertTrue(ret == True)
31+
assert ret == True
3232

3333
ret, names, data = m.in_collision_single(cube,
3434
tf1,
3535
return_names=True,
3636
return_data=True)
37-
self.assertTrue(ret == True)
37+
38+
assert ret == True
3839
assert all(len(i.point) == 3 for i in data)
3940

4041
if 'cube1' not in names:
4142
print('\n\n', m._objs.keys(), names)
42-
self.assertTrue('cube1' in names)
43+
assert 'cube1' in names
4344

4445
ret, names, data = m.in_collision_single(cube,
4546
tf2,
4647
return_names=True,
4748
return_data=True)
48-
self.assertTrue(ret == False)
49-
self.assertTrue(len(names) == 0)
49+
assert ret == False
50+
assert len(names) == 0
5051
assert all(len(i.point) == 3 for i in data)
5152

5253
# Test internal collision checking and object
5354
# addition/removal/modification
5455
ret = m.in_collision_internal()
55-
self.assertTrue(ret == False)
56+
assert ret == False
5657

5758
m.add_object('cube2', cube, tf1)
5859
ret, names = m.in_collision_internal(return_names=True)
59-
self.assertTrue(ret == True)
60-
self.assertTrue(('cube1', 'cube2') in names)
61-
self.assertTrue(('cube0', 'cube1') not in names)
62-
self.assertTrue(('cube2', 'cube1') not in names)
60+
assert ret == True
61+
assert ('cube1', 'cube2') in names
62+
assert ('cube0', 'cube1') not in names
63+
assert ('cube2', 'cube1') not in names
6364

6465
m.set_transform('cube2', tf2)
6566
ret = m.in_collision_internal()
66-
self.assertTrue(ret == False)
67+
assert ret == False
6768

6869
m.set_transform('cube2', tf1)
6970
ret = m.in_collision_internal()
70-
self.assertTrue(ret == True)
71+
assert ret == True
7172

7273
m.remove_object('cube2')
7374
ret = m.in_collision_internal()
74-
self.assertTrue(ret == False)
75+
assert ret == False
7576

7677
# Test manager-to-manager collision checking
7778
m = g.trimesh.collision.CollisionManager()
@@ -82,17 +83,17 @@ def test_collision(self):
8283
n.add_object('cube0', cube, tf2)
8384

8485
ret = m.in_collision_other(n)
85-
self.assertTrue(ret == False)
86+
assert ret == False
8687

8788
n.add_object('cube3', cube, tf1)
8889

8990
ret = m.in_collision_other(n)
90-
self.assertTrue(ret == True)
91+
assert ret == True
9192

9293
ret, names = m.in_collision_other(n, return_names=True)
93-
self.assertTrue(ret == True)
94-
self.assertTrue(('cube1', 'cube3') in names)
95-
self.assertTrue(('cube3', 'cube1') not in names)
94+
assert ret == True
95+
assert ('cube1', 'cube3') in names
96+
assert ('cube3', 'cube1') not in names
9697

9798
def test_distance(self):
9899
# Ensure that FCL is importable
@@ -121,42 +122,42 @@ def test_distance(self):
121122
m.add_object('cube1', cube, tf1)
122123

123124
dist = m.min_distance_single(cube)
124-
self.assertTrue(g.np.isclose(dist, 4.0))
125+
assert g.np.isclose(dist, 4.0)
125126

126127
dist, name = m.min_distance_single(cube, return_name=True)
127-
self.assertTrue(g.np.isclose(dist, 4.0))
128-
self.assertTrue(name == 'cube1')
128+
assert g.np.isclose(dist, 4.0)
129+
assert name == 'cube1'
129130

130131
m.add_object('cube2', cube, tf2)
131132

132133
dist, name = m.min_distance_single(cube, tf3, return_name=True)
133-
self.assertTrue(g.np.isclose(dist, 2.0))
134-
self.assertTrue(name == 'cube1')
134+
assert g.np.isclose(dist, 2.0)
135+
assert name == 'cube1'
135136

136137
dist, name = m.min_distance_single(cube, tf4, return_name=True)
137-
self.assertTrue(g.np.isclose(dist, 2.0))
138-
self.assertTrue(name == 'cube2')
138+
assert g.np.isclose(dist, 2.0)
139+
assert name == 'cube2'
139140

140141
# Test internal distance checking and object
141142
# addition/removal/modification
142143
dist = m.min_distance_internal()
143-
self.assertTrue(g.np.isclose(dist, 9.0))
144+
assert g.np.isclose(dist, 9.0)
144145

145146
dist, names = m.min_distance_internal(return_names=True)
146-
self.assertTrue(g.np.isclose(dist, 9.0))
147-
self.assertTrue(names == ('cube1', 'cube2'))
147+
assert g.np.isclose(dist, 9.0)
148+
assert names == ('cube1', 'cube2')
148149

149150
m.add_object('cube3', cube, tf3)
150151

151152
dist, names = m.min_distance_internal(return_names=True)
152-
self.assertTrue(g.np.isclose(dist, 2.0))
153-
self.assertTrue(names == ('cube1', 'cube3'))
153+
assert g.np.isclose(dist, 2.0)
154+
assert names == ('cube1', 'cube3')
154155

155156
m.set_transform('cube3', tf4)
156157

157158
dist, names = m.min_distance_internal(return_names=True)
158-
self.assertTrue(g.np.isclose(dist, 2.0))
159-
self.assertTrue(names == ('cube2', 'cube3'))
159+
assert g.np.isclose(dist, 2.0)
160+
assert names == ('cube2', 'cube3')
160161

161162
# Test manager-to-manager distance checking
162163
m = g.trimesh.collision.CollisionManager()
@@ -167,14 +168,14 @@ def test_distance(self):
167168
n.add_object('cube0', cube, tf2)
168169

169170
dist, names = m.min_distance_other(n, return_names=True)
170-
self.assertTrue(g.np.isclose(dist, 4.0))
171-
self.assertTrue(names == ('cube0', 'cube0'))
171+
assert g.np.isclose(dist, 4.0)
172+
assert names == ('cube0', 'cube0')
172173

173174
n.add_object('cube4', cube, tf4)
174175

175176
dist, names = m.min_distance_other(n, return_names=True)
176-
self.assertTrue(g.np.isclose(dist, 1.0))
177-
self.assertTrue(names == ('cube0', 'cube4'))
177+
assert g.np.isclose(dist, 1.0)
178+
assert names == ('cube0', 'cube4')
178179

179180
def test_scene(self):
180181
try:

tests/test_loaded.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ def test_obj_groups(self):
2020
# check to make sure there is signal not just zeros
2121
assert mesh.metadata['face_groups'].ptp() > 0
2222

23+
def test_remote(self):
24+
"""
25+
Try loading a remote mesh using requests
26+
"""
27+
# get a unit cube from project's github
28+
mesh = g.trimesh.io.load.load_remote(
29+
url='https://github.com/mikedh/trimesh/raw/master/models/unit_cube.STL')
30+
31+
assert g.np.isclose(mesh.volume, 1.0)
32+
assert isinstance(mesh, g.trimesh.Trimesh)
33+
2334
def test_obj_quad(self):
2435
mesh = g.get_mesh('quadknot.obj')
2536
# make sure some data got loaded

tests/test_voxel.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
class VoxelTest(g.unittest.TestCase):
88

99
def test_voxel(self):
10-
'''
10+
"""
1111
Test that voxels work at all
12-
'''
12+
"""
1313
for m in [g.get_mesh('featuretype.STL'),
1414
g.trimesh.primitives.Box(),
1515
g.trimesh.primitives.Sphere()]:
@@ -72,6 +72,34 @@ def test_marching(self):
7272
except ImportError:
7373
g.log.info('no skimage, skipping marching cubes test')
7474

75+
def test_local(self):
76+
"""
77+
Try calling local voxel functions
78+
"""
79+
mesh = g.trimesh.creation.box()
80+
81+
# it should have some stuff
82+
voxel = g.trimesh.voxel.local_voxelize(mesh=mesh,
83+
point=[.5, .5, .5],
84+
pitch=.1,
85+
radius=5,
86+
fill=True)
87+
88+
assert len(voxel[0].shape) == 3
89+
90+
# try it when it definitly doesn't hit anything
91+
empty = g.trimesh.voxel.local_voxelize(mesh=mesh,
92+
point=[10, 10, 10],
93+
pitch=.1,
94+
radius=5,
95+
fill=True)
96+
97+
# try it when it is in the center of a volume
98+
center = g.trimesh.voxel.local_voxelize(mesh=mesh,
99+
point=[0, 0, 0],
100+
pitch=.1,
101+
radius=2,
102+
fill=True)
75103

76104
if __name__ == '__main__':
77105
g.trimesh.util.attach_to_log()

trimesh/io/load.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def load(file_obj, file_type=None, **kwargs):
104104
file_type, # str, what kind of file
105105
metadata, # dict, any metadata from file name
106106
opened # bool, did we open the file ourselves
107-
) = _parse_file_args(file_obj, file_type)
107+
) = parse_file_args(file_obj, file_type)
108108

109109
if isinstance(file_obj, dict):
110110
# if we've been passed a dict treat it as kwargs
@@ -167,7 +167,7 @@ def load_mesh(file_obj, file_type=None, **kwargs):
167167
(file_obj,
168168
file_type,
169169
metadata,
170-
opened) = _parse_file_args(file_obj, file_type)
170+
opened) = parse_file_args(file_obj, file_type)
171171

172172
# make sure we keep passed kwargs to loader
173173
# but also make sure loader keys override passed keys
@@ -221,7 +221,7 @@ def load_compressed(file_obj, file_type=None, mixed=False):
221221
(file_obj,
222222
file_type,
223223
metadata,
224-
opened) = _parse_file_args(file_obj, file_type)
224+
opened) = parse_file_args(file_obj, file_type)
225225

226226
# a dict of 'name' : file-like object
227227
files = util.decompress(file_obj=file_obj,
@@ -274,6 +274,34 @@ def load_compressed(file_obj, file_type=None, mixed=False):
274274
return result
275275

276276

277+
def load_remote(url, **kwargs):
278+
"""
279+
Load a mesh at a remote URL into a local trimesh object.
280+
281+
This must be called explicitly rather than automatically
282+
from trimesh.load to ensure users don't accidentally make
283+
network requests.
284+
285+
Parameters
286+
------------
287+
url : string
288+
URL containing mesh file
289+
**kwargs : passed to `load`
290+
"""
291+
# import here to keep requirement soft
292+
import requests
293+
294+
# download the mesh
295+
response = requests.get(url)
296+
# wrap as file object
297+
file_obj = util.wrap_as_stream(response.content)
298+
# actually load
299+
loaded = load(file_obj=file_obj,
300+
file_type=url,
301+
**kwargs)
302+
return loaded
303+
304+
277305
def load_kwargs(*args, **kwargs):
278306
"""
279307
Load geometry from a properly formatted dict or kwargs
@@ -339,10 +367,12 @@ def handle_trimesh_export():
339367
return handler()
340368

341369

342-
def _parse_file_args(file_obj, file_type, **kwargs):
370+
def parse_file_args(file_obj,
371+
file_type,
372+
**kwargs):
343373
"""
344-
Given a file_obj and a file_type, try to turn them into a file-like object
345-
and a lowercase string of file type
374+
Given a file_obj and a file_type try to turn them into a file-like
375+
object and a lowercase string of file type.
346376
347377
Parameters
348378
-----------
@@ -356,6 +386,11 @@ def _parse_file_args(file_obj, file_type, **kwargs):
356386
file_obj: the same string passed as file_obj
357387
file_type: set to 'json'
358388
389+
str: string is a valid URL
390+
-------------------------------------------
391+
file_obj: an open 'rb' file object with retrieved data
392+
file_type: from the extension
393+
359394
str: string is not an existing path or a JSON-like object
360395
-------------------------------------------
361396
ValueError will be raised as we can't do anything with input
@@ -416,9 +451,11 @@ def _parse_file_args(file_obj, file_type, **kwargs):
416451
# if a dict bracket is in the string, its probably a straight
417452
# JSON
418453
file_type = 'json'
454+
elif 'https://' in file_obj or 'http://' in file_obj:
455+
# we've been passed a URL so retrieve it
456+
raise ValueError('use load_remote to load URL!')
419457
else:
420-
raise ValueError(
421-
'File object passed as string that is not a file!')
458+
raise ValueError('string is not a file!')
422459

423460
if file_type is None:
424461
file_type = file_obj.__class__.__name__

trimesh/version.py

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

0 commit comments

Comments
 (0)