trimesh icon indicating copy to clipboard operation
trimesh copied to clipboard

Combining/union/concatenating adjacent meshes

Open pavelpereverzev opened this issue 1 year ago • 2 comments

Hello! Recently started using trimesh in a purpose of converting geojson polygons into extruded solids for 3d printing. Tested it on some data and found an issue. Due to some demands of 3d printing process all adjacent solids should be combined together. Let's say we got three simple boxes:

import trimesh
import pyglet
from shapely import Polygon

pts1 = ((0, 0), (10, 0), (10, 10), (0, 10))
pts2 = ((10, 10), (20, 10), (20, 20), (10, 20))
pts3 = ((10, 0), (20, 0), (20, 10), (10, 10))

p1 = Polygon(pts1, [])
p2 = Polygon(pts2, [])
p3 = Polygon(pts3, [])

mesh1 = trimesh.creation.extrude_polygon(p1, 10)
mesh2 = trimesh.creation.extrude_polygon(p2, 10)
mesh3 = trimesh.creation.extrude_polygon(p3, 10)

I tested two methods to do combine all of them.

  1. union - can stuck at moment when meshes are touching each other at single point. Looks like it is an error and cannot be resolved

united_meshes = mesh1.union(mesh2)

image

After this error nothing can be further added to this mesh and trimesh throws an error "Not all meshes are volumes!"

  1. concatenate - works better but keeps internal faces which can be seen later in AutoCAD:

concatenated_meshes = trimesh.util.concatenate([mesh1, mesh2, mesh3])

image

Tried for solutions like:

concatenated_meshes .merge_vertices(merge_tex=True, merge_norm=True)
concatenated_meshes .remove_unreferenced_vertices()
concatenated_meshes .update_faces(concatenated_meshes .unique_faces())
but nothing worked yet. Some interior faces circled red on image above persist in a mesh. 

Is there any way to delete that faces or even more better way to join meshes? It's also would be nice if there is a way to avoid creating triangle faces. Those primitives are simple extrusions of path and if it were extruded manually in AutoCAD, there wouldn't be any of diagonal edge, Thanks in advance!

pavelpereverzev avatar Sep 09 '24 21:09 pavelpereverzev

Hey, yeah I'd avoid mesh boolean operations if you can even though they've gotten a lot better since manifold3d came out. A 2D union works pretty well and you can polygon.buffer(1-e5) first if that's flaky:

In [1]: import numpy as np

In [2]: from shapely.ops import unary_union

In [3]: from shapely import Polygon

In [4]: pts1 = ((0, 0), (10, 0), (10, 10), (0, 10))
   ...: pts2 = ((10, 10), (20, 10), (20, 20), (10, 20))
   ...: pts3 = ((10, 0), (20, 0), (20, 10), (10, 10))

In [5]: p1 = Polygon(pts1, [])
   ...: p2 = Polygon(pts2, [])
   ...: p3 = Polygon(pts3, [])

In [7]: import trimesh

In [8]: trimesh.creation.extrude_polygon(unary_union([p1, p2, p3]), 10)
Out[8]: <trimesh.Trimesh(vertices.shape=(16, 3), faces.shape=(28, 3))

In [9]: trimesh.creation.extrude_polygon(unary_union([p1, p2, p3]), 10).is_volume
Out[9]: True

Or if everything is a quad like the example, you can use trimesh methods which merge vertices and find the outline by using trimesh.grouping.group_rows(mesh.edges_sorted, require_count=1) since edges that aren't on the boundary of a well-constructed mesh occur twice:

In [1]: import numpy as np

In [2]: import trimesh

In [3]: pts1 = ((0, 0), (10, 0), (10, 10), (0, 10))
   ...: pts2 = ((10, 10), (20, 10), (20, 20), (10, 20))
   ...: pts3 = ((10, 0), (20, 0), (20, 10), (10, 10))

In [5]: vertices = np.vstack([pts1, pts2, pts3], dtype=np.float64)

In [6]: quads = np.arange(len(vertices), dtype=np.int64).reshape((-1, 4))

In [7]: mesh = trimesh.Trimesh(vertices=vertices, faces=quads)

# quads are triangulated on load and duplicate vertices are merged by default
In [8]: mesh
Out[8]: <trimesh.Trimesh(vertices.shape=(8, 2), faces.shape=(6, 3))>

In [9]: mesh.outline()
Out[9]: <trimesh.Path3D(vertices.shape=(8, 2), len(entities)=1)>

In [11]: mesh.outline().to_planar()[0].extrude(10)
Out[11]: <trimesh.primitives.Extrusion>

In [12]: e = mesh.outline().to_planar()[0].extrude(10)

In [14]: e.is_volume
Out[14]: True

mikedh avatar Sep 09 '24 23:09 mikedh

@mikedh thanks for response! Your second code snippet does right thing! My complete code for example:

import numpy as np
import trimesh
import ezdxf

pts1 = ((0, 0),   (10, 0),  (10, 10), (0, 10))
pts2 = ((10, 10), (20, 10), (20, 20), (10, 20))
pts3 = ((10, 0),  (20, 0),  (20, 10), (10, 10))

vertices = np.vstack([pts1, pts2, pts3], dtype=np.float64)
quads = np.arange(len(vertices), dtype=np.int64).reshape((-1, 4))
mesh = trimesh.Trimesh(vertices=vertices, faces=quads)

extruded_mesh = mesh.outline().to_planar()[0].extrude(10)

# creating a dxf document with mesh
doc = ezdxf.new("R2000")
msp = doc.modelspace()

new_mesh = msp.add_mesh()
new_mesh.dxf.subdivision_levels = 0
with new_mesh.edit_data() as mesh_data:
    mesh_data.vertices = extruded_mesh.vertices.tolist()
    mesh_data.faces = extruded_mesh.faces.tolist()

doc.saveas("mesh.dxf")

image

It does not remove some faces on exterior but it's not a really big deal. Inner rows and faces are gone and it's okay.

And sorry, I forgot to point that extrusion height of polygons may be different because it refers to real building heights given in geojson source file.

It looks like everything is still about filtering faces which are created during polygon extrusion. And method trimesh.grouping.group_rows returns empty list for concatenated polygon made of three extruded meshed, because each row/face there is unique. image

pts1 = ((0, 0),   (10, 0),  (10, 10), (0, 10))
pts2 = ((10, 10), (20, 10), (20, 20), (10, 20))
pts3 = ((10, 0),  (20, 0),  (20, 10), (10, 10))

p1 = Polygon(pts1, [])
p2 = Polygon(pts2, [])
p3 = Polygon(pts3, [])

# extruded meshes with different heights
mesh1 = trimesh.creation.extrude_polygon(p1, 10) 
mesh2 = trimesh.creation.extrude_polygon(p2, 20)
mesh3 = trimesh.creation.extrude_polygon(p3, 30)

concatenated_mesh = trimesh.util.concatenate([mesh1, mesh2, mesh3])
filtered_faces = trimesh.grouping.group_rows(concatenated_mesh.edges_sorted, require_count=1) #will be empty

@mikedh is there some solution for this case?

pavelpereverzev avatar Sep 10 '24 15:09 pavelpereverzev