From 70710c2dad5a287cbff5fdf6991367142e3d884e Mon Sep 17 00:00:00 2001 From: Dennis Date: Mon, 8 Jul 2024 09:00:38 +0200 Subject: [PATCH 1/4] Serial boolean union changed to binary reduction. --- trimesh/boolean.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/trimesh/boolean.py b/trimesh/boolean.py index 12356db58..bf6fd66f5 100644 --- a/trimesh/boolean.py +++ b/trimesh/boolean.py @@ -111,7 +111,6 @@ def boolean_manifold( meshes: Iterable, operation: str, check_volume: bool = True, - debug: bool = False, **kwargs, ): """ @@ -127,12 +126,12 @@ def boolean_manifold( Raise an error if not all meshes are watertight positive volumes. Advanced users may want to ignore this check as it is expensive. - debug - Enable potentially slow additional checks and debug info. kwargs Passed through to the `engine`. - """ + if check_volume and not all(m.is_volume for m in meshes): + raise ValueError("Not all meshes are volumes!") + # Convert to manifold meshes manifolds = [ Manifold( @@ -151,12 +150,16 @@ def boolean_manifold( result_manifold = manifolds[0] - manifolds[1] elif operation == "union": + for lvl in range(int(1 + np.log2(len(manifolds)))): + results = [] + for i in np.arange(len(manifolds) // 2) * 2: + results.append(manifolds[i] + manifolds[i + 1]) + if len(manifolds) % 2: + results.append(manifolds[-1]) + manifolds = results result_manifold = manifolds[0] - for manifold in manifolds[1:]: - result_manifold = result_manifold + manifold elif operation == "intersection": result_manifold = manifolds[0] - for manifold in manifolds[1:]: result_manifold = result_manifold ^ manifold else: From f5d1863efc4a94336ad73d0b5a78cccb7c2e3ff2 Mon Sep 17 00:00:00 2001 From: Dennis Date: Mon, 8 Jul 2024 09:46:14 +0200 Subject: [PATCH 2/4] Test for new boolean_manifold 'union' method added. --- tests/test_boolean.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index bdb50ef7c..3aa5d73f1 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -95,6 +95,40 @@ def test_empty(self): assert i.is_empty + def test_manifold_union(self): + if manifold3d is None: + return + + meshes = [g.trimesh.primitives.Sphere(center=[x / 2, 0, 0], subdivisions=0) for x in range(100)] + + # the old 'serial' manifold union method + tic = g.time.time() + manifolds = [ + manifold3d.Manifold( + mesh=manifold3d.Mesh( + vert_properties=g.np.array(mesh.vertices, dtype=g.np.float32), + tri_verts=g.np.array(mesh.faces, dtype=g.np.uint32), + ) + ) + for mesh in meshes + ] + result_manifold = manifolds[0] + for manifold in manifolds[1:]: + result_manifold = result_manifold + manifold + result_mesh = result_manifold.to_mesh() + old_mesh = g.trimesh.Trimesh(vertices=result_mesh.vert_properties, faces=result_mesh.tri_verts) + times = {'serial union': g.time.time() - tic} + + # new 'binary reduction' method + tic = g.time.time() + new_mesh = g.trimesh.boolean.boolean_manifold(meshes, 'union') + times['binary union'] = g.time.time() - tic + + assert old_mesh.is_volume == new_mesh.is_volume + assert old_mesh.body_count == new_mesh.body_count + assert g.np.isclose(old_mesh.volume, new_mesh.volume) + + g.log.info(times) if __name__ == "__main__": g.trimesh.util.attach_to_log() From 2e275a8abe4675f243613f7cf8cd552460c2c84c Mon Sep 17 00:00:00 2001 From: Dennis Date: Mon, 8 Jul 2024 10:45:41 +0200 Subject: [PATCH 3/4] Same reduction also implemented for 'intersection' and test extended. --- tests/test_boolean.py | 65 +++++++++++++++++++++++++------------------ trimesh/boolean.py | 11 ++++---- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index 3aa5d73f1..b0821546f 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -95,38 +95,49 @@ def test_empty(self): assert i.is_empty - def test_manifold_union(self): + def test_boolean_manifold(self): if manifold3d is None: return - meshes = [g.trimesh.primitives.Sphere(center=[x / 2, 0, 0], subdivisions=0) for x in range(100)] + times = {} + for operation in ["union", "intersection"]: + + if operation == "union": + # chain of icospheres + meshes = [g.trimesh.primitives.Sphere(center=[x / 2, 0, 0], subdivisions=0) for x in range(100)] + else: + # closer icospheres for non-empty-intersection + meshes = [g.trimesh.primitives.Sphere(center=[x, x, x], subdivisions=0) for x in g.np.linspace(0, 0.5, 101)] - # the old 'serial' manifold union method - tic = g.time.time() - manifolds = [ - manifold3d.Manifold( - mesh=manifold3d.Mesh( - vert_properties=g.np.array(mesh.vertices, dtype=g.np.float32), - tri_verts=g.np.array(mesh.faces, dtype=g.np.uint32), + # the old 'serial' manifold method + tic = g.time.time() + manifolds = [ + manifold3d.Manifold( + mesh=manifold3d.Mesh( + vert_properties=g.np.array(mesh.vertices, dtype=g.np.float32), + tri_verts=g.np.array(mesh.faces, dtype=g.np.uint32), + ) ) - ) - for mesh in meshes - ] - result_manifold = manifolds[0] - for manifold in manifolds[1:]: - result_manifold = result_manifold + manifold - result_mesh = result_manifold.to_mesh() - old_mesh = g.trimesh.Trimesh(vertices=result_mesh.vert_properties, faces=result_mesh.tri_verts) - times = {'serial union': g.time.time() - tic} - - # new 'binary reduction' method - tic = g.time.time() - new_mesh = g.trimesh.boolean.boolean_manifold(meshes, 'union') - times['binary union'] = g.time.time() - tic - - assert old_mesh.is_volume == new_mesh.is_volume - assert old_mesh.body_count == new_mesh.body_count - assert g.np.isclose(old_mesh.volume, new_mesh.volume) + for mesh in meshes + ] + result_manifold = manifolds[0] + for manifold in manifolds[1:]: + if operation == "union": + result_manifold = result_manifold + manifold + else: # operation == "intersection": + result_manifold = result_manifold ^ manifold + result_mesh = result_manifold.to_mesh() + old_mesh = g.trimesh.Trimesh(vertices=result_mesh.vert_properties, faces=result_mesh.tri_verts) + times["serial " + operation] = g.time.time() - tic + + # new 'binary' method + tic = g.time.time() + new_mesh = g.trimesh.boolean.boolean_manifold(meshes, operation) + times["binary " + operation] = g.time.time() - tic + + assert old_mesh.is_volume == new_mesh.is_volume + assert old_mesh.body_count == new_mesh.body_count + assert g.np.isclose(old_mesh.volume, new_mesh.volume) g.log.info(times) diff --git a/trimesh/boolean.py b/trimesh/boolean.py index bf6fd66f5..abe34baf5 100644 --- a/trimesh/boolean.py +++ b/trimesh/boolean.py @@ -149,19 +149,18 @@ def boolean_manifold( raise ValueError("Difference only defined over two meshes.") result_manifold = manifolds[0] - manifolds[1] - elif operation == "union": + elif operation in ["union", "intersection"]: for lvl in range(int(1 + np.log2(len(manifolds)))): results = [] for i in np.arange(len(manifolds) // 2) * 2: - results.append(manifolds[i] + manifolds[i + 1]) + if operation == "union": + results.append(manifolds[i] + manifolds[i + 1]) + else: # operation == "intersection": + results.append(manifolds[i] ^ manifolds[i + 1]) if len(manifolds) % 2: results.append(manifolds[-1]) manifolds = results result_manifold = manifolds[0] - elif operation == "intersection": - result_manifold = manifolds[0] - for manifold in manifolds[1:]: - result_manifold = result_manifold ^ manifold else: raise ValueError(f"Invalid boolean operation: '{operation}'") From c0c23608d710664733ca6c0335aec5b2a437bd5e Mon Sep 17 00:00:00 2001 From: Dennis Date: Mon, 8 Jul 2024 10:57:42 +0200 Subject: [PATCH 4/4] Formatting fixes. --- tests/test_boolean.py | 2 +- trimesh/boolean.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_boolean.py b/tests/test_boolean.py index b0821546f..e07c1e9a1 100644 --- a/tests/test_boolean.py +++ b/tests/test_boolean.py @@ -139,7 +139,7 @@ def test_boolean_manifold(self): assert old_mesh.body_count == new_mesh.body_count assert g.np.isclose(old_mesh.volume, new_mesh.volume) - g.log.info(times) + g.log.info(times) if __name__ == "__main__": g.trimesh.util.attach_to_log() diff --git a/trimesh/boolean.py b/trimesh/boolean.py index abe34baf5..c70710151 100644 --- a/trimesh/boolean.py +++ b/trimesh/boolean.py @@ -131,7 +131,7 @@ def boolean_manifold( """ if check_volume and not all(m.is_volume for m in meshes): raise ValueError("Not all meshes are volumes!") - + # Convert to manifold meshes manifolds = [ Manifold( @@ -150,7 +150,7 @@ def boolean_manifold( result_manifold = manifolds[0] - manifolds[1] elif operation in ["union", "intersection"]: - for lvl in range(int(1 + np.log2(len(manifolds)))): + for _lvl in range(int(1 + np.log2(len(manifolds)))): results = [] for i in np.arange(len(manifolds) // 2) * 2: if operation == "union":