hub/venv/lib/python3.7/site-packages/trimesh/exchange/assimp.py

281 lines
7.4 KiB
Python

import copy
import tempfile
import collections
import numpy as np
from .. import util
def load_pyassimp(file_obj,
file_type=None,
resolver=None,
**kwargs):
"""
Use the pyassimp library to load a mesh from a file object
and type or file name if file_obj is a string
Parameters
---------
file_obj: str, or file object
File path or object containing mesh data
file_type : str
File extension, aka 'stl'
resolver : trimesh.visual.resolvers.Resolver
Used to load referenced data (like texture files)
kwargs : dict
Passed through to mesh constructor
Returns
---------
scene : trimesh.Scene
Native trimesh copy of assimp scene
"""
def LP_to_TM(lp):
# try to get the vertex colors attribute
colors = (np.reshape(lp.colors, (-1, 4))
[:, :3] * 255).round().astype(np.uint8)
# If no vertex colors, try to extract them from the material
if len(colors) == 0:
if 'diffuse' in lp.material.properties.keys():
colors = np.array(lp.material.properties['diffuse'])
# pass kwargs through to mesh constructor
mesh_kwargs = copy.deepcopy(kwargs)
# add data from the LP_Mesh
mesh_kwargs.update({'vertices': lp.vertices,
'vertex_normals': lp.normals,
'vertex_colors': colors,
'faces': lp.faces})
return mesh_kwargs
# did we open the file inside this function
opened = False
# not a file object
if not hasattr(file_obj, 'read'):
# if there is no read attribute
# we assume we've been passed a file name
file_type = (str(file_obj).split('.')[-1]).lower()
file_obj = open(file_obj, 'rb')
opened = True
# we need files to be bytes
elif not hasattr(file_obj, 'mode') or file_obj.mode != 'rb':
# assimp will crash on anything that isn't binary
# so if we have a text mode file or anything else
# grab the data, encode as bytes, and then use stream
data = file_obj.read()
if hasattr(data, 'encode'):
data = data.encode('utf-8')
file_obj = util.wrap_as_stream(data)
# load the scene using pyassimp
scene = pyassimp.load(file_obj,
file_type=file_type)
# save a record of mesh names used so we
# don't have to do queries on mesh_id.values()
mesh_names = set()
# save a mapping for {id(mesh) : name}
mesh_id = {}
# save results as {name : Trimesh}
meshes = {}
# loop through scene LPMesh objects
for m in scene.meshes:
# skip meshes without tri/quad faces
if m.faces.shape[1] not in [3, 4]:
continue
# if this mesh has the name of an existing mesh
if m.name in mesh_names:
# make it the name plus the unique ID of the object
name = m.name + str(id(m))
else:
# otherwise just use the name it calls itself by
name = m.name
# save the name to mark as consumed
mesh_names.add(name)
# save the id:name mapping
mesh_id[id(m)] = name
# convert the mesh to a trimesh object
meshes[name] = LP_to_TM(m)
# now go through and collect the transforms from the scene
# we are going to save them as a list of dict kwargs
transforms = []
# nodes as (parent, node) tuples
# use deque so we can pop from both ends
queue = collections.deque(
[('world', n) for
n in scene.rootnode.children])
# consume the queue
while len(queue) > 0:
# parent name, node object
parent, node = queue.pop()
# assimp uses weirdly duplicate node names
# object ID's are actually unique and consistent
node_name = id(node)
transforms.append({'frame_from': parent,
'frame_to': node_name,
'matrix': node.transformation})
# loop through meshes this node uses
# note that they are the SAME objects as converted
# above so we can find their reference using id()
for m in node.meshes:
if id(m) not in mesh_id:
continue
# create kwargs for graph.update
edge = {'frame_from': node_name,
'frame_to': str(id(m)) + str(node_name),
'matrix': np.eye(4),
'geometry': mesh_id[id(m)]}
transforms.append(edge)
# add any children to the queue to be visited
for child in node.children:
queue.appendleft((node_name, child))
# release the loaded scene
pyassimp.release(scene)
# if we opened the file in this function close it
if opened:
file_obj.close()
# create kwargs for trimesh.exchange.load.load_kwargs
result = {'class': 'Scene',
'geometry': meshes,
'graph': transforms,
'base_frame': 'world'}
return result
def load_cyassimp(file_obj,
file_type=None,
resolver=None,
**kwargs):
"""
Load a file using the cyassimp bindings.
The easiest way to install these is with conda:
conda install -c menpo/label/master cyassimp
Parameters
---------
file_obj: str, or file object
File path or object containing mesh data
file_type : str
File extension, aka 'stl'
resolver : trimesh.visual.resolvers.Resolver
Used to load referenced data (like texture files)
kwargs : dict
Passed through to mesh constructor
Returns
---------
meshes : (n,) list of dict
Contain kwargs for Trimesh constructor
"""
if hasattr(file_obj, 'read'):
# if it has a read attribute it is probably a file object
with tempfile.NamedTemporaryFile(
suffix=str(file_type)) as file_temp:
file_temp.write(file_obj.read())
# file name should be bytes
scene = cyassimp.AIImporter(
file_temp.name.encode('utf-8'))
scene.build_scene()
else:
scene = cyassimp.AIImporter(file_obj.encode('utf-8'))
scene.build_scene()
meshes = []
for m in scene.meshes:
mesh_kwargs = kwargs.copy()
mesh_kwargs.update({'vertices': m.points,
'faces': m.trilist})
meshes.append(mesh_kwargs)
if len(meshes) == 1:
return meshes[0]
return meshes
_assimp_formats = [
'fbx',
'dae',
'gltf',
'glb',
'blend',
'3ds',
'ase',
'obj',
'ifc',
'xgl',
'zgl',
'ply',
'lwo',
'lws',
'lxo',
'stl',
'x',
'ac',
'ms3d',
'cob',
'scn',
'bvh',
'csm',
'xml',
'irrmesh',
'irr',
'mdl',
'md2',
'md3',
'pk3',
'mdc',
'md5',
'smd',
'vta',
'ogex',
'3d',
'b3d',
'q3d',
'.q3s',
'nff',
'off',
'raw',
'ter',
'3dgs',
'mdl',
'hmp',
'ndo']
_assimp_loaders = {}
# try importing both assimp bindings but prefer cyassimp
loader = None
try:
import pyassimp
loader = load_pyassimp
except BaseException:
pass
try:
import cyassimp
loader = load_cyassimp
except BaseException:
pass
if loader:
_assimp_loaders.update(zip(_assimp_formats,
[loader] * len(_assimp_formats)))