|
1 | 1 | import math |
2 | 2 | import compas_libigl as igl |
3 | 3 | from compas.colors import Color |
4 | | -from compas.geometry import Plane |
| 4 | +from compas.geometry import Plane, Rotation, Scale |
5 | 5 | from compas.datastructures import Mesh |
6 | | -from compas.geometry import Rotation, Scale |
7 | 6 | from compas_viewer import Viewer |
8 | 7 |
|
9 | | - |
10 | | -def split_mesh_by_plane(mesh, plane): |
11 | | - """Split a mesh into two parts using a plane. |
12 | | - |
13 | | - Parameters |
14 | | - ---------- |
15 | | - mesh : compas.datastructures.Mesh |
16 | | - The input mesh to split |
17 | | - plane : compas.geometry.Plane |
18 | | - The plane to split with |
19 | | - |
20 | | - Returns |
21 | | - ------- |
22 | | - tuple |
23 | | - Two meshes (below_mesh, above_mesh), representing parts on each side of the plane |
24 | | - """ |
25 | | - # Calculate signed distance to the plane for all vertices |
26 | | - distances = [] |
27 | | - for vertex in mesh.vertices(): |
28 | | - point = mesh.vertex_attributes(vertex, "xyz") |
29 | | - vector = plane.point - point |
30 | | - distance = plane.normal.dot(vector) |
31 | | - distances.append(distance) |
32 | | - mesh.vertex_attribute(vertex, "distance", distance) |
33 | | - |
34 | | - # Remesh along the zero isoline (plane intersection) |
35 | | - V2, F2, L = igl.trimesh_remesh_along_isoline( |
36 | | - mesh.to_vertices_and_faces(), |
37 | | - distances, |
38 | | - 0 |
39 | | - ) |
40 | | - |
41 | | - # Split faces based on labels (0 = below, 1 = above) |
42 | | - below_faces = [f for i, f in enumerate(F2) if L[i] == 0] |
43 | | - above_faces = [f for i, f in enumerate(F2) if L[i] == 1] |
44 | | - |
45 | | - # Get unique vertices for each part |
46 | | - below_vertices = set() |
47 | | - above_vertices = set() |
48 | | - for face in below_faces: |
49 | | - below_vertices.update(face) |
50 | | - for face in above_faces: |
51 | | - above_vertices.update(face) |
52 | | - |
53 | | - # Create vertex maps for new indices |
54 | | - below_vmap = {old: new for new, old in enumerate(sorted(below_vertices))} |
55 | | - above_vmap = {old: new for new, old in enumerate(sorted(above_vertices))} |
56 | | - |
57 | | - # Create new vertex lists |
58 | | - below_verts = [V2[i] for i in sorted(below_vertices)] |
59 | | - above_verts = [V2[i] for i in sorted(above_vertices)] |
60 | | - |
61 | | - # Remap face indices |
62 | | - below_faces = [[below_vmap[v] for v in face] for face in below_faces] |
63 | | - above_faces = [[above_vmap[v] for v in face] for face in above_faces] |
64 | | - |
65 | | - # Create new meshes for each part |
66 | | - below_mesh = Mesh.from_vertices_and_faces(below_verts, below_faces) if below_faces else None |
67 | | - above_mesh = Mesh.from_vertices_and_faces(above_verts, above_faces) if above_faces else None |
68 | | - |
69 | | - return below_mesh, above_mesh |
70 | | - |
71 | | - |
72 | 8 | # Load and transform mesh |
73 | 9 | mesh = Mesh.from_off(igl.get_beetle()) |
74 | 10 | R = Rotation.from_axis_and_angle([1, 0, 0], math.radians(90)) |
75 | 11 | S = Scale.from_factors([10, 10, 10]) |
76 | 12 | mesh.transform(S * R) |
77 | 13 |
|
78 | | -# Define a single cutting plane (horizontal at z=0) |
79 | | -cutting_plane = Plane([0, 0, 0], [0, 1, 1]) |
| 14 | +# Calculate signed distances to plane |
| 15 | +plane = Plane([0, 0, 0], [0, 1, 1]) |
| 16 | +distances = [plane.normal.dot(plane.point - mesh.vertex_coordinates(v)) for v in mesh.vertices()] |
80 | 17 |
|
81 | | -# Split the mesh |
82 | | -below_mesh, above_mesh = split_mesh_by_plane(mesh, cutting_plane) |
83 | | -print(f"Original mesh: {mesh.number_of_vertices()} vertices, {mesh.number_of_faces()} faces") |
84 | | -print(f"Below mesh: {below_mesh.number_of_vertices()} vertices, {below_mesh.number_of_faces()} faces") |
85 | | -print(f"Above mesh: {above_mesh.number_of_vertices()} vertices, {above_mesh.number_of_faces()} faces") |
| 18 | +# Split mesh along plane |
| 19 | +V, F, L = igl.trimesh_remesh_along_isoline(mesh.to_vertices_and_faces(), distances, 0) |
86 | 20 |
|
87 | | -# Setup visualization |
88 | | -viewer = Viewer() |
89 | | - |
90 | | -# Add both mesh parts with different colors |
91 | | -viewer.scene.add(below_mesh, facecolor=Color.red(), name="Below Plane") |
92 | | -viewer.scene.add(above_mesh, facecolor=Color.blue(), name="Above Plane") |
| 21 | +# Create meshes for parts below and above plane |
| 22 | +below = Mesh.from_vertices_and_faces(V, [F[i] for i, l in enumerate(L) if l == 0]) |
| 23 | +above = Mesh.from_vertices_and_faces(V, [F[i] for i, l in enumerate(L) if l == 1]) |
93 | 24 |
|
| 25 | +# Visualize |
| 26 | +viewer = Viewer() |
| 27 | +viewer.scene.add(below, facecolor=Color.red(), show_lines=False) |
| 28 | +viewer.scene.add(above, facecolor=Color.blue(), show_lines=False) |
94 | 29 | viewer.show() |
0 commit comments