Skip to content

Commit 05144e7

Browse files
ADD intersections
1 parent c2cb92e commit 05144e7

File tree

8 files changed

+238
-40
lines changed

8 files changed

+238
-40
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,4 @@ endfunction()
227227
# Add new modules here
228228
add_nanobind_module(booleans_ext src/booleans.cpp)
229229
add_nanobind_module(meshing_ext src/meshing.cpp)
230+
add_nanobind_module(intersections_ext src/intersections.cpp)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import profile
2+
3+
from compas.datastructures import Mesh
4+
from compas.geometry import Box
5+
from compas.geometry import Point
6+
from compas.geometry import Polyline
7+
from compas.geometry import Sphere
8+
from compas_viewer import Viewer
9+
from line_profiler import profile
10+
11+
from compas_cgal.intersections import intersection_mesh_mesh
12+
13+
14+
@profile
15+
def main():
16+
# ==============================================================================
17+
# Make a box and a sphere
18+
# ==============================================================================
19+
20+
box = Box(2)
21+
A = box.to_vertices_and_faces(triangulated=True)
22+
23+
sphere = Sphere(1, point=[1, 1, 1])
24+
B = sphere.to_vertices_and_faces(u=32, v=32, triangulated=True)
25+
26+
# ==============================================================================
27+
# Compute the intersections
28+
# ==============================================================================
29+
30+
pointsets = intersection_mesh_mesh(A, B)
31+
32+
# ==============================================================================
33+
# Process output
34+
# ==============================================================================
35+
36+
polylines = []
37+
for points in pointsets:
38+
points = [Point(*point) for point in points]
39+
polyline = Polyline(points)
40+
polylines.append(polyline)
41+
42+
return A, B, polylines
43+
44+
45+
A, B, polylines = main()
46+
47+
# =============================================================================
48+
# Visualize
49+
# ==============================================================================
50+
51+
viewer = Viewer()
52+
53+
viewer.renderer.camera.target = [0, 0, 0]
54+
viewer.renderer.camera.position = [4, -6, 3]
55+
56+
viewer.scene.add(Mesh.from_vertices_and_faces(*A), facecolor=(1.0, 0.0, 0.0), show_points=False)
57+
viewer.scene.add(
58+
Mesh.from_vertices_and_faces(*B),
59+
facecolor=(0.0, 1.0, 0.0),
60+
show_points=False,
61+
opacity=0.3,
62+
)
63+
64+
for polyline in polylines:
65+
viewer.scene.add(
66+
polyline,
67+
linecolor=(0.0, 0.0, 1.0),
68+
lineswidth=3,
69+
pointcolor=(0.0, 0.0, 1.0),
70+
pointsize=20,
71+
show_points=True,
72+
)
73+
74+
viewer.show()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Mesh Intersections
2+
=====================
3+
4+
This example demonstrates how to compute intersections between two triangle meshes using COMPAS CGAL.
5+
6+
Key Features:
7+
8+
* Computing intersection curves between meshes
9+
* Visualizing intersection results
10+
* Handling multiple intersection curves
11+
12+
.. figure:: /_images/example_intersections.png
13+
:figclass: figure
14+
:class: figure-img img-fluid
15+
16+
.. literalinclude:: example_intersections.py
17+
:language: python

src/booleans.cpp

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -104,43 +104,3 @@ NB_MODULE(booleans_ext, m) {
104104
"VB"_a,
105105
"FB"_a);
106106
}
107-
108-
void init_booleans(nb::module_& m) {
109-
auto submodule = m.def_submodule("booleans");
110-
111-
submodule.def(
112-
"boolean_union",
113-
&pmp_boolean_union,
114-
"Boolean Union from triangular mesh vertices and faces.",
115-
"VA"_a,
116-
"FA"_a,
117-
"VB"_a,
118-
"FB"_a);
119-
120-
submodule.def(
121-
"boolean_difference",
122-
&pmp_boolean_difference,
123-
"Boolean Difference from triangular mesh vertices and faces.",
124-
"VA"_a,
125-
"FA"_a,
126-
"VB"_a,
127-
"FB"_a);
128-
129-
submodule.def(
130-
"boolean_intersection",
131-
&pmp_boolean_intersection,
132-
"Boolean Intersection from triangular mesh vertices and faces.",
133-
"VA"_a,
134-
"FA"_a,
135-
"VB"_a,
136-
"FB"_a);
137-
138-
submodule.def(
139-
"split",
140-
&pmp_split,
141-
"Boolean Split from triangular mesh vertices and faces.",
142-
"VA"_a,
143-
"FA"_a,
144-
"VB"_a,
145-
"FB"_a);
146-
}

src/compas_cgal/intersections.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import numpy as np
2+
from compas.plugins import plugin
3+
4+
from compas_cgal import intersections_ext
5+
6+
from .types import PolylinesNumpy
7+
from .types import VerticesFaces
8+
9+
10+
@plugin(category="intersections")
11+
def intersection_mesh_mesh(
12+
A: VerticesFaces,
13+
B: VerticesFaces,
14+
) -> PolylinesNumpy:
15+
"""Compute the intersection of tow meshes.
16+
17+
Parameters
18+
----------
19+
A : :attr:`compas_cgal.types.VerticesFaces`
20+
Mesh A.
21+
B : :attr:`compas_cgal.types.VerticesFaces`
22+
Mesh B.
23+
24+
Returns
25+
-------
26+
:attr:`compas_cgal.types.PolylinesNumpy`
27+
A list of intersection polylines, with each polyline an array of points.
28+
29+
Examples
30+
--------
31+
>>> from compas.geometry import Box, Sphere, Polyline
32+
>>> from compas_cgal.intersections import intersection_mesh_mesh
33+
34+
>>> box = Box(1)
35+
>>> sphere = Sphere(0.5, point=[1, 1, 1])
36+
37+
>>> A = box.to_vertices_and_faces(triangulated=True)
38+
>>> B = sphere.to_vertices_and_faces(u=32, v=32, triangulated=True)
39+
40+
>>> result = intersection_mesh_mesh(A, B)
41+
>>> polylines = [Polyline(points) for points in result]
42+
43+
"""
44+
VA, FA = A
45+
VB, FB = B
46+
VA = np.asarray(VA, dtype=np.float64, order="C")
47+
FA = np.asarray(FA, dtype=np.int32, order="C")
48+
VB = np.asarray(VB, dtype=np.float64, order="C")
49+
FB = np.asarray(FB, dtype=np.int32, order="C")
50+
51+
pointsets: intersections_ext.VectorRowMatrixXd = intersections_ext.intersection_mesh_mesh(VA, FA, VB, FB)
52+
53+
return pointsets

src/intersections.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#include "intersections.h"
2+
3+
std::vector<compas::RowMatrixXd>
4+
pmp_intersection_mesh_mesh(
5+
Eigen::Ref<const compas::RowMatrixXd> vertices_a,
6+
Eigen::Ref<const compas::RowMatrixXi> faces_a,
7+
Eigen::Ref<const compas::RowMatrixXd> vertices_b,
8+
Eigen::Ref<const compas::RowMatrixXi> faces_b)
9+
{
10+
compas::Mesh mesh_a = compas::mesh_from_vertices_and_faces(vertices_a, faces_a);
11+
compas::Mesh mesh_b = compas::mesh_from_vertices_and_faces(vertices_b, faces_b);
12+
13+
compas::Polylines polylines;
14+
CGAL::Polygon_mesh_processing::surface_intersection(mesh_a, mesh_b, std::back_inserter(polylines));
15+
16+
std::vector<compas::RowMatrixXd> result = compas::polylines_to_lists_of_points(polylines);
17+
return result;
18+
}
19+
20+
NB_MODULE(intersections_ext, m) {
21+
22+
nb::bind_vector<std::vector<compas::RowMatrixXd>>(m, "VectorRowMatrixXd"); // Be aware that both Pybind11 and Nanobind makes copy for vectors
23+
24+
m.def(
25+
"intersection_mesh_mesh",
26+
&pmp_intersection_mesh_mesh,
27+
"Compute intersection polylines between two triangle meshes.",
28+
"vertices_a"_a,
29+
"faces_a"_a,
30+
"vertices_b"_a,
31+
"faces_b"_a);
32+
}

src/intersections.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
3+
#include "compas.h"
4+
5+
// CGAL intersection
6+
#include <CGAL/Polygon_mesh_processing/intersection.h>
7+
8+
/**
9+
* Compute intersection between two triangle meshes.
10+
*
11+
* @param vertices_a Vertices of mesh A as Nx3 matrix in row-major order (float64)
12+
* @param faces_a Faces of mesh A as Mx3 matrix of vertex indices in row-major order (int32)
13+
* @param vertices_b Vertices of mesh B as Px3 matrix in row-major order (float64)
14+
* @param faces_b Faces of mesh B as Qx3 matrix of vertex indices in row-major order (int32)
15+
* @return std::vector<RowMatrixXd> containing:
16+
* - List of intersection polylines, each as Rx3 matrix of points (float64)
17+
* @note Input meshes must be manifold and closed
18+
*/
19+
std::vector<compas::RowMatrixXd>
20+
pmp_intersection_mesh_mesh(
21+
Eigen::Ref<const compas::RowMatrixXd> vertices_a,
22+
Eigen::Ref<const compas::RowMatrixXi> faces_a,
23+
Eigen::Ref<const compas::RowMatrixXd> vertices_b,
24+
Eigen::Ref<const compas::RowMatrixXi> faces_b);

tests/test_intersections.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import pytest
2+
import numpy as np
3+
4+
from compas.geometry import Box, Sphere
5+
from compas_cgal.intersections import intersection_mesh_mesh
6+
7+
from compas_cgal.intersections_ext import VectorRowMatrixXd
8+
9+
10+
@pytest.fixture
11+
def box_sphere_meshes():
12+
"""Create test meshes: a box and a sphere that intersect."""
13+
box = Box(2) # 2x2x2 box centered at origin
14+
box_mesh = box.to_vertices_and_faces(triangulated=True)
15+
16+
sphere = Sphere(1, point=[1, 1, 1]) # Sphere radius 1, centered at [1,1,1]
17+
sphere_mesh = sphere.to_vertices_and_faces(u=32, v=32, triangulated=True)
18+
19+
return box_mesh, sphere_mesh
20+
21+
22+
def test_intersection_mesh_mesh(box_sphere_meshes):
23+
"""Test mesh-mesh intersection computation."""
24+
mesh_a, mesh_b = box_sphere_meshes
25+
26+
# Compute intersections
27+
pointsets = intersection_mesh_mesh(mesh_a, mesh_b)
28+
29+
# Basic validation
30+
assert isinstance(pointsets, VectorRowMatrixXd), "Result should be a nanobind type"
31+
assert len(pointsets) > 0, "Should find at least one intersection curve"
32+
33+
# Validate each pointset
34+
for points in pointsets:
35+
assert isinstance(points, np.ndarray), "Each pointset should be a numpy array"
36+
assert points.shape[1] == 3, "Points should be 3D"
37+
assert len(points) > 1, "Each intersection curve should have multiple points"

0 commit comments

Comments
 (0)