Skip to content

Commit

Permalink
make source a property
Browse files Browse the repository at this point in the history
  • Loading branch information
mikedh committed Jan 20, 2025
1 parent e588abd commit 092e017
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 147 deletions.
3 changes: 2 additions & 1 deletion tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ def test_dict(self):
assert mesh.visual.kind == "vertex"

as_dict = mesh.to_dict()
back = g.trimesh.Trimesh(**as_dict) # NOQA
back = g.trimesh.Trimesh(**as_dict, process=False)
assert g.np.allclose(back.vertices, mesh.vertices)

def test_scene(self):
# get a multi- mesh scene with a transform tree
Expand Down
2 changes: 1 addition & 1 deletion tests/test_scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def test_scene(self):
# then make sure json can serialize it
e = g.json.dumps(s.export(file_type=export_format))
# reconstitute the dict into a scene
r = g.trimesh.load(g.json.loads(e), file_type="dict")
r = g.trimesh.load(g.json.loads(e))

# make sure the extents are similar before and after
assert g.np.allclose(g.np.prod(s.extents), g.np.prod(r.extents))
Expand Down
6 changes: 1 addition & 5 deletions trimesh/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from .constants import log, tol
from .exceptions import ExceptionWrapper
from .exchange.export import export_mesh
from .parent import Geometry3D, LoadSource
from .parent import Geometry3D
from .scene import Scene
from .triangles import MassProperties
from .typed import (
Expand Down Expand Up @@ -99,7 +99,6 @@ def __init__(
use_embree: bool = True,
initial_cache: Optional[Dict[str, ndarray]] = None,
visual: Optional[Union[ColorVisuals, TextureVisuals]] = None,
source: Optional[LoadSource] = None,
**kwargs,
) -> None:
"""
Expand Down Expand Up @@ -203,9 +202,6 @@ def __init__(
elif metadata is not None:
raise ValueError(f"metadata should be a dict or None, got {metadata!s}")

# where was this loaded from
self.source = source

# store per-face and per-vertex attributes which will
# be updated when an update_faces call is made
self.face_attributes = {}
Expand Down
6 changes: 4 additions & 2 deletions trimesh/exchange/cascade.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import tempfile

from ..exceptions import ExceptionWrapper
from ..typed import BinaryIO, Dict, Number, Optional

# used as an intermediate format
Expand Down Expand Up @@ -68,5 +69,6 @@ def load_step(
import cascadio

_cascade_loaders = {"stp": load_step, "step": load_step}
except BaseException:
_cascade_loaders = {}
except BaseException as E:
wrapper = ExceptionWrapper(E)
_cascade_loaders = {"stp": wrapper, "step": wrapper}
77 changes: 32 additions & 45 deletions trimesh/exchange/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from ..parent import Geometry, LoadSource
from ..points import PointCloud
from ..scene.scene import Scene, append_scenes
from ..typed import Dict, Loadable, Optional
from ..typed import Dict, Loadable, Optional, Set
from ..util import log
from . import misc
from .binvox import _binvox_loaders
Expand Down Expand Up @@ -37,29 +37,30 @@ def path_formats() -> set:
return set()


def mesh_formats() -> set:
def mesh_formats() -> Set[str]:
"""
Get a list of mesh formats available to load.
Returns
-----------
loaders : list
Extensions of available mesh loaders,
i.e. 'stl', 'ply', etc.
loaders
Extensions of available mesh loaders
i.e. `{'stl', 'ply'}`
"""
# filter out exceptionmodule loaders
return {k for k, v in mesh_loaders.items() if not isinstance(v, ExceptionWrapper)}


def available_formats() -> set:
def available_formats() -> Set[str]:
"""
Get a list of all available loaders
Returns
-----------
loaders : list
Extensions of available loaders
i.e. 'stl', 'ply', 'dxf', etc.
loaders
Extensions of all available loaders
i.e. `{'stl', 'ply', 'dxf'}`
"""
loaders = mesh_formats()
loaders.update(path_formats())
Expand All @@ -77,6 +78,8 @@ def load(
**kwargs,
) -> Geometry:
"""
THIS FUNCTION IS DEPRECATED but there are no current plans for it to be removed.
For new code the typed load functions `trimesh.load_scene` or `trimesh.load_mesh`
are recommended over `trimesh.load` which is a backwards-compatibility wrapper
that mimics the behavior of the old function and can return any geometry type.
Expand Down Expand Up @@ -126,8 +129,7 @@ def load(
return loaded

###########################################
# we are matching deprecated behavior here!
# matching old behavior you should probably use `load_scene`
# we are matching old, deprecated behavior here!
kind = loaded.source.file_type
always_scene = {"glb", "gltf", "zip", "3dxml", "tar.gz"}

Expand Down Expand Up @@ -195,23 +197,19 @@ def load_scene(
)

try:
if arg.file_type in path_formats():
# path formats get loaded with path loader
if isinstance(file_obj, dict):
# we've been passed a dictionary so treat them as keyword arguments
loaded = _load_kwargs(file_obj)
elif arg.file_type in path_formats():
# use path loader
loaded = load_path(
file_obj=arg.file_obj,
file_type=arg.file_type,
metadata=metadata,
**kwargs,
)
elif arg.file_type in ["svg", "dxf"]:
# call the dummy function to raise the import error
# this prevents the exception from being super opaque
load_path()
elif isinstance(file_obj, dict):
loaded = _load_kwargs(file_obj)
elif arg.file_type in mesh_loaders:
# mesh loaders use mesh loader

# use mesh loader
loaded = _load_kwargs(
mesh_loaders[arg.file_type](
file_obj=arg.file_obj,
Expand Down Expand Up @@ -241,12 +239,14 @@ def load_scene(
arg.file_obj.close()

if not isinstance(loaded, Scene):
# file name may be used for nodes
loaded._source = arg
loaded = Scene(loaded)

# tack that sumbitch on
loaded.source = arg
# add on the loading information
loaded._source = arg
for g in loaded.geometry.values():
g.source = arg
g._source = arg

return loaded

Expand Down Expand Up @@ -462,10 +462,12 @@ def handle_export():
Handle an exported mesh.
"""
data, file_type = kwargs["data"], kwargs["file_type"]
if not isinstance(data, dict):
data = util.wrap_as_stream(data)
k = mesh_loaders[file_type](data, file_type=file_type)
return Trimesh(**k)
if isinstance(data, dict):
return _load_kwargs(data)
elif file_type in mesh_loaders:
return Trimesh(**mesh_loaders[file_type](data, file_type=file_type))

raise NotImplementedError(f"`{file_type}` is not supported")

def handle_path():
from ..path import Path2D, Path3D
Expand Down Expand Up @@ -617,12 +619,6 @@ def _parse_file_args(
raise ValueError(f"string is not a file: {file_obj}")
else:
file_obj = None
elif isinstance(file_obj, dict):
file_obj = util.wrap_as_stream(json.dumps(file_obj))
file_type = "dict"

if file_type is None:
file_type = file_obj.__class__.__name__

if isinstance(file_type, str) and "." in file_type:
# if someone has passed the whole filename as the file_type
Expand All @@ -633,17 +629,8 @@ def _parse_file_args(
resolver = resolvers.FilePathResolver(file_type)

# all our stored extensions reference in lower case
file_type = file_type.lower()

# if user passed in a metadata dict add it
# if len(kwargs.get("metadata", {})) > 0:
# metadata = kwargs["metadata"]
# else:
# metadata["file_type"] = file_type
# if file_path is not None:
# metadata.update(
# {"file_path": file_path, "file_name": os.path.basename(file_path)}
# )
if file_type is not None:
file_type = file_type.lower()

# if we still have no resolver try using file_obj name
if (
Expand Down
39 changes: 19 additions & 20 deletions trimesh/exchange/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from tempfile import NamedTemporaryFile

from .. import util
from ..exceptions import ExceptionWrapper


def load_dict(file_obj, **kwargs):
Expand Down Expand Up @@ -75,7 +76,7 @@ def load_dict(file_obj, **kwargs):
return loaded


def load_meshio(file_obj, file_type=None, **kwargs):
def load_meshio(file_obj, file_type: str, **kwargs):
"""
Load a meshio-supported file into the kwargs for a Trimesh
constructor.
Expand All @@ -94,30 +95,26 @@ def load_meshio(file_obj, file_type=None, **kwargs):
kwargs for Trimesh constructor
"""
# trimesh "file types" are really filename extensions
# meshio may return multiple answers for each file extension
file_formats = meshio.extension_to_filetypes["." + file_type]
# load_meshio gets passed and io.BufferedReader
# not all readers can cope with that
# e.g., the ones that use h5m underneath
# in that case use the associated file name instead

mesh = None
exceptions = []

# meshio appears to only support loading by file name so use a tempfile
with NamedTemporaryFile(suffix=f".{file_type}") as temp:
temp.write(file_obj.read())
temp.flush()
# try the loaders in order
for file_format in file_formats:
try:
mesh = meshio.read(temp.name, file_format=file_format)
break
except BaseException as E:
exceptions.append(str(E))

if file_type in file_formats:
# if we've been passed the file type and don't have to guess
mesh = meshio.read(temp.name, file_format=file_type)
else:
# try the loaders in order
for file_format in file_formats:
try:
mesh = meshio.read(temp.name, file_format=file_format)
break
except BaseException:
util.log.debug("failed to load", exc_info=True)
if mesh is None:
raise ValueError("Failed to load file!")
if mesh is None:
raise ValueError("Failed to load file:" + "\n".join(exceptions))

# save file_obj as kwargs for a trimesh.Trimesh
result = {}
Expand All @@ -136,6 +133,8 @@ def load_meshio(file_obj, file_type=None, **kwargs):


_misc_loaders = {"dict": load_dict, "dict64": load_dict}
_misc_loaders = {}


try:
import meshio
Expand All @@ -150,5 +149,5 @@ def load_meshio(file_obj, file_type=None, **kwargs):
import openctm

_misc_loaders["ctm"] = openctm.load_ctm
except BaseException:
pass
except BaseException as E:
_misc_loaders["ctm"] = ExceptionWrapper(E)
30 changes: 24 additions & 6 deletions trimesh/parent.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@ class LoadSource:
"""

# a file-like object that can be accessed
file_obj: Optional[Stream]
file_obj: Optional[Stream] = None

# a cleaned file type string, i.e. "stl"
file_type: str
file_type: Optional[str] = None

# if this was originally loaded from a file path
# save it here so we can check it later.
file_path: Optional[str]
file_path: Optional[str] = None

# did we open `file_obj` ourselves?
was_opened: bool
was_opened: bool = None

# a resolver for loading assets next to the file
resolver: Optional[ResolverLike]
resolver: Optional[ResolverLike] = None

@property
def file_name(self) -> Optional[str]:
Expand Down Expand Up @@ -68,7 +68,25 @@ class Geometry(ABC):

# geometry should have a dict to store loose metadata
metadata: Dict
source: Optional[LoadSource] = None

@property
def source(self) -> LoadSource:
"""
Where and what was this current geometry loaded from?
Returns
--------
source
If loaded from a file, has the path, type, etc.
"""
# this should have been tacked on by the loader
# but we want to *always* be able to access
# a value like `mesh.source.file_type` so add a default
current = getattr(self, "_source", None):
if current is not None:
return current
self._source = LoadSource()
return self._source

@property
@abc.abstractmethod
Expand Down
Loading

0 comments on commit 092e017

Please sign in to comment.