Skip to content

Commit 3d5af39

Browse files
authored
Merge pull request #2152 from mikedh/release/blender
Release: Scale Fix
2 parents 11cd6f8 + c1a0c6b commit 3d5af39

12 files changed

Lines changed: 109 additions & 75 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ COPY --chown=499 pyproject.toml .
6363
COPY --chown=499 ./.git ./.git/
6464

6565
USER root
66-
RUN trimesh-setup --install=test,build,gltf_validator,llvmpipe,binvox
66+
RUN trimesh-setup --install=test,gltf_validator,llvmpipe,binvox
6767
USER user
6868

6969
# install things like pytest

pyproject.toml

Lines changed: 2 additions & 2 deletions
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.1.3"
8+
version = "4.1.4"
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."
@@ -78,6 +78,7 @@ easy = [
7878
"embreex",
7979
"pillow",
8080
"vhacdx",
81+
"xatlas",
8182
]
8283

8384
recommend = [
@@ -86,7 +87,6 @@ recommend = [
8687
"meshio",
8788
"pyglet<2",
8889
"psutil",
89-
"xatlas",
9090
"scikit-image",
9191
"python-fcl",
9292
"manifold3d>=2.3.0"

tests/test_ply.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -103,24 +103,30 @@ def test_face_attributes(self):
103103
# Test writing face attributes to a ply, by reading
104104
# them back and asserting the written attributes array matches
105105

106-
m = g.get_mesh("box.STL")
107-
test_1d_attribute = g.np.copy(m.face_angles[:, 0])
108-
test_nd_attribute = g.np.copy(m.face_angles)
109-
m.face_attributes["test_1d_attribute"] = test_1d_attribute
110-
m.face_attributes["test_nd_attribute"] = test_nd_attribute
111-
112-
export = m.export(file_type="ply")
113-
reconstructed = g.roundtrip(export, file_type="ply")
114-
115-
face_attributes = reconstructed.metadata["_ply_raw"]["face"]["data"]
116-
result_1d = face_attributes["test_1d_attribute"]
117-
result_nd = face_attributes["test_nd_attribute"]["f1"]
118-
119-
g.np.testing.assert_almost_equal(result_1d, test_1d_attribute)
120-
g.np.testing.assert_almost_equal(result_nd, test_nd_attribute)
121-
122-
no_attr = m.export(file_type="ply", include_attributes=False)
123-
assert len(no_attr) < len(export)
106+
for encoding in ["binary", "ascii"]:
107+
for dt in [g.np.float32, g.np.float64]:
108+
m = g.get_mesh("box.STL")
109+
test_1d_attribute = g.np.copy(m.face_angles[:, 0])
110+
test_nd_attribute = g.np.copy(m.face_angles)
111+
m.face_attributes["test_1d_attribute"] = test_1d_attribute.astype(dt)
112+
m.face_attributes["test_nd_attribute"] = test_nd_attribute.astype(dt)
113+
114+
export = m.export(file_type="ply", include_attributes=True, encoding=encoding)
115+
reconstructed = g.roundtrip(export, file_type="ply", process=False)
116+
117+
face_attributes = reconstructed.metadata["_ply_raw"]["face"]["data"]
118+
result_1d = face_attributes["test_1d_attribute"]
119+
if encoding == "binary":
120+
# only binary format allows this
121+
result_nd = face_attributes["test_nd_attribute"]["f1"]
122+
else:
123+
result_nd = face_attributes["test_nd_attribute"]
124+
125+
g.np.testing.assert_almost_equal(result_1d, test_1d_attribute)
126+
g.np.testing.assert_almost_equal(result_nd, test_nd_attribute)
127+
128+
no_attr = m.export(file_type="ply", include_attributes=False)
129+
assert len(no_attr) < len(export)
124130

125131
def test_cases(self):
126132
a = g.get_mesh("featuretype.STL")

trimesh/base.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -565,24 +565,6 @@ def extents(self) -> Optional[NDArray[float64]]:
565565

566566
return extents
567567

568-
@caching.cache_decorator
569-
def scale(self) -> float:
570-
"""
571-
A metric for the overall scale of the mesh, the length of the
572-
diagonal of the axis aligned bounding box of the mesh.
573-
574-
Returns
575-
----------
576-
scale : float
577-
The length of the meshes AABB diagonal
578-
"""
579-
# if mesh is empty just return no scale
580-
if self.extents is None:
581-
return 1.0
582-
# make sure we are returning python floats
583-
scale = float((self.extents**2).sum() ** 0.5)
584-
return scale
585-
586568
@caching.cache_decorator
587569
def centroid(self) -> NDArray[float64]:
588570
"""

trimesh/boolean.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ def intersection(meshes, engine=None, **kwargs):
6767
Meshes to be processed
6868
engine : str
6969
Which backend to use, i.e. 'blender' or 'manifold'
70+
solver_options: str
71+
Fast has some limitations
72+
Exact is slow but handles most of the cases
73+
use_self: Bool
74+
Self Intersection, Do self-union or self-intersection
7075
7176
Returns
7277
----------
@@ -100,7 +105,6 @@ def boolean_manifold(meshes, operation, debug=False, **kwargs):
100105
result_manifold = manifolds[0] - manifolds[1]
101106
elif operation == "union":
102107
result_manifold = manifolds[0]
103-
104108
for manifold in manifolds[1:]:
105109
result_manifold = result_manifold + manifold
106110
elif operation == "intersection":

trimesh/caching.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
```
2121
"""
2222
import os
23+
import sys
2324
import time
2425
from functools import wraps
26+
from hashlib import sha256 as _sha256
2527

2628
import numpy as np
2729

@@ -34,22 +36,17 @@
3436
from collections.abc import Mapping
3537

3638

37-
# sha256 is always available
38-
from hashlib import sha256 as _sha256
39-
40-
41-
def sha256(item):
39+
def sha256(item) -> int:
4240
return int(_sha256(item).hexdigest(), 16)
4341

4442

45-
try:
43+
if sys.version_info >= (3, 9):
4644
# blake2b is available on Python 3 and
4745
from hashlib import blake2b as _blake2b
4846

4947
def hash_fallback(item):
50-
return int(_blake2b(item).hexdigest(), 16)
51-
52-
except BaseException:
48+
return int(_blake2b(item, usedforsecurity=False).hexdigest(), 16)
49+
else:
5350
# fallback to sha256
5451
hash_fallback = sha256
5552

trimesh/exchange/ply.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ def _add_attributes_to_dtype(dtype, attributes):
153153
dtype.append((name, data.dtype))
154154
else:
155155
attribute_dtype = data.dtype if len(data.dtype) == 0 else data.dtype[0]
156-
dtype.append((f"{name}_count", "u1"))
157-
dtype.append((name, _numpy_type_to_ply_type(attribute_dtype), data.shape[1]))
156+
dtype.append((f"{name}_count", "<u1"))
157+
dtype.append((name, attribute_dtype, data.shape[1]))
158158
return dtype
159159

160160

trimesh/interfaces/blender.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,49 @@
3232
exists = _blender_executable is not None
3333

3434

35-
def boolean(meshes, operation="difference", debug=False):
35+
def boolean(
36+
meshes,
37+
operation: str = "difference",
38+
use_exact: bool = False,
39+
use_self: bool = False,
40+
debug: bool = False,
41+
):
3642
"""
3743
Run a boolean operation with multiple meshes using Blender.
44+
45+
Parameters
46+
-----------
47+
meshes
48+
List of mesh objects to be operated on
49+
operation
50+
Type of boolean operation ("difference", "union", "intersect").
51+
use_exact
52+
Use the "exact" mode as opposed to the "fast" mode.
53+
use_self
54+
Whether to consider self-intersections.
55+
debug
56+
Provide additional output for troubleshooting.
57+
58+
Returns
59+
----------
60+
result
61+
The result of the boolean operation on the provided meshes.
3862
"""
3963
if not exists:
4064
raise ValueError("No blender available!")
4165
operation = str.upper(operation)
4266
if operation == "INTERSECTION":
4367
operation = "INTERSECT"
4468

69+
if use_exact:
70+
solver_options = "EXACT"
71+
else:
72+
solver_options = "FAST"
4573
# get the template from our resources folder
4674
template = resources.get("templates/blender_boolean.py.tmpl")
4775
script = template.replace("$OPERATION", operation)
76+
script = script.replace("$SOLVER_OPTIONS", solver_options)
77+
script = script.replace("$USE_SELF", f"{use_self}")
4878

4979
with MeshScript(meshes=meshes, script=script, debug=debug) as blend:
5080
result = blend.run(_blender_executable + " --background --python $SCRIPT")

trimesh/parent.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from . import bounds, caching
1212
from . import transformations as tf
13+
from .caching import cache_decorator
1314
from .constants import tol
1415
from .util import ABC
1516

@@ -152,6 +153,32 @@ def __radd__(self, other):
152153
# otherwise just use the regular add function
153154
return self.__add__(type(self)(other))
154155

156+
@cache_decorator
157+
def scale(self) -> float:
158+
"""
159+
A loosely specified "order of magnitude scale" for the
160+
geometry which always returns a value and can be used
161+
to make code more robust to large scaling differences.
162+
163+
It returns the diagonal of the axis aligned bounding box
164+
or if anything is invalid or undefined, `1.0`.
165+
166+
Returns
167+
----------
168+
scale : float
169+
Approximate order of magnitude scale of the geometry.
170+
"""
171+
# if geometry is empty return 1.0
172+
if self.extents is None:
173+
return 1.0
174+
175+
# get the length of the AABB diagonal
176+
scale = float((self.extents**2).sum() ** 0.5)
177+
if scale < tol.zero:
178+
return 1.0
179+
180+
return scale
181+
155182

156183
class Geometry3D(Geometry):
157184

trimesh/path/path.py

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -267,24 +267,6 @@ def kdtree(self):
267267
kdtree = cKDTree(self.vertices.view(np.ndarray))
268268
return kdtree
269269

270-
@property
271-
def scale(self) -> float:
272-
"""
273-
What is a representitive number that reflects the magnitude
274-
of the world holding the paths, for numerical comparisons.
275-
276-
Returns
277-
----------
278-
scale : float
279-
Approximate size of the world holding this path
280-
"""
281-
# check to see if this path is empty first
282-
if len(self.vertices) == 0:
283-
return 1.0
284-
285-
# return the diagonal length of the AABB
286-
return np.linalg.norm(self.vertices.ptp(axis=0))
287-
288270
@caching.cache_decorator
289271
def length(self):
290272
"""

0 commit comments

Comments
 (0)