Hi,
I recently made compas_cgal available for the Rhino 8 Script Editor.
It should generally work on most Mac, Windows, and Linux systems (outside Rhino).
Here are some examples: Examples — COMPAS CGAL
A GitHub Action builds this package for Python from 3.9 to 3.13 and uploads it to PyPI.
Therefore, the simple #r: tag should be the only command needed to install it.
I would be grateful if anyone could test whether it works. Here is a sample script for the ScriptEditor:
#! python3
# r: compas, compas_cgal
from compas.geometry import Box
from compas.geometry import Polyhedron
from compas.geometry import Sphere
from compas.datastructures import Mesh
from compas_cgal.booleans import (
boolean_difference_mesh_mesh,
boolean_intersection_mesh_mesh,
boolean_union_mesh_mesh,
split_mesh_mesh,
)
from compas_cgal.meshing import mesh_remesh
from compas.scene import Scene
box = Box(2)
A = box.to_vertices_and_faces(triangulated=True)
sphere = Sphere(1, point=[1, 1, 1])
B = sphere.to_vertices_and_faces(u=64, v=64, triangulated=True)
B = mesh_remesh(B, 0.3, 50)
V, F = boolean_difference_mesh_mesh(A, B)
shape = Polyhedron(V.tolist(), F.tolist())
shape = shape.to_mesh()
scene = Scene()
scene.clear_context()
scene.add(shape)
scene.draw()
It remeshes a sphere and performs mesh boolean-difference:
P.S. if anyone wants to contribute to compas_cgal package or just curious how the binding C++ to Python works, I am happy to share the little details behind this process
@eirannejad ScriptEditor is really great and helps to bring a lot of things, we could not do before, thanks!
7 Likes
diff-arch
(diff-arch)
March 25, 2025, 1:19pm
2
I’ve tested it on macOS with your script below and it doesn’t work. The compas module can’t be found, although my firewall reported a connection to PYPI.
1 Like
diff-arch
(diff-arch)
March 25, 2025, 1:25pm
6
You’re welcome. This is very cool!
2 Likes
eirannejad
(Ehsan Iran-Nejad)
March 25, 2025, 4:38pm
7
Worked just fine here (Rhino 8 on Windows) Thanks for all the work you put into this
1 Like
eirannejad
(Ehsan Iran-Nejad)
May 29, 2025, 11:29pm
9
FYI
Next build of Rhino WIP is going to deploy Python 3.13.3
Why?
Python 3.9 currently shipping with Rhino 8, is going to hit End-of-Life towards the end of 2025.
One of the major wins for having Python 3 in a dotnet app is access to packages that use python C API. if these packages drop support for Python 3.9, then that stops us from getting the more recent package versions inside of Rhino.
Which Python?
Python 3.13. This python is the current stable version and will only be getting security pat…
wood-nano needs a 3.13 build now
1 Like
This is an error that I really like!
Thank you
1 Like
brian
(Brian Gillespie)
June 3, 2025, 10:06pm
11
RH-81753 is fixed in Rhino WIP
1 Like
taraskydon
(Taras Kydon)
January 29, 2026, 2:09pm
12
Petras_Vestartas:
#! python3
# r: compas, compas_cgal
from compas.geometry import Box
from compas.geometry import Polyhedron
from compas.geometry import Sphere
from compas.datastructures import Mesh
from compas_cgal.booleans import (
boolean_difference_mesh_mesh,
boolean_intersection_mesh_mesh,
boolean_union_mesh_mesh,
split_mesh_mesh,
)
from compas_cgal.meshing import mesh_remesh
from compas.scene import Scene
box = Box(2)
A = box.to_vertices_and_faces(triangulated=True)
sphere = Sphere(1, point=[1, 1, 1])
B = sphere.to_vertices_and_faces(u=64, v=64, triangulated=True)
B = mesh_remesh(B, 0.3, 50)
V, F = boolean_difference_mesh_mesh(A, B)
shape = Polyhedron(V.tolist(), F.tolist())
shape = shape.to_mesh()
scene = Scene()
scene.clear_context()
scene.add(shape)
scene.draw()
Rhino 8 Script Editor. ImportError: cannot import name ‘mesh_remesh’ from ‘compas_cgal.meshing’ I currently have the following problem.
taraskydon
(Taras Kydon)
January 29, 2026, 3:23pm
14
import rhinoscriptsyntax as rs
import scriptcontext as sc
from compas.datastructures import Mesh
from compas_cgal.booleans import boolean_difference_mesh_mesh
from compas_rhino.conversions import mesh_to_compas, mesh_to_rhino
import Rhino.Geometry as rg
def clean_rhino_mesh(mesh):
"""
Cleans and optimizes Rhino mesh before boolean operations.
Equivalent to C# CleanRhinoMesh method.
Args:
mesh: Rhino.Geometry.Mesh object
"""
# Weld vertices with tolerance
mesh.Weld(3.14)
# Combine identical vertices
mesh.Vertices.CombineIdentical(True, True)
# Remove unused vertices
mesh.Vertices.CullUnused()
# Unify normal directions
mesh.UnifyNormals()
# Compute face normals
mesh.FaceNormals.ComputeFaceNormals()
# Compute vertex normals
mesh.Normals.ComputeNormals()
# Compact mesh data structure
mesh.Compact()
print(f"Mesh cleaned: {mesh.Vertices.Count} vertices, {mesh.Faces.Count} faces")
def boolean_difference_rhino_meshes():
"""
Performs sequential boolean difference operations between one base mesh and multiple cutters.
First, select the base mesh, then select multiple cutter meshes.
"""
# Крок 1: Вибір основного меша (1 деталь)
mesh_guid_main = rs.GetObject(
"Select the main Mesh (1 object)",
filter=rs.filter.mesh,
preselect=True
)
if not mesh_guid_main:
print("Main mesh not selected")
return
# Крок 2: Вибір кількох мешів-різців (cutters)
mesh_guids_cutters = rs.GetObjects(
"Select Cutter meshes (multiple objects allowed)",
filter=rs.filter.mesh,
preselect=False
)
if not mesh_guids_cutters:
print("No cutters selected")
return
print(f"Selected: 1 main mesh and {len(mesh_guids_cutters)} cutter(s)")
# Крок 3: Підготовка основного меша
print("\n=== Processing main mesh ===")
rhino_mesh_main = rs.coercemesh(mesh_guid_main)
print("Cleaning main mesh...")
clean_rhino_mesh(rhino_mesh_main)
# Робоча копія основного меша
current_result_mesh = rhino_mesh_main.Duplicate()
# Крок 4: Послідовне виконання boolean difference з кожним cutter'ом
try:
for i, cutter_guid in enumerate(mesh_guids_cutters, 1):
print(f"\n=== Processing cutter {i}/{len(mesh_guids_cutters)} ===")
# Отримання cutter меша
rhino_mesh_cutter = rs.coercemesh(cutter_guid)
print(f"Cleaning cutter {i}...")
clean_rhino_mesh(rhino_mesh_cutter)
# Конвертація в COMPAS mesh
compas_mesh_main = mesh_to_compas(current_result_mesh)
compas_mesh_cutter = mesh_to_compas(rhino_mesh_cutter)
# Підготовка даних для CGAL boolean операції
mesh_main_data = compas_mesh_main.to_vertices_and_faces(triangulated=True)
mesh_cutter_data = compas_mesh_cutter.to_vertices_and_faces(triangulated=True)
# Виконання boolean difference
print(f"Performing boolean difference {i}...")
vertices, faces = boolean_difference_mesh_mesh(mesh_main_data, mesh_cutter_data)
# Створення результуючого COMPAS mesh
result_compas_mesh = Mesh.from_vertices_and_faces(vertices, faces)
# Конвертація назад в Rhino mesh
current_result_mesh = mesh_to_rhino(result_compas_mesh)
# Очищення проміжного результату
print(f"Cleaning intermediate result {i}...")
clean_rhino_mesh(current_result_mesh)
print(f"Completed {i}/{len(mesh_guids_cutters)}: {len(vertices)} vertices, {len(faces)} faces")
# Крок 5: Фінальне очищення результату
print("\n=== Final cleaning ===")
clean_rhino_mesh(current_result_mesh)
# Крок 6: Додавання результату в документ Rhino
result_guid = sc.doc.Objects.AddMesh(current_result_mesh)
# Крок 7: Видалення оригінальних мешів (опціонально)
delete_original = rs.MessageBox(
"Remove original meshes?",
buttons=4 | 32
)
if delete_original == 6: # Yes
rs.DeleteObject(mesh_guid_main)
for cutter_guid in mesh_guids_cutters:
rs.DeleteObject(cutter_guid)
# Оновлення відображення
sc.doc.Views.Redraw()
rs.SelectObject(result_guid)
print("\n=== SUCCESS ===")
print(f"Boolean difference successfully completed!")
print(f"Processed 1 main mesh with {len(mesh_guids_cutters)} cutter(s)")
print(f"Final result: {current_result_mesh.Vertices.Count} vertices, {current_result_mesh.Faces.Count} faces")
return result_guid
except Exception as e:
print(f"\n=== ERROR ===")
print(f"Error when performing boolean difference: {e}")
import traceback
traceback.print_exc()
return None
# Запуск функції
if __name__ == "__main__":
boolean_difference_rhino_meshes()
1 Like
If you have fails please share .obj files of A and B meshes.