How to cancel a long running operation like BooleanDifference or BooleanUnion in RhinoCommon

It happens that certain operations like BooleanUnion or BooleanDifference run for several hours.

Is there a way to abort the running operation after a certain time in the code?

E.g. by using a Task based CancellationToken (https://docs.microsoft.com/en-us/dotnet/standard/threading/cancellation-in-managed-threads)?

Many thanks in advance.

I’m afraid that’s your best option, no jokes.

1 Like

It works even better if you smash it against the edge of the table… :wink:

// Rolf

I was thinking more of a software solution than the proposed hardware solution… is just too expensive :wink:

2 Likes

Hi @thomas.k,

Have you tried your own suggestion (above)?

– Dale

Create a thread for the computation. Terminate the thread if you need to cancel it mid-way.

Is there anyway to check for a keypress?

Hi @Brenda
In this case it is not possible. The builtin BooleanUnion would hav to check for a keypress.

Hi @dale
Before I burn my fingers, I wanted to ask if anyone has had any experience of what works and what doesn’t.
Is it possible to pass in-memory BREP’s over a thread boundary into a new thread and return the result BREP’s?
I was not sure because I read this post:

I was curious–are you doing Boolean operations on more that 2 objects at once?

Hi @thomas.k,

Before you burn your fingers, perhaps we can have a look at the geometry you are trying to Boolean, and the code you are using to perform this task?

– Dale

Hi @dale
I try to calculate the boolean difference between the extruded building footprint and the roof.

If the first attempt with the default tolerance fails, I try a reduced and an increased tolerance.

The code looks like this:

    public static bool TryCalculateBooleanDifference(List<Brep> substractFrom, List<Brep> substractWith, out List<Brep> result, double tolerance)
    {

        Brep[] difference = Brep.CreateBooleanDifference(substractFrom, substractWith, tolerance);

        if (difference == null || difference.Length == 0)
        {
            _log.Info($"Decreasing tolerance for boolean difference from {tolerance:F4} to {Controller.Instance.DecreasedTolerance:F4}");
            difference = Brep.CreateBooleanDifference(substractFrom, substractWith, Controller.Instance.DecreasedTolerance);

        }

        if (difference != null && difference.Length > 0)
        {
            result = difference.ToList();
            return true;
        }
        else
        {
            result = null;
            return false;
        }
    }

My question is how I can abort the Brep.CreateBooleanDifference() call after a certain time.

The function has no provisions for this, obviously. You’'ll need to cancel thread it.

If you feel the Boolean should work, please post some geometry.

– Dale

I just gave it a shot. And it seems to work!
TaskFactory.StartNew() offers the possibility to pass a CancellationToken
Here is the example code:

    protected override Result RunCommand(RhinoDoc doc, RunMode mode)
    {
        try
        {
            RhinoApp.WriteLine(
                $"Executing the command {EnglishName} on ManagedThreadId={Thread.CurrentThread.ManagedThreadId}. Should be 1 for the UI tread");

            BoundingBox boundingBox1 = new BoundingBox(0, 0, 0, 10, 10, 10);
            BoundingBox boundingBox2 = new BoundingBox(5, 5, 5, 15, 15, 15);

            Brep brep1 = Brep.CreateFromBox(boundingBox1);
            Brep brep2 = Brep.CreateFromBox(boundingBox2);

            List<Brep> breps = new List<Brep>();
            breps.Add(brep1);
            breps.Add(brep2);

            var result = Task<List<Brep>>.Factory.StartNew(() => CalculateBooleanUnionOnDifferentThread(breps), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
            result.Wait();
            foreach (var brep in result.Result)
            {
                doc.Objects.AddBrep(brep);
            }

            doc.Views.Redraw();
            RhinoApp.WriteLine($"The {EnglishName} added {result.Result.Count} brep(s) to the document");

            // ---

            return Result.Success;
        }
        catch (Exception e)
        {
            RhinoApp.WriteLine(e.ToString());
            throw;
        }
    }

    private static List<Brep> CalculateBooleanUnionOnDifferentThread(List<Brep> breps)
    {
        RhinoApp.WriteLine($"Create boolean union for {breps.Count} breps on ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
        return Brep.CreateBooleanUnion(breps, 0.001).ToList();
    }

The output looks like this

Executing the command RhinoThreadingSamplesCommand on ManagedThreadId=1. Should be 1 for the UI tread
Create boolean union for 2 breps on ManagedThreadId=18
The RhinoThreadingSamplesCommand added 1 brep(s) to the document

What I don’t understand: How is it possible that a BREP created via P/Invoke in an umnanaged library can be moved across the thread boundary.
I was of the opinion that only plain old CLR objects (POCO’s) can be passed.

Many thanks for your support!

Here is the example where the task is aborted by the cancellation token. If someone has a better idea than Thread.Abort() input would be welcome.

    protected override Result RunCommand(RhinoDoc doc, RunMode mode)
    {
        try
        {


            // TODO: start here modifying the behaviour of your command.
            // ---
            RhinoApp.WriteLine(
                $"Executing the command {EnglishName} on ManagedThreadId={Thread.CurrentThread.ManagedThreadId}. Should be 1 for the UI tread");

            BoundingBox boundingBox1 = new BoundingBox(0, 0, 0, 10, 10, 10);
            BoundingBox boundingBox2 = new BoundingBox(5, 5, 5, 15, 15, 15);

            Brep brep1 = Brep.CreateFromBox(boundingBox1);
            Brep brep2 = Brep.CreateFromBox(boundingBox2);

            List<Brep> breps = new List<Brep>();
            breps.Add(brep1);
            breps.Add(brep2);

            var cancellationSource = new CancellationTokenSource();
            var cancellationToken = cancellationSource.Token;
            var task = Task<List<Brep>>.Factory.StartNew(() => CalculateBooleanUnionOnDifferentThread(breps, cancellationToken), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

            // wait for a certain time until the task is finished 
            if (task.Wait(1000))
            {
                foreach (var brep in task.Result)
                {
                    doc.Objects.AddBrep(brep);
                }

                doc.Views.Redraw();
                RhinoApp.WriteLine(
                    $"The {EnglishName} added {task.Result.Count} brep(s) to the document.  ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
                return Result.Success;
            }
            else
            {
                // task did not complete execution within the allotted time
                // cancel the thread
                cancellationSource.Cancel();

                //todo: catch the ThreadAbortException
            }

            return Result.Failure;

        }
        catch (Exception e)
        {
            RhinoApp.WriteLine(e.ToString());
            return Result.Failure;
        }
      
    }

    private static List<Brep> CalculateBooleanUnionOnDifferentThread(List<Brep> breps,
        CancellationToken cancellationToken)
    {
        var thread = Thread.CurrentThread;
        RhinoApp.InvokeOnUiThread(new Action(() => RhinoApp.WriteLine($"Create boolean union for {breps.Count} breps on ManagedThreadId={thread.ManagedThreadId}")));

        //Register the cancel action
        cancellationToken.Register(() =>
        {
            RhinoApp.InvokeOnUiThread(new Action(() => RhinoApp.WriteLine($"Aborting the thread.  ManagedThreadId={thread.ManagedThreadId}")));
            // It is not recommended to use Thread.Abort()... but could not find a better solution
            thread.Abort();
        });

        while (true)
        {
            // very long running operation
            // Brep.CreateBooleanUnion(breps, 0.001).ToList();
        }

        return null;
    }

It would be nice if RhinoCommon would support natively the cancellation of operations.
At least for some of the potentially long running operations.

Something like

public static Brep[] CreateBooleanUnion(
    IEnumerable<Brep> breps,
    double tolerance,
    bool manifoldOnly,
    **CancellationToken token**
)