Skip to content

Commit ae5f526

Browse files
REFACTOR project method.
1 parent 251038c commit ae5f526

File tree

8 files changed

+257
-145
lines changed

8 files changed

+257
-145
lines changed

docs/examples/example_meshing.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from compas.datastructures import Mesh
33
from compas_viewer import Viewer
44

5-
from compas_cgal.meshing import mesh_remesh
5+
from compas_cgal.meshing import trimesh_remesh
66

77

88
input_file = Path(__file__).parent.parent.parent / "data" / "rhinovault_mesh_0.ply"
@@ -13,7 +13,7 @@
1313
iterations = 10
1414

1515
# Remesh
16-
V1, F1 = mesh_remesh(mesh.to_vertices_and_faces(), edge_length, iterations)
16+
V1, F1 = trimesh_remesh(mesh.to_vertices_and_faces(), edge_length, iterations)
1717
remeshed = Mesh.from_vertices_and_faces(V1, F1)
1818

1919

docs/examples/example_meshing_dual.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from compas.datastructures import Mesh
44
from compas_viewer import Viewer
55

6-
from compas_cgal.meshing import remesh_dual
6+
from compas_cgal.meshing import trimesh_dual
77

88

99
input_file = Path(__file__).parent.parent.parent / "data" / "rhinovault_mesh_0.ply"
@@ -17,10 +17,9 @@
1717
fixed_vertices.append(v)
1818

1919
# Remesh and Dual
20-
V2, F2, V3, F3 = remesh_dual(mesh.to_vertices_and_faces(), length_factor=1.0, number_of_iterations=100, angle_radians=0.9, fixed_vertices=[])
20+
V2, F2, V3, F3 = trimesh_dual(mesh.to_vertices_and_faces(), length_factor=1.0, number_of_iterations=100, angle_radians=0.9, fixed_vertices=[])
2121
remeshed, dual = Mesh.from_vertices_and_faces(V2, F2), Mesh.from_vertices_and_faces(V3, F3)
2222

23-
2423
# ==============================================================================
2524
# Visualize
2625
# ==============================================================================

docs/examples/example_projection.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from compas.datastructures import Mesh
55
from compas_viewer import Viewer
66

7-
from compas_cgal.meshing import project_mesh_on_mesh, project_points_on_mesh
7+
from compas_cgal.meshing import project_mesh_on_mesh, project_points_on_mesh, pull_mesh_on_mesh, pull_points_on_mesh
88

99

1010
# Mesh to project
@@ -15,14 +15,17 @@
1515
input_file_1 = Path(__file__).parent.parent.parent / "data" / "rhinovault_mesh_1.ply"
1616
mesh_1 = Mesh.from_ply(input_file_1)
1717

18-
mesh_result = project_mesh_on_mesh(mesh_0, mesh_1, use_normals=False)
18+
mesh_result_project = project_mesh_on_mesh(mesh_0, mesh_1)
1919

2020
# Points with normals to project
2121
v, f = mesh_0.to_vertices_and_faces()
2222
normals = []
2323
for i in range(len(v)):
2424
normals.append(mesh_0.vertex_normal(i))
25-
points_result = project_points_on_mesh(v, mesh_1, normals)
25+
points_result_project = project_points_on_mesh(v, mesh_1)
26+
27+
mesh_result_pull = pull_mesh_on_mesh(mesh_0, mesh_1)
28+
points_result_pull = pull_points_on_mesh(v, normals, mesh_1)
2629

2730
# ==============================================================================
2831
# Visualize
@@ -34,8 +37,15 @@
3437

3538
viewer.scene.add(mesh_0, name="source mesh_0", show_faces=False)
3639
viewer.scene.add(mesh_1, name="target mesh_1", show_faces=False)
37-
viewer.scene.add(mesh_result, name="projected mesh_result", color=[255, 0, 0])
38-
for i, point in enumerate(points_result):
39-
viewer.scene.add(Point(*point), name=f"point_{i}", color=[0, 255, 0])
40+
viewer.scene.add(mesh_result_project, name="projected mesh", color=[255, 0, 0])
41+
viewer.scene.add(mesh_result_pull, name="pulled mesh", color=[0, 255, 0])
42+
43+
group_projected = viewer.scene.add_group("projected points")
44+
for i, point in enumerate(points_result_project):
45+
group_projected.add(Point(*point), name=f"point_{i}", color=[0, 0, 0])
46+
47+
group_pulled = viewer.scene.add_group("pulled points")
48+
for i, point in enumerate(points_result_pull):
49+
group_pulled.add(Point(*point), name=f"point_{i}", color=[0, 0, 0])
4050

4151
viewer.show()

src/compas_cgal/meshing.py

Lines changed: 103 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from typing import Optional
2-
31
import numpy as np
42
from compas.datastructures import Mesh
53
from compas.plugins import plugin
@@ -12,7 +10,7 @@
1210

1311

1412
@plugin(category="trimesh", pluggable_name="trimesh_remesh")
15-
def mesh_remesh(
13+
def trimesh_remesh(
1614
mesh: VerticesFaces,
1715
target_edge_length: float,
1816
number_of_iterations: int = 10,
@@ -55,24 +53,71 @@ def mesh_remesh(
5553
V, F = mesh
5654
V = np.asarray(V, dtype=np.float64, order="C")
5755
F = np.asarray(F, dtype=np.int32, order="C")
58-
return _meshing.remesh(V, F, target_edge_length, number_of_iterations, do_project)
56+
return _meshing.pmp_trimesh_remesh(V, F, target_edge_length, number_of_iterations, do_project)
57+
58+
59+
def trimesh_dual(
60+
mesh: VerticesFaces,
61+
length_factor: float = 1.0,
62+
number_of_iterations: int = 10,
63+
angle_radians: float = 0.9,
64+
scale_factor: float = 1.0,
65+
fixed_vertices: list[int] = [],
66+
) -> tuple[np.ndarray, list[list[int]]]:
67+
"""Create a dual mesh from a triangular mesh with variable-length faces.
68+
69+
Parameters
70+
----------
71+
mesh : :attr:`compas_cgal.types.VerticesFaces`
72+
The mesh to create a dual from.
73+
angle_radians : double, optional
74+
Angle limit in radians for boundary vertices to remove.
75+
length_factor : double, optional
76+
Length factor for remeshing.
77+
number_of_iterations : int, optional
78+
Number of remeshing iterations.
79+
scale_factor : double, optional
80+
Scale factor for inner vertices.
81+
fixed_vertices : list[int], optional
82+
List of vertex indices to keep fixed during remeshing.
83+
84+
Returns
85+
-------
86+
tuple
87+
A tuple containing:
88+
89+
- Remeshed mesh vertices as an Nx3 numpy array.
90+
- Remeshed mesh faces as an Mx3 numpy array.
91+
- Dual mesh vertices as an Nx3 numpy array.
92+
- Variable-length faces as a list of lists of vertex indices.
93+
94+
Notes
95+
-----
96+
This dual mesh implementation includes proper boundary handling by:
97+
1. Creating vertices at face centroids of the primal mesh
98+
2. Creating additional vertices at boundary edge midpoints
99+
3. Creating proper connections for boundary edges
100+
101+
"""
102+
V, F = mesh
103+
V = np.asarray(V, dtype=np.float64, order="C")
104+
F = np.asarray(F, dtype=np.int32, order="C")
105+
fixed_vertices = np.asarray(fixed_vertices, dtype=np.int32, order="C")
106+
return _meshing.pmp_trimesh_remesh_dual(V, F, fixed_vertices, length_factor, number_of_iterations, angle_radians, scale_factor)
59107

60108

61109
def project_mesh_on_mesh(
62110
mesh_source: VerticesFaces,
63111
mesh_target: VerticesFaces,
64-
use_normals: bool = True,
65112
) -> VerticesFaces:
66-
"""Project mesh_target vertices onto mesh_source surface.
113+
"""Project mesh_source vertices onto mesh_target surface.
67114
68115
Parameters
69116
----------
70117
mesh_source : :attr:`compas_cgal.types.VerticesFaces`
71-
The mesh to project onto (target surface).
118+
Mesh that is projected onto the target mesh.
72119
mesh_target : :attr:`compas_cgal.types.VerticesFaces`
73-
The mesh whose vertices will be projected (source points).
74-
use_normals : bool, optional
75-
Whether to use normals for ray-based projection. If False, closest point projection is used.
120+
Mesh that is projected onto.
76121
77122
Returns
78123
-------
@@ -81,130 +126,89 @@ def project_mesh_on_mesh(
81126
82127
"""
83128
V_source, F_source = mesh_source.to_vertices_and_faces()
84-
V_target, F_target = mesh_target.to_vertices_and_faces(triangulated=True)
129+
V_source = project_points_on_mesh(V_source, mesh_target)
130+
return Mesh.from_vertices_and_faces(V_source, F_source)
85131

86-
# Convert inputs to numpy arrays
87-
numpy_V_source = np.asarray(V_source, dtype=np.float64, order="C")
88132

89-
numpy_V_target = np.asarray(V_target, dtype=np.float64, order="C")
90-
numpy_F_target = np.asarray(F_target, dtype=np.int32, order="C")
133+
def pull_mesh_on_mesh(
134+
mesh_source: VerticesFaces,
135+
mesh_target: VerticesFaces,
136+
) -> VerticesFaces:
137+
"""Pull mesh_source vertices onto mesh_target surface using mesh_source normals.
91138
92-
# Handle normals calculation
93-
if use_normals:
94-
# Pre-allocate numpy array for normals with correct size
95-
numpy_N_source = np.zeros((mesh_source.number_of_vertices(), 3), dtype=np.float64)
139+
Parameters
140+
----------
141+
mesh_source : :attr:`compas_cgal.types.VerticesFaces`
142+
Mesh that is projected onto the target mesh.
143+
mesh_target : :attr:`compas_cgal.types.VerticesFaces`
144+
Mesh that is projected onto.
96145
97-
# Fill the array with vertex normals
98-
for i, vertex_key in enumerate(mesh_source.vertices()):
99-
normal = -mesh_source.vertex_normal(vertex_key)
100-
numpy_N_source[i, 0] = normal.x
101-
numpy_N_source[i, 1] = normal.y
102-
numpy_N_source[i, 2] = normal.z
103-
else:
104-
# Don't use normals - create empty array
105-
numpy_N_source = np.zeros((0, 0), dtype=np.float64)
146+
Returns
147+
-------
148+
:attr:`compas_cgal.types.VerticesFaces`
149+
The projected mesh (vertices on mesh_source surface, original faces).
106150
107-
# Call the C++ function
108-
_meshing.project(numpy_V_target, numpy_F_target, numpy_V_source, numpy_N_source)
151+
"""
152+
V_source, F_source = mesh_source.to_vertices_and_faces()
153+
N_source = []
154+
for v in mesh_source.vertices():
155+
N_source.append(mesh_source.vertex_normal(v))
109156

110-
return Mesh.from_vertices_and_faces(numpy_V_source, F_source)
157+
V_source = pull_points_on_mesh(V_source, N_source, mesh_target)
158+
return Mesh.from_vertices_and_faces(V_source, F_source)
111159

112160

113161
def project_points_on_mesh(
114162
points: list[list[float]],
115-
mesh_target: VerticesFaces,
116-
normals: Optional[list[list[float]]] = None,
163+
mesh: VerticesFaces,
117164
) -> list[list[float]]:
118-
"""Project points onto mesh_target surface.
165+
"""Project points onto a mesh by closest perpendicular distance.
119166
120167
Parameters
121168
----------
122169
points : list[list[float]]
123170
The points to project.
124-
mesh_target : :attr:`compas_cgal.types.VerticesFaces`
125-
The mesh whose vertices will be projected (source points).
126-
normals : list[list[float]], optional
127-
The normals of the points to project.
171+
mesh : :attr:`compas_cgal.types.VerticesFaces`
172+
Mesh that the points are projected onto.
128173
129174
Returns
130175
-------
131176
list[list[float]]
132-
The projected points (vertices on mesh_target surface).
177+
The projected points (vertices on the mesh surface).
133178
134179
"""
135-
V_target, F_target = mesh_target.to_vertices_and_faces(triangulated=True)
136-
137-
# Convert inputs to numpy arrays
180+
V_target, F_target = mesh.to_vertices_and_faces(triangulated=True)
138181
numpy_V_source = np.asarray(points, dtype=np.float64, order="C")
139-
140182
numpy_V_target = np.asarray(V_target, dtype=np.float64, order="C")
141183
numpy_F_target = np.asarray(F_target, dtype=np.int32, order="C")
142-
143-
# Handle normals calculation
144-
if normals:
145-
# Pre-allocate numpy array for normals with correct size
146-
numpy_N_source = np.zeros((len(points), 3), dtype=np.float64)
147-
148-
# Fill the array with vertex normals
149-
for i, normal in enumerate(normals):
150-
numpy_N_source[i, 0] = normal[0]
151-
numpy_N_source[i, 1] = normal[1]
152-
numpy_N_source[i, 2] = normal[2]
153-
else:
154-
# Don't use normals - create empty array
155-
numpy_N_source = np.zeros((0, 0), dtype=np.float64)
156-
157-
# Call the C++ function
158-
_meshing.project(numpy_V_target, numpy_F_target, numpy_V_source, numpy_N_source)
184+
_meshing.pmp_project(numpy_V_target, numpy_F_target, numpy_V_source)
159185

160186
return numpy_V_source
161187

162188

163-
def remesh_dual(
164-
mesh: VerticesFaces,
165-
length_factor: float = 1.0,
166-
number_of_iterations: int = 10,
167-
angle_radians: float = 0.9,
168-
scale_factor: float = 1.0,
169-
fixed_vertices: list[int] = [],
170-
) -> tuple[np.ndarray, list[list[int]]]:
171-
"""Create a dual mesh from a triangular mesh with variable-length faces.
189+
def pull_points_on_mesh(points: list[list[float]], normals: list[list[float]], mesh: VerticesFaces) -> list[list[float]]:
190+
"""Pull points onto a mesh surface using ray-mesh intersection along normal vectors.
172191
173192
Parameters
174193
----------
194+
points : list[list[float]]
195+
The points to pull.
196+
normals : list[list[float]]
197+
The normal vectors used for directing the projection.
175198
mesh : :attr:`compas_cgal.types.VerticesFaces`
176-
The mesh to create a dual from.
177-
angle_radians : double, optional
178-
Angle limit in radians for boundary vertices to remove.
179-
length_factor : double, optional
180-
Length factor for remeshing.
181-
number_of_iterations : int, optional
182-
Number of remeshing iterations.
183-
scale_factor : double, optional
184-
Scale factor for inner vertices.
185-
fixed_vertices : list[int], optional
186-
List of vertex indices to keep fixed during remeshing.
199+
Mesh that the points are pulled onto.
187200
188201
Returns
189202
-------
190-
tuple
191-
A tuple containing:
192-
193-
- Remeshed mesh vertices as an Nx3 numpy array.
194-
- Remeshed mesh faces as an Mx3 numpy array.
195-
- Dual mesh vertices as an Nx3 numpy array.
196-
- Variable-length faces as a list of lists of vertex indices.
197-
198-
Notes
199-
-----
200-
This dual mesh implementation includes proper boundary handling by:
201-
1. Creating vertices at face centroids of the primal mesh
202-
2. Creating additional vertices at boundary edge midpoints
203-
3. Creating proper connections for boundary edges
203+
list[list[float]]
204+
The pulled points (vertices on the mesh surface).
204205
205206
"""
206-
V, F = mesh
207-
V = np.asarray(V, dtype=np.float64, order="C")
208-
F = np.asarray(F, dtype=np.int32, order="C")
209-
fixed_vertices = np.asarray(fixed_vertices, dtype=np.int32, order="C")
210-
return _meshing.remesh_dual(V, F, fixed_vertices, length_factor, number_of_iterations, angle_radians, scale_factor)
207+
208+
V_target, F_target = mesh.to_vertices_and_faces(triangulated=True)
209+
numpy_V_source = np.asarray(points, dtype=np.float64, order="C")
210+
numpy_N_source = np.asarray(normals, dtype=np.float64, order="C")
211+
numpy_V_target = np.asarray(V_target, dtype=np.float64, order="C")
212+
numpy_F_target = np.asarray(F_target, dtype=np.int32, order="C")
213+
_meshing.pmp_pull(numpy_V_target, numpy_F_target, numpy_V_source, numpy_N_source)
214+
return numpy_V_source

0 commit comments

Comments
 (0)