Skip to content

Commit

Permalink
Merge pull request #1523 from mikedh/fix/stl
Browse files Browse the repository at this point in the history
Fix: Scene Exports
  • Loading branch information
mikedh committed Mar 17, 2022
2 parents 1912a93 + eef69eb commit d3bdfb9
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 41 deletions.
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'

0 comments on commit d3bdfb9

Please sign in to comment.