Skip to content

Commit 31ea046

Browse files
authored
Merge pull request #2083 from mikedh/feat/loadable
Release: Loadable Type, Grouping, Skip Materials
2 parents 4c4a9a0 + e1b0ad8 commit 31ea046

14 files changed

Lines changed: 195 additions & 84 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ requires = ["setuptools >= 61.0", "wheel"]
55
[project]
66
name = "trimesh"
77
requires-python = ">=3.7"
8-
version = "4.0.5"
8+
version = "4.0.6"
99
authors = [{name = "Michael Dawson-Haggerty", email = "mikedh@kerfed.com"}]
1010
license = {file = "LICENSE.md"}
1111
description = "Import, export, process, analyze and view triangular meshes."

tests/test_gltf.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,24 @@ def test_buffer_dedupe(self):
102102
a = g.json.loads(scene.export(file_type="gltf")["model.gltf"].decode("utf-8"))
103103
assert len(a["buffers"]) <= 3
104104

105+
def test_skip_materials(self):
106+
# load textured PLY
107+
mesh = g.get_mesh("fuze.ply")
108+
g.check_fuze(mesh)
109+
110+
# load as GLB
111+
export = mesh.export(file_type="glb", unitize_normals=True)
112+
validate_glb(export)
113+
mesh_glb = g.trimesh.load(
114+
g.trimesh.util.wrap_as_stream(export),
115+
file_type="glb",
116+
force="mesh",
117+
skip_materials=True,
118+
)
119+
120+
# visuals should not be present
121+
assert not mesh_glb.visual.defined
122+
105123
def test_tex_export(self):
106124
# load textured PLY
107125
mesh = g.get_mesh("fuze.ply")

tests/test_graph.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,15 @@ def test_traversals(self):
176176
)
177177

178178
# make a set from edges included in the traversal
179-
inc_set = set(g.trimesh.grouping.hashable_rows(g.np.sort(inc, axis=1)))
179+
inc_set = {
180+
i.tobytes()
181+
for i in g.trimesh.grouping.hashable_rows(g.np.sort(inc, axis=1))
182+
}
180183
# make a set of the source edges we were supposed to include
181-
edge_set = set(g.trimesh.grouping.hashable_rows(g.np.sort(edges, axis=1)))
184+
edge_set = {
185+
i.tobytes()
186+
for i in g.trimesh.grouping.hashable_rows(g.np.sort(edges, axis=1))
187+
}
182188

183189
# we should have exactly the same edges
184190
# after the filled traversal as we started with

tests/test_inertia.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ def test_inertia(self):
4343
assert g.np.abs(g.np.dot(t1, t1.T) - g.np.eye(4)).max() < 1e-10
4444

4545
c = g.trimesh.primitives.Cylinder(
46-
height=10, radius=1, sections=720, transform=t0 # number of slices
46+
height=10,
47+
radius=1,
48+
sections=720,
49+
transform=t0, # number of slices
4750
)
4851
c0m = c.moment_inertia.copy()
4952
c0 = g.trimesh.inertia.cylinder_inertia(
@@ -343,9 +346,7 @@ def parallel_axis_theorem(inertia, mass, a1, a2, a3):
343346
# CHECK FRAME 0
344347
# analytical calculations of inertia tensor by hand
345348
inertia0 = (
346-
0.083333333333
347-
* mass
348-
* g.np.diag([h**2 + b**2, h**2 + d**2, b**2 + d**2])
349+
0.083333333333 * mass * g.np.diag([h**2 + b**2, h**2 + d**2, b**2 + d**2])
349350
)
350351
a1 = -0.5 * d
351352
a2 = -0.5 * b
@@ -358,9 +359,7 @@ def parallel_axis_theorem(inertia, mass, a1, a2, a3):
358359
# CHECK FRAME 1
359360
# analytical calculations of inertia tensor by hand
360361
inertia1 = (
361-
0.083333333333
362-
* mass
363-
* g.np.diag([h**2 + d**2, h**2 + b**2, b**2 + d**2])
362+
0.083333333333 * mass * g.np.diag([h**2 + d**2, h**2 + b**2, b**2 + d**2])
364363
)
365364
a1 = -0.5 * b
366365
a2 = 0.5 * d
@@ -376,9 +375,7 @@ def parallel_axis_theorem(inertia, mass, a1, a2, a3):
376375
# CHECK FRAME 2
377376
# analytical calculations of inertia tensor by hand
378377
inertia2 = (
379-
0.083333333333
380-
* mass
381-
* g.np.diag([h**2 + b**2, b**2 + d**2, h**2 + d**2])
378+
0.083333333333 * mass * g.np.diag([h**2 + b**2, b**2 + d**2, h**2 + d**2])
382379
)
383380
a1 = -0.5 * d
384381
a2 = 0.5 * h

tests/test_obj.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,16 @@ def test_export_path(self):
175175
r = g.trimesh.load(file_path)
176176
g.check_fuze(r)
177177

178+
def test_skip_mtl(self):
179+
# not loading materials should produce a trivial texture
180+
m_tex = g.get_mesh("fuze.obj")
181+
m_tex_size = m_tex.visual.material.image.size
182+
183+
m_notex = g.get_mesh("fuze.obj", skip_materials=True)
184+
m_notex_size = m_notex.visual.material.image.size
185+
186+
assert m_tex_size != m_notex_size
187+
178188
def test_mtl(self):
179189
# get a mesh with texture
180190
m = g.get_mesh("fuze.obj")

tests/test_ply.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,16 @@ def test_texturefile(self):
232232
# correct number of vertices and has texture loaded
233233
g.check_fuze(m)
234234

235+
def test_skip_texturefile(self):
236+
# not loading the texture should produce a trivial texture
237+
m_tex = g.get_mesh("fuze.ply")
238+
m_tex_size = m_tex.visual.material.image.size
239+
240+
m_notex = g.get_mesh("fuze.ply", skip_materials=True)
241+
m_notex_size = m_notex.visual.material.image.size
242+
243+
assert m_tex_size != m_notex_size
244+
235245
def test_metadata(self):
236246
mesh = g.get_mesh("metadata.ply")
237247

trimesh/exchange/gltf.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ def load_gltf(
264264
resolver=None,
265265
ignore_broken=False,
266266
merge_primitives=False,
267+
skip_materials=False,
267268
**mesh_kwargs,
268269
):
269270
"""
@@ -283,6 +284,8 @@ def load_gltf(
283284
merge_primitives : bool
284285
If True, each GLTF 'mesh' will correspond
285286
to a single Trimesh object
287+
skip_materials : bool
288+
If true, will not load materials (if present).
286289
**mesh_kwargs : dict
287290
Passed to mesh constructor
288291
@@ -325,13 +328,19 @@ def load_gltf(
325328
ignore_broken=ignore_broken,
326329
merge_primitives=merge_primitives,
327330
mesh_kwargs=mesh_kwargs,
331+
skip_materials=skip_materials,
328332
resolver=resolver,
329333
)
330334
return kwargs
331335

332336

333337
def load_glb(
334-
file_obj, resolver=None, ignore_broken=False, merge_primitives=False, **mesh_kwargs
338+
file_obj,
339+
resolver=None,
340+
ignore_broken=False,
341+
merge_primitives=False,
342+
skip_materials=False,
343+
**mesh_kwargs,
335344
):
336345
"""
337346
Load a GLTF file in the binary GLB format into a trimesh.Scene.
@@ -345,9 +354,15 @@ def load_glb(
345354
Containing GLB data
346355
resolver : trimesh.visual.Resolver
347356
Object which can be used to load other files by name
357+
ignore_broken : bool
358+
If there is a mesh we can't load and this
359+
is True don't raise an exception but return
360+
a partial result
348361
merge_primitives : bool
349362
If True, each GLTF 'mesh' will correspond to a
350363
single Trimesh object.
364+
skip_materials : bool
365+
If true, will not load materials (if present).
351366
352367
Returns
353368
------------
@@ -426,6 +441,7 @@ def load_glb(
426441
buffers=buffers,
427442
ignore_broken=ignore_broken,
428443
merge_primitives=merge_primitives,
444+
skip_materials=skip_materials,
429445
mesh_kwargs=mesh_kwargs,
430446
)
431447

@@ -1337,6 +1353,7 @@ def _read_buffers(
13371353
mesh_kwargs,
13381354
ignore_broken=False,
13391355
merge_primitives=False,
1356+
skip_materials=False,
13401357
resolver=None,
13411358
):
13421359
"""
@@ -1357,6 +1374,8 @@ def _read_buffers(
13571374
a partial result
13581375
merge_primitives : bool
13591376
If true, combine primitives into a single mesh.
1377+
skip_materials : bool
1378+
If true, will not load materials (if present).
13601379
resolver : trimesh.resolvers.Resolver
13611380
Resolver to load referenced assets
13621381
@@ -1436,8 +1455,11 @@ def _read_buffers(
14361455
# a "sparse" accessor should be initialized as zeros
14371456
access[index] = np.zeros(count * per_count, dtype=dtype).reshape(shape)
14381457

1439-
# load images and textures into material objects
1440-
materials = _parse_materials(header, views=views, resolver=resolver)
1458+
# possibly load images and textures into material objects
1459+
if skip_materials:
1460+
materials = []
1461+
else:
1462+
materials = _parse_materials(header, views=views, resolver=resolver)
14411463

14421464
mesh_prim = collections.defaultdict(list)
14431465
# load data from accessors into Trimesh objects
@@ -1505,7 +1527,7 @@ def _read_buffers(
15051527
kwargs["vertex_normals"] = access[attr["NORMAL"]]
15061528
# do we have UV coordinates
15071529
visuals = None
1508-
if "material" in p:
1530+
if "material" in p and not skip_materials:
15091531
if materials is None:
15101532
log.debug("no materials! `pip install pillow`")
15111533
else:
@@ -1561,7 +1583,7 @@ def _read_buffers(
15611583
mesh_prim[index].append(name)
15621584
except BaseException as E:
15631585
if ignore_broken:
1564-
log.debug("failed to load mesh", exc_info=True),
1586+
log.debug("failed to load mesh", exc_info=True)
15651587
else:
15661588
raise E
15671589

trimesh/exchange/load.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from ..parent import Geometry
1010
from ..points import PointCloud
1111
from ..scene.scene import Scene, append_scenes
12+
from ..typed import Loadable, Optional
1213
from ..util import log, now
1314
from . import misc
1415
from .binvox import _binvox_loaders
@@ -32,11 +33,11 @@
3233
load_path = ExceptionWrapper(E)
3334
# no path formats available
3435

35-
def path_formats():
36+
def path_formats() -> set:
3637
return set()
3738

3839

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

5253

53-
def available_formats():
54+
def available_formats() -> set:
5455
"""
5556
Get a list of all available loaders
5657
@@ -60,15 +61,20 @@ def available_formats():
6061
Extensions of available loaders
6162
i.e. 'stl', 'ply', 'dxf', etc.
6263
"""
63-
# set
6464
loaders = mesh_formats()
6565
loaders.update(path_formats())
6666
loaders.update(compressed_loaders.keys())
6767

6868
return loaders
6969

7070

71-
def load(file_obj, file_type=None, resolver=None, force=None, **kwargs):
71+
def load(
72+
file_obj: Loadable,
73+
file_type: Optional[str] = None,
74+
resolver: Optional[resolvers.Resolver] = None,
75+
force: Optional[str] = None,
76+
**kwargs,
77+
):
7278
"""
7379
Load a mesh or vectorized path into objects like
7480
Trimesh, Path2D, Path3D, Scene
@@ -155,7 +161,12 @@ def load(file_obj, file_type=None, resolver=None, force=None, **kwargs):
155161
return loaded
156162

157163

158-
def load_mesh(file_obj, file_type=None, resolver=None, **kwargs):
164+
def load_mesh(
165+
file_obj: Loadable,
166+
file_type: Optional[str] = None,
167+
resolver: Optional[resolvers.Resolver] = None,
168+
**kwargs,
169+
):
159170
"""
160171
Load a mesh file into a Trimesh object
161172
@@ -491,7 +502,12 @@ def handle_pointcloud():
491502
return handler()
492503

493504

494-
def parse_file_args(file_obj, file_type, resolver=None, **kwargs):
505+
def parse_file_args(
506+
file_obj: Loadable,
507+
file_type: Optional[str],
508+
resolver: Optional[resolvers.Resolver] = None,
509+
**kwargs,
510+
):
495511
"""
496512
Given a file_obj and a file_type try to magically convert
497513
arguments to a file-like object and a lowercase string of

0 commit comments

Comments
 (0)