Skip to content

Commit

Permalink
#680 Vectorized And Merge Updates
Browse files Browse the repository at this point in the history
  • Loading branch information
mikedh committed Dec 30, 2019
2 parents 8c56330 + b5d9b36 commit 4b374d2
Show file tree
Hide file tree
Showing 18 changed files with 148 additions and 153 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Expand Up @@ -71,7 +71,6 @@ install:
- source activate test-environment
# make sure we parked our car in the right spot
- python -c "import os,sys;assert '{}.{}'.format(*sys.version_info[:2])==os.environ['PYTHON_VERSION']"

# only install the basic version of the library to make sure
# trimesh imports and functions with only the minimal install
- pip install .
Expand Down Expand Up @@ -103,6 +102,8 @@ script:
- if [[ "$PYTHON_VERSION" == "3.6" ]]; then pip install python-fcl; fi;
# lxml stopped supporting EOL Python
- if [[ "$PYTHON_VERSION" == "3.4" ]]; then pip install lxml==4.3.5; fi;
# sdist for released shapely has issues on 3.8
- if [[ "$PYTHON_VERSION" == "3.8" ]]; then pip install shapely==1.7a2; fi;

# install most deps here
- pip install -q .[easy]
Expand Down
2 changes: 1 addition & 1 deletion docker/builds/apt.bash
Expand Up @@ -2,7 +2,7 @@ set -xe
apt-get update -qq
apt-get upgrade -y -qq --no-install-recommends
apt-get install -y -qq --no-install-recommends wget bzip2 supervisor \
libgl1-mesa-glx libgl1-mesa-dri xvfb xauth libgeos-dev libspatialindex-c5 \
libgl1-mesa-glx libgl1-mesa-dri xvfb xauth \
libassimp-dev ca-certificates zstd unzip \
freeglut3-dev

Expand Down
2 changes: 1 addition & 1 deletion docker/builds/conda.bash
Expand Up @@ -19,7 +19,7 @@ conda config --add channels conda-forge
# pyembree is used for fast ray tests
# this will also install numpy from conda
# conda/numpy is compiled with intel's MKL
conda install pyembree
conda install pyembree rtree

# install trimesh from the repo
cd /tmp/trimesh
Expand Down
7 changes: 5 additions & 2 deletions tests/test_export.py
Expand Up @@ -16,7 +16,7 @@ def test_export(self):

for mesh in meshes:
# disregard texture
mesh.merge_vertices(textured=False)
mesh.merge_vertices(merge_tex=True, merge_norm=True)
for file_type in export_types:
# skip pointcloud format
if file_type in ['xyz', 'gltf']:
Expand Down Expand Up @@ -50,7 +50,10 @@ def test_export(self):
file_obj = export

loaded = g.trimesh.load(file_obj=file_obj,
file_type=file_type)
file_type=file_type,
process=True,
merge_norm=True,
merge_tex=True)

# if we exported as GLTF/dae it will come back as a Scene
if isinstance(loaded, g.trimesh.Scene) and isinstance(
Expand Down
26 changes: 25 additions & 1 deletion tests/test_gltf.py
Expand Up @@ -28,7 +28,7 @@ def test_duck(self):
g.scene_equal(scene, reloaded)

# if we merge ugly it should now be watertight
geom.merge_vertices(textured=False)
geom.merge_vertices(merge_tex=True)
assert geom.is_volume

def test_tex_export(self):
Expand Down Expand Up @@ -217,6 +217,30 @@ def test_node_name(self):
assert (set(s.graph.nodes_geometry) ==
set(r.graph.nodes_geometry))

def test_schema(self):
# get a copy of the GLTF schema and do simple checks
s = g.trimesh.exchange.gltf.get_schema()

# make sure it has at least the keys we expect
assert set(s['properties'].keys()).issuperset(
{'accessors',
'animations',
'asset',
'buffers',
'bufferViews',
'cameras',
'images',
'materials',
'meshes',
'nodes',
'samplers',
'scene',
'scenes',
'skins',
'textures',
'extensions',
'extras'})


if __name__ == '__main__':
g.trimesh.util.attach_to_log()
Expand Down
2 changes: 1 addition & 1 deletion tests/test_normals.py
Expand Up @@ -159,7 +159,7 @@ def test_merge(self):
assert g.np.isclose(m.volume, 8.0, atol=1e-4)

# without considering normals should just be cube
m.merge_vertices(use_norm=False)
m.merge_vertices(merge_norm=True)
assert m.vertices.shape == (8, 3)
assert m.faces.shape == (12, 3)
assert g.np.isclose(m.volume, 8.0, atol=1e-4)
Expand Down
18 changes: 1 addition & 17 deletions tests/test_paths.py
Expand Up @@ -7,15 +7,6 @@
class VectorTests(g.unittest.TestCase):

def test_discrete(self):
try:
# TODO : REMOVE THIS WHEN SHAPELY WORKS IN 3.8
from shapely import vectorized # noqa
vec_ok = True
except BaseException:
vec_ok = False
g.log.error(
'no shapely.vectorized!', exc_info=True)

for d in g.get_2D():
# store md5 before requesting passive functions
md5 = d.md5()
Expand Down Expand Up @@ -108,7 +99,7 @@ def test_discrete(self):
# Y should not have moved
assert g.np.allclose(d.bounds[:, 1], ori[:, 1])

if vec_ok and len(d.polygons_full) > 0 and len(d.vertices) < 150:
if len(d.polygons_full) > 0 and len(d.vertices) < 150:
g.log.info('Checking medial axis on %s',
d.metadata['file_name'])
m = d.medial_axis()
Expand Down Expand Up @@ -239,13 +230,6 @@ def test_sample(self):
"""
Test random sampling of polygons
"""
try:
# TODO : REMOVE THIS
from shapely import vectorized # noqa
except BaseException:
g.log.error(
'no shapely.vectorized!', exc_info=True)
return

p = g.Point([0, 0]).buffer(1.0)
count = 100
Expand Down
27 changes: 13 additions & 14 deletions trimesh/base.py
Expand Up @@ -194,18 +194,17 @@ def __init__(self,
# process will remove NaN and Inf values and merge vertices
# if validate, will remove degenerate and duplicate faces
if process or validate:
self.process()
self.process(validate=validate, **kwargs)

# save reference to kwargs
self._kwargs = kwargs

def process(self):
def process(self, **kwargs):
"""
Do the bare minimum processing to make a mesh useful.
Does this by:
1) removing NaN and Inf values
2) merging duplicate vertices
If self._validate:
Expand All @@ -226,10 +225,10 @@ def process(self):
# avoid clearing the cache during operations
with self._cache:
self.remove_infinite_values()
self.merge_vertices()
self.merge_vertices(**kwargs)
# if we're cleaning remove duplicate
# and degenerate faces
if self._validate:
if self._validate or ('validate' in kwargs and kwargs['validate']):
self.remove_duplicate_faces()
self.remove_degenerate_faces()
# since none of our process operations moved vertices or faces
Expand Down Expand Up @@ -1101,9 +1100,9 @@ def merge_vertices(self, **kwargs):
Number of digits to consider for UV coordinates
"""
if 'textured' in kwargs:
kwargs['use_tex'] = kwargs.pop('textured')
kwargs['merge_tex'] = not kwargs.pop('textured')
log.warning(
'merge_vertices depreciation: `textured`->`use_tex`')
'merge_vertices depreciation: `not textured`->`merge_tex`')
grouping.merge_vertices(self, **kwargs)

def update_vertices(self, mask, inverse=None):
Expand Down Expand Up @@ -1557,7 +1556,7 @@ def is_volume(self):
self.volume > 0.0)
return valid

@caching.cache_decorator
@property
def is_empty(self):
"""
Does the current mesh have data defined.
Expand Down Expand Up @@ -1918,7 +1917,7 @@ def subdivide(self, face_index=None):
return result

@log_time
def smoothed(self, angle=None, facet_minlen=4):
def smoothed(self, **kwargs):
"""
Return a version of the current mesh which will render
nicely, without changing source mesh.
Expand All @@ -1928,8 +1927,10 @@ def smoothed(self, angle=None, facet_minlen=4):
angle : float or None
Angle in radians face pairs with angles
smaller than this will appear smoothed
facet_minlen : int or None
Minimum length of facets to consider
facet_minarea : float or None
Minimum area fraction to consider
IE for `facets_minarea=25` only facets larger
than `mesh.area / 25` will be considered.
Returns
---------
Expand All @@ -1945,9 +1946,7 @@ def smoothed(self, angle=None, facet_minlen=4):
return cached
# run smoothing
smoothed = graph.smoothed(
self,
angle=angle,
facet_minlen=facet_minlen)
self, **kwargs)
self.visual._cache['smoothed'] = smoothed
return smoothed

Expand Down
11 changes: 7 additions & 4 deletions trimesh/collision.py
Expand Up @@ -242,8 +242,11 @@ def set_transform(self, name, transform):
else:
raise ValueError('{} not in collision manager!'.format(name))

def in_collision_single(self, mesh, transform=None,
return_names=False, return_data=False):
def in_collision_single(self,
mesh,
transform=None,
return_names=False,
return_data=False):
"""
Check a single object for collisions against all objects in the
manager.
Expand All @@ -265,10 +268,10 @@ def in_collision_single(self, mesh, transform=None,
is_collision : bool
True if a collision occurs and False otherwise
names : set of str
The set of names of objects that collided with the
[OPTIONAL] The set of names of objects that collided with the
provided one
contacts : list of ContactData
All contacts detected
[OPTIONAL] All contacts detected
"""
if transform is None:
transform = np.eye(4)
Expand Down
14 changes: 8 additions & 6 deletions trimesh/exchange/gltf.py
Expand Up @@ -1237,7 +1237,7 @@ def get_schema():
A copy of the GLTF 2.0 schema without external references.
"""
# replace references
from ..schemas import resolve_json
from ..schemas import resolve
# get zip resolver to access referenced assets
from ..visual.resolvers import ZipResolver

Expand All @@ -1247,11 +1247,13 @@ def get_schema():
archive = util.decompress(util.wrap_as_stream(blob), 'zip')
# get a resolver object for accessing the schema
resolver = ZipResolver(archive)
# remove references to other files in the schema and load
schema = json.loads(
resolve_json(
resolver.get('glTF.schema.json').decode('utf-8'),
resolver=resolver))
# get a loaded dict from the base file
unresolved = json.loads(util.decode_text(
resolver.get('glTF.schema.json')))
# remove references to other files in the schema
schema = resolve(unresolved,
resolver=resolver)

return schema


Expand Down
21 changes: 12 additions & 9 deletions trimesh/graph.py
Expand Up @@ -760,7 +760,7 @@ def edges_to_coo(edges, count=None, data=None):
return matrix


def smoothed(mesh, angle, facet_minlen=4):
def smoothed(mesh, angle=None, facet_minarea=15):
"""
Return a non- watertight version of the mesh which
will render nicely with smooth shading by
Expand All @@ -770,12 +770,13 @@ def smoothed(mesh, angle, facet_minlen=4):
-----------
mesh : trimesh.Trimesh
Source geometry
angle : float
Angle in radians: adjacent faces
below this angle will be smoothed
facet_minlen : None or int
If specified will specially group facets
with more faces
angle : float or None
Angle in radians face pairs with angles
smaller than this will appear smoothed
facet_minarea : float or None
Minimum area fraction to consider
IE for `facets_minarea=25` only facets larger
than `mesh.area / 25` will be considered.
Returns
---------
Expand All @@ -798,12 +799,14 @@ def smoothed(mesh, angle, facet_minlen=4):
facets = []
nodes = None
# collect coplanar regions for smoothing
if facet_minlen is not None:
if facet_minarea is not None:
areas = mesh.area_faces
min_area = mesh.area / facet_minarea
try:
# we can survive not knowing facets
# exclude facets with few faces
facets = [f for f in mesh.facets
if len(f) > facet_minlen]
if areas[f].sum() > min_area]
if len(facets) > 0:
# mask for removing adjacency pairs where
# one of the faces is contained in a facet
Expand Down

0 comments on commit 4b374d2

Please sign in to comment.