CGAL on Rhino8 ScriptEditor

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 :slight_smile:

@eirannejad ScriptEditor is really great and helps to bring a lot of things, we could not do before, thanks!

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.

If you write this:

# r: compas, compas_cgal

Is there any difference?

Yes, that fixed it.

AWESOME, thank you :slight_smile:

You’re welcome. This is very cool!

:sparkler: :fireworks:

Worked just fine here (Rhino 8 on Windows) :smiley: Thanks for all the work you put into this

Thank you:)

FYI

wood-nano needs a 3.13 build now :smiley:

This is an error that I really like!

Thank you

RH-81753 is fixed in Rhino WIP

Rhino 8 Script Editor. ImportError: cannot import name ‘mesh_remesh’ from ‘compas_cgal.meshing’ I currently have the following problem.

trimesh_remesh

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()

:smiling_face_with_sunglasses:

If you have fails please share .obj files of A and B meshes.