trimesh icon indicating copy to clipboard operation
trimesh copied to clipboard

How to use per-face uv-map to texture a mesh?

Open trainlock opened this issue 3 years ago • 1 comments

Given a trimesh.Trimesh, a per-face uv-map coordinates (not per-vertex), and a texture, I could not find a way to texture the mesh using only trimesh. In trimesh, AFIK, I can only texture a mesh using per-vertex uv-map (not per-face).

I could use the open3d view of the trimesh object to do such an operation. However, the changes are not reflected in the original object, and I am required to write the mesh down to a file and reload it with trimesh in order to get a textured mesh.

import trimesh
import open3d
import numpy as np

from pathlib import Path
from skimage import data
from sklearn.preprocessing import minmax_scale

def _get_arbitrary_uv_map_faces(mesh):
    _arbitrary_uv_map_vertex = minmax_scale(mesh.vertices.view(np.ndarray)[:, :2])
    return np.array([
        _arbitrary_uv_map_vertex[face].reshape(-1)
        for face in mesh.faces.view(np.ndarray)
    ])

def get_textured_mesh(mesh: trimesh, uv_map_faces, texture: np.ndarray):
    # Save texture through open3d and load it again in trimesh
    def _save_load_workaround_to_get_textured_mesh(open3dmesh):
        from tempfile import mkdtemp

        _tmp_fname = Path(mkdtemp()) / "tmp.obj"
        open3d.io.write_triangle_mesh(str(_tmp_fname), open3dmesh)
        return trimesh.load(_tmp_fname)

    # Set uv-coordinates on the mesh and assign texture
    mesh.as_open3d.triangle_uvs = open3d.utility.Vector2dVector(uv_map_faces.reshape(-1, 2))
    mesh.as_open3d.textures = [open3d.geometry.Image(np.asarray(texture)).flip_vertical()]

    # return mesh  # I would expect this to return a textured mesh
    return _save_load_workaround_to_get_textured_mesh(mesh.as_open3d)

def main():
    def transformation(uv_map_faces):  # For illustration purposes, transformation that only works from faces to faces
        return uv_map_faces

    mesh = trimesh.creation.uv_sphere()
    uv_faces = _get_arbitrary_uv_map_faces(mesh)

    scene = get_textured_mesh(
        mesh=mesh,
        uv_map_faces=transformation(uv_faces),
        texture=data.checkerboard(),
    )
    scene.show()

if __name__ == "__main__":
    main()

Is there a way to create a trimesh object out of an open3d object? If not, is there a way to create a trimesh object in memory, without having to write it to a file?

Possibly related issues:

  • https://github.com/mikedh/trimesh/issues/1289
  • https://github.com/mikedh/trimesh/issues/1117

Expected result: mwe_sphere_texture

Outputted result (without workaround): mwe_sphere_no_texture

trainlock avatar Jul 19 '21 13:07 trainlock

I've ran into the same problem and I didn't find a solution other than duplicating the vertices to assign different UV values. You must also pass process=False, maintain_order=True when instantiating the Trimesh.

mcejp avatar Apr 10 '22 17:04 mcejp