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

obj file loaded as Scene object instead of Trimesh object #507

Closed
SilvioJin opened this issue Jul 22, 2019 · 14 comments
Closed

obj file loaded as Scene object instead of Trimesh object #507

SilvioJin opened this issue Jul 22, 2019 · 14 comments

Comments

@SilvioJin
Copy link

SilvioJin commented Jul 22, 2019

Hi,

I'm trying to load a mesh from an .obj file (from ShapeNet dataset) using Trimesh, and then use the repair. fix_winding(mesh) function.

But when I load the mesh, via trimesh.load('/path/to/file.obj') or trimesh.load_mesh('/path/to/file.obj'), the object class returned is Scene, which is incompatible with repair. fix_winding(mesh), only Trimesh object are accepted.

How can I force it to load and return a Trimesh object or parse the Scene object to Trimesh object?

@Mondobot
Copy link

Mondobot commented Jul 24, 2019

I have the same issue. One thing bothers me. How can a load function return a trimesh object or a Scene? When does it return one and when the other? It's really ambiguous. Also, where is the documentation for older versions?

@jackd
Copy link
Contributor

jackd commented Jul 25, 2019

My understanding is that some .obj files specify different sub-meshes. trimesh attempts to recreate this as faithfully as possible. You can always concatenate the meshes together, though not all aspect have concatenation which is supported. If all you care about is the vertices/faces, you can use something like the following:

import trimesh


def as_mesh(scene_or_mesh):
    """
    Convert a possible scene to a mesh.

    If conversion occurs, the returned mesh has only vertex and face data.
    """
    if isinstance(scene_or_mesh, trimesh.Scene):
        if len(scene_or_mesh.geometry) == 0:
            mesh = None  # empty scene
        else:
            # we lose texture information here
            mesh = trimesh.util.concatenate(
                tuple(trimesh.Trimesh(vertices=g.vertices, faces=g.faces)
                    for g in scene_or_mesh.geometry.values()))
    else:
        assert(isinstance(mesh, trimesh.Trimesh))
        mesh = scene_or_mesh
    return mesh

@Mondobot
Copy link

Thank you for the answer! For now I just locked the version of trimesh to an older one, but when we'll update I'll use your solution.

@SilvioJin
Copy link
Author

Same! Trying a previous version at the moment.
@jackd thanks for your answer!

@mikedh
Copy link
Owner

mikedh commented Jul 26, 2019

Yeah, this was one of the larger API changes in #467, and is an unfortunate consequence of better texture support: meshes with multiple materials get put into different mesh objects. The solution I would recommend (after I fix a bug in texture concatenation, sigh) is:

In [1]: import trimesh

In [2]: m = trimesh.load('bigmodel/airplane/models/model_normalized.obj')

In [3]: m
Out[3]: <trimesh.scene.scene.Scene at 0x7f20b27bf2e8>

In [4]: m.dump?
Signature: m.dump()
Docstring:
Append all meshes in scene to a list of meshes.

Returns
----------
dumped: (n,) list, of Trimesh objects transformed to their
                   location the scene.graph
File:      /dropbox/drop/Dropbox/robotics/trimesh/trimesh/scene/scene.py
Type:      method

In [5]: dump = m.dump()

In [6]: len(dump)
Out[6]: 450

In [7]: dump[0]
Out[7]: <trimesh.base.Trimesh at 0x7f20ea3fc4e0>

# should be a single mesh object
In [8]: concat = dump.sum()

If we had a nice "combine textured meshes" method, we could possibly return the default behavior to return a single mesh, or a have a load kwarg force_mesh which always tried to concatenate multiple meshes in a scene.

@mikedh
Copy link
Owner

mikedh commented Jul 27, 2019

I just pushed a version (3.1.0) that hopefully fixes this in most cases: OBJ models with 0-1 materials will return a single mesh again.

@aluo-x
Copy link

aluo-x commented Aug 13, 2020

Got frustrated w/ PyMesh not building on the cluster, Pytorch3d's mesh loader was too slow, so here's a short snippet using trimesh:

import trimesh
from trimesh.scene.scene import Scene
import logging
logger = logging.getLogger("pywavefront")
logger.setLevel(logging.ERROR)

def custom_load_obj(filename_obj):
    obj_info = trimesh.load(filename_obj, file_type='obj', process=False)
    if type(obj_info) is Scene:
        geo_keys = list(obj_info.geometry.keys())
        total_vert = []
        total_faces = []
        for gk in geo_keys:
            cur_geo = obj_info.geometry[gk]
            cur_vert = cur_geo.vertices.tolist()
            cur_face = np.array(cur_geo.faces.tolist())+len(total_vert)
            total_vert += cur_vert
            total_faces += cur_face.tolist()
        return np.array(total_vert).astype("float32"), np.array(total_faces).astype("int32")
    else:
        return np.array(obj_info.vertices).astype("float32"), np.array(obj_info.faces).astype("int32")

@mikedh
Copy link
Owner

mikedh commented Aug 13, 2020

Nice! There are some convenience methods we added since this issue was closed that also might be helpful:

In [6]: m = trimesh.load('bigmodel/bigben/model.obj')

In [7]: m
Out[7]: <trimesh.Scene(len(geometry)=88)>

In [8]: m = trimesh.load('bigmodel/bigben/model.obj', force='mesh')
concatenating texture: may result in visual artifacts

In [9]: m
Out[9]: <trimesh.Trimesh(vertices.shape=(848, 3), faces.shape=(448, 3))>

@aluo-x
Copy link

aluo-x commented Aug 13, 2020

That's incredibly useful. Since I only need the geometry, I wonder if there is a way to bypass the texture/material concat step.

@mikedh
Copy link
Owner

mikedh commented Aug 13, 2020

Yeah the material concatenation is pretty expensive, we could add Yet Another Keyword Argument™️ to the OBJ loader haha. It still complains about concatenation because it's still loading UV coordinates, but speeds it up by an order of magnitude:

In [7]: %timeit m = trimesh.load('bigmodel/bigben/model.obj', force='mesh', skip_texture=True)
concatenating texture: may result in visual artifacts

123 ms ± 1.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [8]: %timeit m = trimesh.load('bigmodel/bigben/model.obj', force='mesh', skip_texture=False)
concatenating texture: may result in visual artifacts

1.9 s ± 27.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Happy to include in the next release if that's useful.

@aluo-x
Copy link

aluo-x commented Aug 13, 2020

I would be super grateful.

@aluo-x
Copy link

aluo-x commented Sep 19, 2020

Also would appreciate a merge of the texture skipping.

@mikedh
Copy link
Owner

mikedh commented Sep 19, 2020

Oh that was merged, I just renamed it to skip_materials from skip_texture before merging to more accurately reflect what it does:

skip_materials=False,
**kwargs):
"""
Load a Wavefront OBJ file into kwargs for a trimesh.Scene
object.
Parameters
--------------
file_obj : file like object
Contains OBJ data
resolver : trimesh.visual.resolvers.Resolver
Allow assets such as referenced textures and
material files to be loaded
split_object : bool
Split meshes at each `o` declared in file
group_material : bool
Group faces that share the same material
into the same mesh.
skip_materials : bool
Don't load any materials.

@aluo-x
Copy link

aluo-x commented Sep 19, 2020

Nice, there's a lot of flags in there.
Maintaining such a high profile project must be a lot of work! Thanks for the effort.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants