""" 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