Skip to content

Commit

Permalink
Merge pull request #2083 from mikedh/feat/loadable
Browse files Browse the repository at this point in the history
Release: Loadable Type, Grouping, Skip Materials
  • Loading branch information
mikedh authored Dec 19, 2023
2 parents 4c4a9a0 + e1b0ad8 commit 31ea046
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 84 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ requires = ["setuptools >= 61.0", "wheel"]
[project]
name = "trimesh"
requires-python = ">=3.7"
version = "4.0.5"
version = "4.0.6"
authors = [{name = "Michael Dawson-Haggerty", email = "mikedh@kerfed.com"}]
license = {file = "LICENSE.md"}
description = "Import, export, process, analyze and view triangular meshes."
Expand Down
18 changes: 18 additions & 0 deletions tests/test_gltf.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,24 @@ def test_buffer_dedupe(self):
a = g.json.loads(scene.export(file_type="gltf")["model.gltf"].decode("utf-8"))
assert len(a["buffers"]) <= 3

def test_skip_materials(self):
# load textured PLY
mesh = g.get_mesh("fuze.ply")
g.check_fuze(mesh)

# load as GLB
export = mesh.export(file_type="glb", unitize_normals=True)
validate_glb(export)
mesh_glb = g.trimesh.load(
g.trimesh.util.wrap_as_stream(export),
file_type="glb",
force="mesh",
skip_materials=True,
)

# visuals should not be present
assert not mesh_glb.visual.defined

def test_tex_export(self):
# load textured PLY
mesh = g.get_mesh("fuze.ply")
Expand Down
10 changes: 8 additions & 2 deletions tests/test_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,15 @@ def test_traversals(self):
)

# make a set from edges included in the traversal
inc_set = set(g.trimesh.grouping.hashable_rows(g.np.sort(inc, axis=1)))
inc_set = {
i.tobytes()
for i in g.trimesh.grouping.hashable_rows(g.np.sort(inc, axis=1))
}
# make a set of the source edges we were supposed to include
edge_set = set(g.trimesh.grouping.hashable_rows(g.np.sort(edges, axis=1)))
edge_set = {
i.tobytes()
for i in g.trimesh.grouping.hashable_rows(g.np.sort(edges, axis=1))
}

# we should have exactly the same edges
# after the filled traversal as we started with
Expand Down
17 changes: 7 additions & 10 deletions tests/test_inertia.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ def test_inertia(self):
assert g.np.abs(g.np.dot(t1, t1.T) - g.np.eye(4)).max() < 1e-10

c = g.trimesh.primitives.Cylinder(
height=10, radius=1, sections=720, transform=t0 # number of slices
height=10,
radius=1,
sections=720,
transform=t0, # number of slices
)
c0m = c.moment_inertia.copy()
c0 = g.trimesh.inertia.cylinder_inertia(
Expand Down Expand Up @@ -343,9 +346,7 @@ def parallel_axis_theorem(inertia, mass, a1, a2, a3):
# CHECK FRAME 0
# analytical calculations of inertia tensor by hand
inertia0 = (
0.083333333333
* mass
* g.np.diag([h**2 + b**2, h**2 + d**2, b**2 + d**2])
0.083333333333 * mass * g.np.diag([h**2 + b**2, h**2 + d**2, b**2 + d**2])
)
a1 = -0.5 * d
a2 = -0.5 * b
Expand All @@ -358,9 +359,7 @@ def parallel_axis_theorem(inertia, mass, a1, a2, a3):
# CHECK FRAME 1
# analytical calculations of inertia tensor by hand
inertia1 = (
0.083333333333
* mass
* g.np.diag([h**2 + d**2, h**2 + b**2, b**2 + d**2])
0.083333333333 * mass * g.np.diag([h**2 + d**2, h**2 + b**2, b**2 + d**2])
)
a1 = -0.5 * b
a2 = 0.5 * d
Expand All @@ -376,9 +375,7 @@ def parallel_axis_theorem(inertia, mass, a1, a2, a3):
# CHECK FRAME 2
# analytical calculations of inertia tensor by hand
inertia2 = (
0.083333333333
* mass
* g.np.diag([h**2 + b**2, b**2 + d**2, h**2 + d**2])
0.083333333333 * mass * g.np.diag([h**2 + b**2, b**2 + d**2, h**2 + d**2])
)
a1 = -0.5 * d
a2 = 0.5 * h
Expand Down
10 changes: 10 additions & 0 deletions tests/test_obj.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ def test_export_path(self):
r = g.trimesh.load(file_path)
g.check_fuze(r)

def test_skip_mtl(self):
# not loading materials should produce a trivial texture
m_tex = g.get_mesh("fuze.obj")
m_tex_size = m_tex.visual.material.image.size

m_notex = g.get_mesh("fuze.obj", skip_materials=True)
m_notex_size = m_notex.visual.material.image.size

assert m_tex_size != m_notex_size

def test_mtl(self):
# get a mesh with texture
m = g.get_mesh("fuze.obj")
Expand Down
10 changes: 10 additions & 0 deletions tests/test_ply.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,16 @@ def test_texturefile(self):
# correct number of vertices and has texture loaded
g.check_fuze(m)

def test_skip_texturefile(self):
# not loading the texture should produce a trivial texture
m_tex = g.get_mesh("fuze.ply")
m_tex_size = m_tex.visual.material.image.size

m_notex = g.get_mesh("fuze.ply", skip_materials=True)
m_notex_size = m_notex.visual.material.image.size

assert m_tex_size != m_notex_size

def test_metadata(self):
mesh = g.get_mesh("metadata.ply")

Expand Down
32 changes: 27 additions & 5 deletions trimesh/exchange/gltf.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ def load_gltf(
resolver=None,
ignore_broken=False,
merge_primitives=False,
skip_materials=False,
**mesh_kwargs,
):
"""
Expand All @@ -283,6 +284,8 @@ def load_gltf(
merge_primitives : bool
If True, each GLTF 'mesh' will correspond
to a single Trimesh object
skip_materials : bool
If true, will not load materials (if present).
**mesh_kwargs : dict
Passed to mesh constructor
Expand Down Expand Up @@ -325,13 +328,19 @@ def load_gltf(
ignore_broken=ignore_broken,
merge_primitives=merge_primitives,
mesh_kwargs=mesh_kwargs,
skip_materials=skip_materials,
resolver=resolver,
)
return kwargs


def load_glb(
file_obj, resolver=None, ignore_broken=False, merge_primitives=False, **mesh_kwargs
file_obj,
resolver=None,
ignore_broken=False,
merge_primitives=False,
skip_materials=False,
**mesh_kwargs,
):
"""
Load a GLTF file in the binary GLB format into a trimesh.Scene.
Expand All @@ -345,9 +354,15 @@ def load_glb(
Containing GLB data
resolver : trimesh.visual.Resolver
Object which can be used to load other files by name
ignore_broken : bool
If there is a mesh we can't load and this
is True don't raise an exception but return
a partial result
merge_primitives : bool
If True, each GLTF 'mesh' will correspond to a
single Trimesh object.
skip_materials : bool
If true, will not load materials (if present).
Returns
------------
Expand Down Expand Up @@ -426,6 +441,7 @@ def load_glb(
buffers=buffers,
ignore_broken=ignore_broken,
merge_primitives=merge_primitives,
skip_materials=skip_materials,
mesh_kwargs=mesh_kwargs,
)

Expand Down Expand Up @@ -1337,6 +1353,7 @@ def _read_buffers(
mesh_kwargs,
ignore_broken=False,
merge_primitives=False,
skip_materials=False,
resolver=None,
):
"""
Expand All @@ -1357,6 +1374,8 @@ def _read_buffers(
a partial result
merge_primitives : bool
If true, combine primitives into a single mesh.
skip_materials : bool
If true, will not load materials (if present).
resolver : trimesh.resolvers.Resolver
Resolver to load referenced assets
Expand Down Expand Up @@ -1436,8 +1455,11 @@ def _read_buffers(
# a "sparse" accessor should be initialized as zeros
access[index] = np.zeros(count * per_count, dtype=dtype).reshape(shape)

# load images and textures into material objects
materials = _parse_materials(header, views=views, resolver=resolver)
# possibly load images and textures into material objects
if skip_materials:
materials = []
else:
materials = _parse_materials(header, views=views, resolver=resolver)

mesh_prim = collections.defaultdict(list)
# load data from accessors into Trimesh objects
Expand Down Expand Up @@ -1505,7 +1527,7 @@ def _read_buffers(
kwargs["vertex_normals"] = access[attr["NORMAL"]]
# do we have UV coordinates
visuals = None
if "material" in p:
if "material" in p and not skip_materials:
if materials is None:
log.debug("no materials! `pip install pillow`")
else:
Expand Down Expand Up @@ -1561,7 +1583,7 @@ def _read_buffers(
mesh_prim[index].append(name)
except BaseException as E:
if ignore_broken:
log.debug("failed to load mesh", exc_info=True),
log.debug("failed to load mesh", exc_info=True)
else:
raise E

Expand Down
30 changes: 23 additions & 7 deletions trimesh/exchange/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from ..parent import Geometry
from ..points import PointCloud
from ..scene.scene import Scene, append_scenes
from ..typed import Loadable, Optional
from ..util import log, now
from . import misc
from .binvox import _binvox_loaders
Expand All @@ -32,11 +33,11 @@
load_path = ExceptionWrapper(E)
# no path formats available

def path_formats():
def path_formats() -> set:
return set()


def mesh_formats():
def mesh_formats() -> set:
"""
Get a list of mesh formats available to load.
Expand All @@ -50,7 +51,7 @@ def mesh_formats():
return {k for k, v in mesh_loaders.items() if not isinstance(v, ExceptionWrapper)}


def available_formats():
def available_formats() -> set:
"""
Get a list of all available loaders
Expand All @@ -60,15 +61,20 @@ def available_formats():
Extensions of available loaders
i.e. 'stl', 'ply', 'dxf', etc.
"""
# set
loaders = mesh_formats()
loaders.update(path_formats())
loaders.update(compressed_loaders.keys())

return loaders


def load(file_obj, file_type=None, resolver=None, force=None, **kwargs):
def load(
file_obj: Loadable,
file_type: Optional[str] = None,
resolver: Optional[resolvers.Resolver] = None,
force: Optional[str] = None,
**kwargs,
):
"""
Load a mesh or vectorized path into objects like
Trimesh, Path2D, Path3D, Scene
Expand Down Expand Up @@ -155,7 +161,12 @@ def load(file_obj, file_type=None, resolver=None, force=None, **kwargs):
return loaded


def load_mesh(file_obj, file_type=None, resolver=None, **kwargs):
def load_mesh(
file_obj: Loadable,
file_type: Optional[str] = None,
resolver: Optional[resolvers.Resolver] = None,
**kwargs,
):
"""
Load a mesh file into a Trimesh object
Expand Down Expand Up @@ -491,7 +502,12 @@ def handle_pointcloud():
return handler()


def parse_file_args(file_obj, file_type, resolver=None, **kwargs):
def parse_file_args(
file_obj: Loadable,
file_type: Optional[str],
resolver: Optional[resolvers.Resolver] = None,
**kwargs,
):
"""
Given a file_obj and a file_type try to magically convert
arguments to a file-like object and a lowercase string of
Expand Down
Loading

0 comments on commit 31ea046

Please sign in to comment.