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

224 lines
5.6 KiB
Python

"""
resolvers.py
---------------
Provides a common interface to load assets referenced by name
like MTL files, texture images, etc. Assets can be from ZIP
archives, web assets, or a local file path.
"""
import os
from . import util
# URL parsing for remote resources via WebResolver
try:
# Python 3
from urllib.parse import urlparse, urljoin
except ImportError:
# Python 2
from urlparse import urlparse, urljoin
class Resolver(object):
"""
The base class for resolvers.
"""
def __init__(self, *args, **kwargs):
raise NotImplementedError('Use a resolver subclass!')
def write(self, name, data):
raise NotImplementedError('write not implemented for {}'.format(
self.__class__.__name__))
def __getitem__(self, key):
return self.get(key)
class FilePathResolver(Resolver):
"""
Resolve files from a source path on the file system.
"""
def __init__(self, source):
"""
Resolve files based on a source path.
Parameters
------------
source : str
File path where mesh was loaded from
"""
# remove everything other than absolute path
clean = os.path.expanduser(os.path.abspath(str(source)))
if os.path.isdir(clean):
self.parent = clean
# get the parent directory of the
elif os.path.isfile(clean):
split = os.path.split(clean)
self.parent = split[0]
else:
raise ValueError('path not a file or directory!')
def get(self, name):
"""
Get an asset.
Parameters
-------------
name : str
Name of the asset
Returns
------------
data : bytes
Loaded data from asset
"""
# load the file by path name
with open(os.path.join(self.parent, name.strip()), 'rb') as f:
data = f.read()
return data
def write(self, name, data):
"""
Write an asset to a file path.
Parameters
-----------
name : str
Name of the file to write
data : str or bytes
Data to write to the file
"""
# write files to path name
with open(os.path.join(self.parent, name.strip()), 'wb') as f:
# handle encodings correctly for str/bytes
util.write_encoded(file_obj=f, stuff=data)
class ZipResolver(Resolver):
"""
Resolve files inside a ZIP archive.
"""
def __init__(self, archive):
"""
Resolve files inside a ZIP archive as loaded by
trimesh.util.decompress
Parameters
-------------
archive : dict
Contains resources as file object
"""
self.archive = archive
def get(self, name):
"""
Get an asset from the ZIP archive.
Parameters
-------------
name : str
Name of the asset
Returns
-------------
data : bytes
Loaded data from asset
"""
# not much we can do with that
if name is None:
return
# if name isn't in archive try some similar values
if name not in self.archive:
if hasattr(name, 'decode'):
name = name.decode('utf-8')
# try with cleared whitespace, split paths
for option in [name,
name.lstrip('./'),
name.strip(),
name.split('/')[-1]]:
if option in self.archive:
name = option
break
# get the stored data
obj = self.archive[name]
# if the dict is storing data as bytes just return
if isinstance(obj, (bytes, str)):
return obj
# otherwise get it as a file object
# read file object from beginning
obj.seek(0)
# data is stored as a file object
data = obj.read()
obj.seek(0)
return data
class WebResolver(Resolver):
"""
Resolve assets from a remote URL.
"""
def __init__(self, url):
"""
Resolve assets from a base URL.
Parameters
--------------
url : str
Location where a mesh was stored or
directory where mesh was stored
"""
if hasattr(url, 'decode'):
url = url.decode('utf-8')
# parse string into namedtuple
parsed = urlparse(url)
# we want a base url where the mesh was located
path = parsed.path
if path[-1] != '/':
# clip off last item
path = '/'.join(path.split('/')[:-1]) + '/'
# store the base url
self.base_url = '{scheme}://{netloc}/{path}'.format(
scheme=parsed.scheme,
netloc=parsed.netloc,
path=path)
def get(self, name):
"""
Get a resource from the remote site.
Parameters
-------------
name : str
Asset name, i.e. 'quadknot.obj.mtl'
"""
# do import here to keep soft dependency
import requests
# remove leading and trailing whitespace
name = name.strip()
# fetch the data from the remote url
response = requests.get(urljoin(
self.base_url, name))
if response.status_code != 200:
# try to strip off filesystem crap
response = requests.get(urljoin(
self.base_url,
name.lstrip('./')))
if response.status_code == '404':
raise ValueError(response.content)
# return the bytes of the response
return response.content