Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Scene Exports #1523

Merged
merged 6 commits into from Mar 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
@@ -1,14 +1,14 @@
[![trimesh](https://trimsh.org/images/logotype-a.svg)](http://trimsh.org)

-----------
[![Github Actions](https://github.com/mikedh/trimesh/workflows/Release%20Trimesh/badge.svg)](https://github.com/mikedh/trimesh/actions) [![PyPI version](https://badge.fury.io/py/trimesh.svg)](https://badge.fury.io/py/trimesh) [![codecov](https://codecov.io/gh/mikedh/trimesh/branch/master/graph/badge.svg?token=4PVRQXyl2h)](https://codecov.io/gh/mikedh/trimesh)
[![Github Actions](https://github.com/mikedh/trimesh/workflows/Release%20Trimesh/badge.svg)](https://github.com/mikedh/trimesh/actions) [![PyPI version](https://badge.fury.io/py/trimesh.svg)](https://badge.fury.io/py/trimesh) [![codecov](https://codecov.io/gh/mikedh/trimesh/branch/main/graph/badge.svg?token=4PVRQXyl2h)](https://codecov.io/gh/mikedh/trimesh)


Trimesh is a pure Python (2.7-3.4+) library for loading and using [triangular meshes](https://en.wikipedia.org/wiki/Triangle_mesh) with an emphasis on watertight surfaces. The goal of the library is to provide a full featured and well tested Trimesh object which allows for easy manipulation and analysis, in the style of the Polygon object in the [Shapely library](https://github.com/Toblerity/Shapely).

The API is mostly stable, but this should not be relied on and is not guaranteed: install a specific version if you plan on deploying something using trimesh.

Pull requests are appreciated and responded to promptly! If you'd like to contribute, here is an [up to date list of potential enhancements](https://github.com/mikedh/trimesh/issues/199) although things not on that list are also welcome. Here are some [tips for writing mesh code in Python.](https://github.com/mikedh/trimesh/blob/master/trimesh/exchange/README.md)
Pull requests are appreciated and responded to promptly! If you'd like to contribute, here is an [up to date list of potential enhancements](https://github.com/mikedh/trimesh/issues/199) although things not on that list are also welcome. Here are some [tips for writing mesh code in Python.](https://github.com/mikedh/trimesh/blob/main/trimesh/exchange/README.md)


## Basic Installation
Expand All @@ -29,7 +29,7 @@ Further information is available in the [advanced installation documentation](ht
## Quick Start

Here is an example of loading a mesh from file and colorizing its faces. Here is a nicely formatted
[ipython notebook version](https://trimsh.org/examples/quick_start.html) of this example. Also check out the [cross section example](https://trimsh.org/examples/section.html) or possibly the [integration of a function over a mesh example](https://github.com/mikedh/trimesh/blob/master/examples/integrate.ipynb).
[ipython notebook version](https://trimsh.org/examples/quick_start.html) of this example. Also check out the [cross section example](https://trimsh.org/examples/section.html) or possibly the [integration of a function over a mesh example](https://github.com/mikedh/trimesh/blob/main/examples/integrate.ipynb).

```python
import numpy as np
Expand Down Expand Up @@ -216,9 +216,9 @@ A question that comes up pretty frequently is [how to cite the library.](https:/

## Containers

If you want to deploy something in a container that uses trimesh, automated `debian:buster-slim` based builds with trimesh and dependencies are available on [Docker Hub](https://hub.docker.com/repository/docker/trimesh/trimesh) with image tags for `latest`, git short hash for the commit in master (i.e. `trimesh/trimesh:0c1298d`), and version (i.e. `trimesh/trimesh:3.5.27`):
If you want to deploy something in a container that uses trimesh, automated `debian:buster-slim` based builds with trimesh and dependencies are available on [Docker Hub](https://hub.docker.com/repository/docker/trimesh/trimesh) with image tags for `latest`, git short hash for the commit in main (i.e. `trimesh/trimesh:0c1298d`), and version (i.e. `trimesh/trimesh:3.5.27`):

`docker pull trimesh/trimesh`

[Here's an example](https://github.com/mikedh/trimesh/tree/master/examples/dockerRender) of how to render meshes using LLVMpipe and XVFB inside a container.
[Here's an example](https://github.com/mikedh/trimesh/tree/main/examples/dockerRender) of how to render meshes using LLVMpipe and XVFB inside a container.

17 changes: 8 additions & 9 deletions tests/test_obj.py
Expand Up @@ -239,17 +239,16 @@ def test_empty_or_pointcloud(self):
e = g.get_mesh('emptyIO/' + empty_file)

# create export
export = e.export(file_type='ply')
reconstructed = g.wrapload(export, file_type='ply')

if 'empty' in empty_file:
# result should be an empty scene without vertices
assert isinstance(e, g.trimesh.Scene)
assert not hasattr(e, 'vertices')
# export should not contain geometry
assert isinstance(reconstructed, g.trimesh.Scene)
assert not hasattr(reconstructed, 'vertices')
try:
export = e.export(file_type='ply')
except BaseException:
continue
raise ValueError('cannot export empty')
elif 'points' in empty_file:
export = e.export(file_type='ply')
reconstructed = g.wrapload(export, file_type='ply')

# result should be a point cloud instance
assert isinstance(e, g.trimesh.PointCloud)
assert hasattr(e, 'vertices')
Expand Down
21 changes: 10 additions & 11 deletions tests/test_ply.py
Expand Up @@ -160,19 +160,18 @@ def test_empty_or_pointcloud(self):

for empty_file in empty_files:
e = g.get_mesh('emptyIO/' + empty_file)

# create export
export = e.export(file_type='ply')
reconstructed = g.wrapload(export, file_type='ply')

if 'empty' in empty_file:
# result should be an empty scene without vertices
assert isinstance(e, g.trimesh.Scene)
assert not hasattr(e, 'vertices')
# export should not contain geometry
assert isinstance(reconstructed, g.trimesh.Scene)
assert not hasattr(reconstructed, 'vertices')
# result should be an empty scene
try:
e.export(file_type='ply')
except BaseException:
continue
raise ValueError('should not export empty')
elif 'points' in empty_file:
# create export
export = e.export(file_type='ply')
reconstructed = g.wrapload(export, file_type='ply')

# result should be a point cloud instance
assert isinstance(e, g.trimesh.PointCloud)
assert hasattr(e, 'vertices')
Expand Down
32 changes: 25 additions & 7 deletions tests/test_scene.py
Expand Up @@ -107,9 +107,8 @@ def test_scene(self):
s.explode()

def test_scaling(self):
"""
Test the scaling of scenes including unit conversion.
"""
# Test the scaling of scenes including unit conversion.

scene = g.get_mesh('cycloidal.3DXML')

md5 = scene.md5()
Expand Down Expand Up @@ -296,10 +295,9 @@ def test_empty(self):
assert len(n) == 0

def test_zipped(self):
"""
Make sure a zip file with multiple file types
is returned as a single scene.
"""
# Make sure a zip file with multiple file types
# is returned as a single scene.

# allow mixed 2D and 3D geometry
m = g.get_mesh('scenes.zip', mixed=True)

Expand Down Expand Up @@ -372,6 +370,26 @@ def test_material_group(self):
s = g.get_mesh('box.obj', group_material=False)
assert set(s.geometry.keys()) != {'Material', 'SecondMaterial'}

def test_export_concat(self):
# Scenes exported in mesh formats should be
# concatenating the meshes somewhere.
original = g.trimesh.creation.icosphere(
radius=0.123312)
original_hash = original.identifier_md5

scene = g.trimesh.Scene()
scene.add_geometry(original)

with g.TemporaryDirectory() as d:
for ext in ['stl', 'ply']:
file_name = g.os.path.join(d, 'mesh.' + ext)
scene.export(file_name)
loaded = g.trimesh.load(file_name)
assert g.np.isclose(loaded.volume,
original.volume)
# nothing should have changed
assert original.identifier_md5 == original_hash


if __name__ == '__main__':
g.trimesh.util.attach_to_log()
Expand Down
10 changes: 5 additions & 5 deletions tests/test_stl.py
Expand Up @@ -84,11 +84,11 @@ def test_empty(self):
assert not hasattr(e, 'vertices')

# create export
export = e.export(file_type='ply')
reconstructed = g.wrapload(export, file_type='ply')
# export should not contain geometry
assert isinstance(reconstructed, g.trimesh.Scene)
assert not hasattr(reconstructed, 'vertices')
try:
e.export(file_type='ply')
except BaseException:
return
raise ValueError("Shouldn't export empty scenes!")


if __name__ == '__main__':
Expand Down
7 changes: 5 additions & 2 deletions trimesh/exchange/export.py
Expand Up @@ -218,6 +218,9 @@ def export_scene(scene,
export : bytes
Only returned if file_obj is None
"""
if len(scene.geometry) == 0:
raise ValueError("Can't export empty scenes!")

# if we weren't passed a file type extract from file_obj
if file_type is None:
if util.is_string(file_obj):
Expand Down Expand Up @@ -245,9 +248,9 @@ def export_scene(scene,
from trimesh.path.exchange import svg_io
data = svg_io.export_svg(scene, **kwargs)
elif file_type == 'ply':
data = export_ply(scene)
data = export_ply(scene.dump(concatenate=True))
elif file_type == 'stl':
data = export_stl(scene)
data = export_stl(scene.dump(concatenate=True))
else:
raise ValueError(
'unsupported export format: {}'.format(file_type))
Expand Down
3 changes: 2 additions & 1 deletion trimesh/util.py
Expand Up @@ -1488,6 +1488,8 @@ def concatenate(a, b=None):
# if there is only one mesh just return the first
if len(meshes) == 1:
return meshes[0].copy()
elif len(meshes) == 0:
return []

# extract the trimesh type to avoid a circular import
# and assert that both inputs are Trimesh objects
Expand Down Expand Up @@ -1518,7 +1520,6 @@ def concatenate(a, b=None):
face_normals=face_normals,
visual=visual,
process=False)

return mesh


Expand Down
2 changes: 1 addition & 1 deletion trimesh/version.py
@@ -1 +1 @@
__version__ = '3.10.4'
__version__ = '3.10.5'