Spaces:
Runtime error
Runtime error
"""Meshes, conforming to the glTF 2.0 standards as specified in | |
https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#reference-mesh | |
Author: Matthew Matl | |
""" | |
import copy | |
import numpy as np | |
import trimesh | |
from .primitive import Primitive | |
from .constants import GLTF | |
from .material import MetallicRoughnessMaterial | |
class Mesh(object): | |
"""A set of primitives to be rendered. | |
Parameters | |
---------- | |
name : str | |
The user-defined name of this object. | |
primitives : list of :class:`Primitive` | |
The primitives associated with this mesh. | |
weights : (k,) float | |
Array of weights to be applied to the Morph Targets. | |
is_visible : bool | |
If False, the mesh will not be rendered. | |
""" | |
def __init__(self, primitives, name=None, weights=None, is_visible=True): | |
self.primitives = primitives | |
self.name = name | |
self.weights = weights | |
self.is_visible = is_visible | |
self._bounds = None | |
def name(self): | |
"""str : The user-defined name of this object. | |
""" | |
return self._name | |
def name(self, value): | |
if value is not None: | |
value = str(value) | |
self._name = value | |
def primitives(self): | |
"""list of :class:`Primitive` : The primitives associated | |
with this mesh. | |
""" | |
return self._primitives | |
def primitives(self, value): | |
self._primitives = value | |
def weights(self): | |
"""(k,) float : Weights to be applied to morph targets. | |
""" | |
return self._weights | |
def weights(self, value): | |
self._weights = value | |
def is_visible(self): | |
"""bool : Whether the mesh is visible. | |
""" | |
return self._is_visible | |
def is_visible(self, value): | |
self._is_visible = value | |
def bounds(self): | |
"""(2,3) float : The axis-aligned bounds of the mesh. | |
""" | |
if self._bounds is None: | |
bounds = np.array([[np.infty, np.infty, np.infty], | |
[-np.infty, -np.infty, -np.infty]]) | |
for p in self.primitives: | |
bounds[0] = np.minimum(bounds[0], p.bounds[0]) | |
bounds[1] = np.maximum(bounds[1], p.bounds[1]) | |
self._bounds = bounds | |
return self._bounds | |
def centroid(self): | |
"""(3,) float : The centroid of the mesh's axis-aligned bounding box | |
(AABB). | |
""" | |
return np.mean(self.bounds, axis=0) | |
def extents(self): | |
"""(3,) float : The lengths of the axes of the mesh's AABB. | |
""" | |
return np.diff(self.bounds, axis=0).reshape(-1) | |
def scale(self): | |
"""(3,) float : The length of the diagonal of the mesh's AABB. | |
""" | |
return np.linalg.norm(self.extents) | |
def is_transparent(self): | |
"""bool : If True, the mesh is partially-transparent. | |
""" | |
for p in self.primitives: | |
if p.is_transparent: | |
return True | |
return False | |
def from_points(points, colors=None, normals=None, | |
is_visible=True, poses=None): | |
"""Create a Mesh from a set of points. | |
Parameters | |
---------- | |
points : (n,3) float | |
The point positions. | |
colors : (n,3) or (n,4) float, optional | |
RGB or RGBA colors for each point. | |
normals : (n,3) float, optionals | |
The normal vectors for each point. | |
is_visible : bool | |
If False, the points will not be rendered. | |
poses : (x,4,4) | |
Array of 4x4 transformation matrices for instancing this object. | |
Returns | |
------- | |
mesh : :class:`Mesh` | |
The created mesh. | |
""" | |
primitive = Primitive( | |
positions=points, | |
normals=normals, | |
color_0=colors, | |
mode=GLTF.POINTS, | |
poses=poses | |
) | |
mesh = Mesh(primitives=[primitive], is_visible=is_visible) | |
return mesh | |
def from_trimesh(mesh, material=None, is_visible=True, | |
poses=None, wireframe=False, smooth=True): | |
"""Create a Mesh from a :class:`~trimesh.base.Trimesh`. | |
Parameters | |
---------- | |
mesh : :class:`~trimesh.base.Trimesh` or list of them | |
A triangular mesh or a list of meshes. | |
material : :class:`Material` | |
The material of the object. Overrides any mesh material. | |
If not specified and the mesh has no material, a default material | |
will be used. | |
is_visible : bool | |
If False, the mesh will not be rendered. | |
poses : (n,4,4) float | |
Array of 4x4 transformation matrices for instancing this object. | |
wireframe : bool | |
If `True`, the mesh will be rendered as a wireframe object | |
smooth : bool | |
If `True`, the mesh will be rendered with interpolated vertex | |
normals. Otherwise, the mesh edges will stay sharp. | |
Returns | |
------- | |
mesh : :class:`Mesh` | |
The created mesh. | |
""" | |
if isinstance(mesh, (list, tuple, set, np.ndarray)): | |
meshes = list(mesh) | |
elif isinstance(mesh, trimesh.Trimesh): | |
meshes = [mesh] | |
else: | |
raise TypeError('Expected a Trimesh or a list, got a {}' | |
.format(type(mesh))) | |
primitives = [] | |
for m in meshes: | |
positions = None | |
normals = None | |
indices = None | |
# Compute positions, normals, and indices | |
if smooth: | |
positions = m.vertices.copy() | |
normals = m.vertex_normals.copy() | |
indices = m.faces.copy() | |
else: | |
positions = m.vertices[m.faces].reshape((3 * len(m.faces), 3)) | |
normals = np.repeat(m.face_normals, 3, axis=0) | |
# Compute colors, texture coords, and material properties | |
color_0, texcoord_0, primitive_material = Mesh._get_trimesh_props(m, smooth=smooth, material=material) | |
# Override if material is given. | |
if material is not None: | |
#primitive_material = copy.copy(material) | |
primitive_material = copy.deepcopy(material) # TODO | |
if primitive_material is None: | |
# Replace material with default if needed | |
primitive_material = MetallicRoughnessMaterial( | |
alphaMode='BLEND', | |
baseColorFactor=[0.3, 0.3, 0.3, 1.0], | |
metallicFactor=0.2, | |
roughnessFactor=0.8 | |
) | |
primitive_material.wireframe = wireframe | |
# Create the primitive | |
primitives.append(Primitive( | |
positions=positions, | |
normals=normals, | |
texcoord_0=texcoord_0, | |
color_0=color_0, | |
indices=indices, | |
material=primitive_material, | |
mode=GLTF.TRIANGLES, | |
poses=poses | |
)) | |
return Mesh(primitives=primitives, is_visible=is_visible) | |
def _get_trimesh_props(mesh, smooth=False, material=None): | |
"""Gets the vertex colors, texture coordinates, and material properties | |
from a :class:`~trimesh.base.Trimesh`. | |
""" | |
colors = None | |
texcoords = None | |
# If the trimesh visual is undefined, return none for both | |
if not mesh.visual.defined: | |
return colors, texcoords, material | |
# Process vertex colors | |
if material is None: | |
if mesh.visual.kind == 'vertex': | |
vc = mesh.visual.vertex_colors.copy() | |
if smooth: | |
colors = vc | |
else: | |
colors = vc[mesh.faces].reshape( | |
(3 * len(mesh.faces), vc.shape[1]) | |
) | |
material = MetallicRoughnessMaterial( | |
alphaMode='BLEND', | |
baseColorFactor=[1.0, 1.0, 1.0, 1.0], | |
metallicFactor=0.2, | |
roughnessFactor=0.8 | |
) | |
# Process face colors | |
elif mesh.visual.kind == 'face': | |
if smooth: | |
raise ValueError('Cannot use face colors with a smooth mesh') | |
else: | |
colors = np.repeat(mesh.visual.face_colors, 3, axis=0) | |
material = MetallicRoughnessMaterial( | |
alphaMode='BLEND', | |
baseColorFactor=[1.0, 1.0, 1.0, 1.0], | |
metallicFactor=0.2, | |
roughnessFactor=0.8 | |
) | |
# Process texture colors | |
if mesh.visual.kind == 'texture': | |
# Configure UV coordinates | |
if mesh.visual.uv is not None and len(mesh.visual.uv) != 0: | |
uv = mesh.visual.uv.copy() | |
if smooth: | |
texcoords = uv | |
else: | |
texcoords = uv[mesh.faces].reshape( | |
(3 * len(mesh.faces), uv.shape[1]) | |
) | |
if material is None: | |
# Configure mesh material | |
mat = mesh.visual.material | |
if isinstance(mat, trimesh.visual.texture.PBRMaterial): | |
material = MetallicRoughnessMaterial( | |
normalTexture=mat.normalTexture, | |
occlusionTexture=mat.occlusionTexture, | |
emissiveTexture=mat.emissiveTexture, | |
emissiveFactor=mat.emissiveFactor, | |
alphaMode='BLEND', | |
baseColorFactor=mat.baseColorFactor, | |
baseColorTexture=mat.baseColorTexture, | |
metallicFactor=mat.metallicFactor, | |
roughnessFactor=mat.roughnessFactor, | |
metallicRoughnessTexture=mat.metallicRoughnessTexture, | |
doubleSided=mat.doubleSided, | |
alphaCutoff=mat.alphaCutoff | |
) | |
elif isinstance(mat, trimesh.visual.texture.SimpleMaterial): | |
glossiness = mat.kwargs.get('Ns', 1.0) | |
if isinstance(glossiness, list): | |
glossiness = float(glossiness[0]) | |
roughness = (2 / (glossiness + 2)) ** (1.0 / 4.0) | |
material = MetallicRoughnessMaterial( | |
alphaMode='BLEND', | |
roughnessFactor=roughness, | |
baseColorFactor=mat.diffuse, | |
baseColorTexture=mat.image, | |
) | |
elif isinstance(mat, MetallicRoughnessMaterial): | |
material = mat | |
return colors, texcoords, material | |