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!

7 Likes

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

If you write this:

# r: compas, compas_cgal

Is there any difference?

1 Like

Yes, that fixed it.

2 Likes

AWESOME, thank you :slight_smile:

2 Likes

You’re welcome. This is very cool!

2 Likes

:sparkler: :fireworks:

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

1 Like

Thank you:)

1 Like

FYI

wood-nano needs a 3.13 build now :smiley:

1 Like

This is an error that I really like!

Thank you

1 Like

RH-81753 is fixed in Rhino WIP

1 Like

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:

1 Like

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