Using Tasks within Grasshopper Components

Hi,

I am am trying to run heavy boolean in a separate thread without freezing grasshopper:

System.Threading.Tasks.Task.Run(() => { ... });

The issue is described in the following screen capture.

  1. The code runs successfully.
  2. But component does not know that the method is and when it is finished. So I need a) to move grasshopper canvas b) manually rewire grasshopper component output wires.

Is there a way to ouput data once the task is finished without this manual step?

Your code needs to be made aware of the Task process and do something once the Tasks completes/fails/cancels. Usually that means writing an async method, but that can be a problem if your code runs within an existing api. You can also just start a timer which checks every 500 milliseconds or so what the state of the Task is, and then takes appropriate action. You’ll need to transfer your data into some sort of class level field, expire your component, start a new solution and inside that solution your component must be smart enough to know whether it’s supposed to begin calculating a new set of breps or whether to copy the class level results into the output.

Is there any possibility to show how to do it? Especially for Async method.

For me these tasks are a bit of black box, and how data can be transferred outside them.
But I really need them due to very slow process of these methods.

For now it only contains this for loop:

                    for (int i = 0; i < dtLoftCurvesElements.BranchCount; i++) {
                        var path = dtLoftCurvesElements.BranchCount == 1 ? new GH_Path(0) : dtLoftCurvesElements.Paths[i];
                        bool nothing = true;
                        if (dtLoftCurvesCutters.PathExists(path)) {
                            if (dtLoftCurvesElements.Branch(path).Count > 0 && dtLoftCurvesCutters.Branch(path).Count > 0) {
                                if (dtLoftCurvesElements.Branch(path).Count == 0) continue;
                                var a = dtLoftCurvesElements.Branch(path)[0];
                                var b = dtLoftCurvesCutters.Branch(path);
                                dtLoftCurvesResult.Add(BooleanDiffBrep(a, b, true), path);
                                nothing = false;
                            }
                        }
                        if (nothing) {
                            dtLoftCurvesResult.Add(dtLoftCurvesElements.Branch(path)[0], path);
                        }
                    }

Colander.gh (6.8 KB)

This is not great, but there doesn’t seem to be a way to have a cancelable solid difference operation in Rhino, so once you start calculating you have to finish. That means that if you drag the slider from 5.000 to 1.000, you’ll end up calculating hundreds of in between values.

I’m also not 100% sure this is the best way to approach this, I feel like there must be a cleaner way.

Thank you very much.
Just one question, in the screen capture below I have high resolution BReps, outputing geometry takes some time. You can notice by seeing how much time is passed after geometry is printed in the panel and the actual preview. If I hide preview of the component there is no lag. Is there is a way to have a faster preview?

This is implemented in my code (the name should be BrepBooleanDiff):

MeshBooleanDiff.cs (22.3 KB)

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using NGonsCore;
using Rhino.Geometry;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;

namespace NGonGh.UtilMesh {
    public class MeshBooleanDiff : GH_Component {

        private DataTree<Brep> dtLoftCurvesElements=new DataTree<Brep>();
        private DataTree<Brep> dtLoftCurvesCutters = new DataTree<Brep>();
        private DataTree<Brep> _results;
        private Task<DataTree<Brep>> _task;
        private bool _startNewTask = true;
        private Timer _timer;
        private int counter = 0;

        private void Tick(object state) {
            _timer.Change(Timeout.Infinite, Timeout.Infinite);
            counter++;

            Task<DataTree<Brep>> task = _task;
            if (task == null)
                return;

            if (task.IsFaulted) {
                _task = null;
                _results = null;
                _startNewTask = false;

                Rhino.RhinoApp.WriteLine("Task Failed");
                Rhino.RhinoApp.InvokeOnUiThread(new Action<bool>(this.ExpireSolution), new object[] { true });
                return;
            }

            if (task.IsCompleted) {
                _results = task.Result;
                _startNewTask = false;
                Rhino.RhinoApp.WriteLine("Task Completed");
                Rhino.RhinoApp.InvokeOnUiThread(new Action<bool>(this.ExpireSolution), new object[] { true });
                return;
            }

            // Task is still busy computing, wait for the next timer tick.
            Rhino.RhinoApp.WriteLine("Timer Delayed");
            _timer.Change(250, Timeout.Infinite);
        }

        private static Task<DataTree<Brep>> StartTask(DataTree<Brep> dtLoftCurvesElements, DataTree<Brep> dtLoftCurvesCutters) {//Brep shape, double spacing
            Rhino.RhinoApp.WriteLine("Task Started");
            var factory = new TaskFactory<DataTree<Brep>>();
            //return factory.StartNew(() => PunchHoles(shape, spacing));
            return factory.StartNew(() => BrepBooleanDataTree(dtLoftCurvesElements,dtLoftCurvesCutters));
        }
        private static Brep[] PunchHoles(Brep shape, double spacing) {
            var box = shape.GetBoundingBox(true);
            var cylinders = new List<Brep>();
            for (var x = box.Min.X; x < box.Max.X; x += spacing)
                for (var y = box.Min.Y; y < box.Max.Y; y += spacing) {
                    var cir = new Circle(spacing * 0.4);
                    cir.Center = new Point3d(x, y, box.Min.Z - 1);
                    var cyl = new Cylinder(cir, box.Diagonal.Z + 2);
                    cylinders.Add(cyl.ToBrep(true, true));
                }

            return Brep.CreateBooleanDifference(new Brep[] { shape }, cylinders, 0.01);
        }

        private static DataTree<Brep> BrepBooleanDataTree(DataTree<Brep> dtLoftCurvesElements, DataTree<Brep> dtLoftCurvesCutters) {

            DataTree<Brep> dtLoftCurvesResult = new DataTree<Brep>();

            if (dtLoftCurvesElements.DataCount == 0 || dtLoftCurvesCutters.DataCount == 0)
                return dtLoftCurvesResult;

            if (dtLoftCurvesElements.BranchCount == 1) {
                dtLoftCurvesResult.Add(BooleanDiffBrep(dtLoftCurvesElements.AllData()[0], dtLoftCurvesCutters.AllData(), true), new GH_Path(0));
            } else {

                for (int i = 0; i < dtLoftCurvesElements.BranchCount; i++) {
                    var path = dtLoftCurvesElements.BranchCount == 1 ? new GH_Path(0) : dtLoftCurvesElements.Paths[i];
                    bool nothing = true;
                    if (dtLoftCurvesCutters.PathExists(path)) {
                        if (dtLoftCurvesElements.Branch(path).Count > 0 && dtLoftCurvesCutters.Branch(path).Count > 0) {
                            if (dtLoftCurvesElements.Branch(path).Count == 0) continue;
                            var a = dtLoftCurvesElements.Branch(path)[0];
                            var b = dtLoftCurvesCutters.Branch(path);
                            dtLoftCurvesResult.Add(BooleanDiffBrep(a, b, true), path);
                            nothing = false;
                        }
                    }
                    if (nothing) {
                        dtLoftCurvesResult.Add(dtLoftCurvesElements.Branch(path)[0], path);
                    }
                }
            }

            return dtLoftCurvesResult;
        }
        ////Output
        //DA.SetDataTree(0, dtLoftCurvesElements);
        //DA.SetDataTree(1, dtLoftCurvesCutters);
        //DA.SetDataTree(2, dtLoftCurvesResult);








        public static Brep LargestBrep(Brep[] diff) {

            if (diff == null) return null;
            if (diff.Length == 0) return null;

            /////////////////////////////////////////////////////////////
            ///Take largest from boolean split
            /////////////////////////////////////////////////////////////
            double len = 0;
            int largestElementID = -1;

            for (int i = 0; i < diff.Length; i++) {

                if (diff[i] == null) continue;
                if (!diff[i].IsValid) continue;

                double lenCurrent = diff[i].GetBoundingBox(true).Diagonal.Length;
                if (lenCurrent > len) {
                    len = lenCurrent;
                    largestElementID = i;
                }
            }

            if (largestElementID == -1)
                return null;
            else
                return diff[largestElementID];

            /////////////////////////////////////////////////////////////


        }

        public static Brep BooleanDiffBrep(Brep input, List<Brep> cutters, bool unionCutters) {


            /////////////////////////////////////////////////////////////
            ///Input
            /////////////////////////////////////////////////////////////
            double t = Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance;
            if (input == null) return input;
            if (!input.IsValid) return input;
            var result = input;//.DuplicateBrep();


            var cuttersCleaned = new List<Brep>(cutters.Count);
            bool openBreps = false;
            for (int i = 0; i < cutters.Count; i++) {
                if (cutters[i] == null) continue;
                if (!cutters[i].IsValid) continue;
                Brep brep = cutters[i];//.DuplicateBrep();
                cuttersCleaned.Add(brep);
                if (!openBreps) {
                    openBreps = brep.IsSolid == false;
                }
            }
            if (cuttersCleaned.Count == 0) return input;
            /////////////////////////////////////////////////////////////
            ///




            //if (!openBreps) {


            //}


            /////////////////////////////////////////////////////////////
            ///If boolean difference fails, the split objects one by one and take largest
            /////////////////////////////////////////////////////////////


            Brep resultCopy2 = result.DuplicateBrep();
            foreach (Brep b_ in cuttersCleaned) {

                Brep b = b_.DuplicateBrep();
                BoundingBox bbox = b.GetBoundingBox(true);
                bbox.Union(resultCopy2.GetBoundingBox(true));
                var xForm = Rhino.Geometry.Transform.PlaneToPlane(new Plane(bbox.Center, Vector3d.ZAxis), Plane.WorldXY);
                var xFormInv = Rhino.Geometry.Transform.PlaneToPlane(Plane.WorldXY, new Plane(bbox.Center, Vector3d.ZAxis));
                b.Transform(xForm);
                resultCopy2.Transform(xForm);


                Brep[] split = Brep.CreateBooleanSplit(resultCopy2, b, t);//Rhino 6 Recent option

                Brep tempResult = LargestBrep(split);
                if (tempResult != null) {
                    tempResult.Transform(xFormInv);
                    resultCopy2 = tempResult;
                } else {
                    resultCopy2.Transform(xFormInv);
                }
            }


            if (resultCopy2 != null)
                if (resultCopy2.IsValid)
                    return resultCopy2;

                //If boolean split failed
                else {
                    /////////////////////////////////////////////////////////////
                    ///Union Cutters
                    /////////////////////////////////////////////////////////////
                    Brep[] cuttersUnion = new Brep[0];

                    if (unionCutters)
                        cuttersUnion = Brep.CreateBooleanUnion(cuttersCleaned, t);

                    if (cuttersUnion == null)
                        cuttersUnion = cuttersCleaned.ToArray();

                    if (cuttersUnion == null) return input;
                    /////////////////////////////////////////////////////////////




                    /////////////////////////////////////////////////////////////
                    ///Try to perform boolean difference
                    /////////////////////////////////////////////////////////////


                    //All
                    Brep[] diffAll = Brep.CreateBooleanDifference(new Brep[] { result }, cuttersUnion, t);

                    if (diffAll != null)
                        if (diffAll.Length > 0)
                            if (diffAll[0].IsValid)
                                return diffAll[0];


                    //If All fails, then one-by-one
                    Brep resultCopy = result.DuplicateBrep();
                    foreach (Brep b in cuttersUnion) {
                        Brep[] diff = Brep.CreateBooleanDifference(resultCopy, b, t);
                        Brep tempResult = LargestBrep(diff);
                        if (tempResult != null)
                            resultCopy = tempResult;
                    }
                    if (resultCopy != null)
                        if (resultCopy.IsValid)
                            return resultCopy;

                    /////////////////////////////////////////////////////////////
                }



            return input;



        }

        public DataTree<Brep> LoftCurvesBrep(GH_Structure<GH_Curve> Elements, GH_Structure<GH_Number> Radius) {

            DataTree<Brep> dtLoftCurvesBrep = new DataTree<Brep>();

            for (int i = 0; i < Elements.Branches.Count; i++) {

                GH_Path path = Elements.Paths[i];
                List<Curve> openC = new List<Curve>();
                List<Curve> closC = new List<Curve>();

                for (int j = 0; j < Elements[i].Count; j++) {
                    Curve c = Elements[i][j].Value;
                    if (!c.IsClosed) {
                        openC.Add(c);
                    } else {
                        closC.Add(c);
                    }
                }

                closC.AddRange(CurveUtil.LinesToCircles(openC, 8, (Elements.Branches.Count == Radius.Branches.Count ? Radius[path][0].Value : 10)));


                for (int j = 0; j < closC.Count; j += 2) {
                    Brep brep = BrepUtil.Loft(Elements[i][j].Value, Elements[i][j + 1].Value, true);
                    dtLoftCurvesBrep.Add(brep, path);
                }
            }
            return dtLoftCurvesBrep;
        }


        public DataTree<Mesh> LoftCurvesMesh(GH_Structure<GH_Curve> Elements, GH_Structure<GH_Number> Radius, bool merge) {


            DataTree<Mesh> dtLoftCurvesMesh = new DataTree<Mesh>();

            for (int i = 0; i < Elements.Branches.Count; i++) {

                GH_Path path = Elements.Paths[i];
                List<Curve> openC = new List<Curve>();
                List<Curve> closC = new List<Curve>();

                for (int j = 0; j < Elements[i].Count; j++) {
                    Curve c = Elements[i][j].Value;
                    if (!c.IsClosed) {
                        openC.Add(c);
                    } else {
                        closC.Add(c);
                    }
                }

                closC.AddRange(CurveUtil.LinesToCircles(openC, 8, (Elements.Branches.Count == Radius.Branches.Count ? Radius[path][0].Value : 10)));

                Mesh meshJoined = new Mesh();

                for (int j = 0; j < closC.Count; j += 2) {


                    int divisions = -1;
                    var meshes = NGonsCore.MeshCreate.MeshLoftMultiple(new List<Curve> { closC[j], closC[j + 1] }, 1, 0, ref divisions, 1E10, false);



                    if (meshes != null)
                        if (meshes.Count > 0) {

                            meshes[0].FillHoles();
                            meshes[0] = meshes[0].WeldUsingRTree(0.01, false);


                            meshes[0].Compact();
                            meshes[0].Vertices.CombineIdentical(true, true);
                            meshes[0].Vertices.CullUnused();
                            meshes[0].Ngons.Clear();


                            meshes[0].Weld(3.14159265358979);
                            meshes[0].FaceNormals.ComputeFaceNormals();
                            meshes[0].Normals.ComputeNormals();



                            if (meshes[0].SolidOrientation() == -1)
                                meshes[0].Flip(true, true, true);

                            if (merge) {
                                if (meshes[0].IsValid)
                                    meshJoined.Append(meshes[0]);
                            } else {
                                dtLoftCurvesMesh.Add(meshes[0], path);
                            }


                        }

                }

                if (merge)
                    dtLoftCurvesMesh.Add(meshJoined, path);

            }



            return dtLoftCurvesMesh;

        }













        #region UI
        public MeshBooleanDiff()
          : base("MeshBooleanDiff", "Nickname",
              "Description",
              "NGon", "Utilities Mesh") {
        }



        protected override System.Drawing.Bitmap Icon {
            get {
                return Properties.Resources.BooleanDiff;
            }
        }


        public override Guid ComponentGuid {
            get { return new Guid("3794a363-47e6-4c48-a499-c57a72c5424e"); }
        }
        #endregion

        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager) {
            pManager.AddGeometryParameter("Elements", "Elements", "Elements to substract from", GH_ParamAccess.tree);
            pManager.AddGeometryParameter("Cutters", "Cutters", "If a polyline is not closed it is treated as a pipe", GH_ParamAccess.tree);
            pManager.AddNumberParameter("DrillRadius", "DrillRadius", "Drill Radius", GH_ParamAccess.tree, 5);
            pManager.AddBooleanParameter("B/M", "Brep/Mesh", "Brep or Mesh", 0, true);
            pManager.AddBooleanParameter("Run", "Run", "Run", GH_ParamAccess.item, false);
            pManager.AddBooleanParameter("Init", "Init", "Init", GH_ParamAccess.item, false);

            pManager[2].Optional = (true);
            pManager[3].Optional = (true);
            pManager[4].Optional = (true);
            pManager[5].Optional = (true);

        }


        protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager) {
            pManager.AddGeometryParameter("Elements", "Elements", "Elements to substract from", GH_ParamAccess.tree);
            pManager.AddGeometryParameter("Cutter", "Cutter", "If a polyline is not closed it is treated as a pipe", GH_ParamAccess.tree);
            pManager.AddGeometryParameter("Result", "Result", "Result", GH_ParamAccess.tree);
            pManager.HideParameter(0);
            pManager.HideParameter(1);

        }


        protected override void SolveInstance(IGH_DataAccess DA) {

            bool Run = false;
            DA.GetData<bool>(4, ref Run);
            bool init = false;
            DA.GetData(5, ref init);

            if(Run) {

                if (counter == 0 || init ) {
               
                    //counter++;
                    //Solution
                    try {

                        //Input
                        DA.GetDataTree<IGH_GeometricGoo>(0, out GH_Structure<IGH_GeometricGoo> Elements_);
                        DA.GetDataTree<IGH_GeometricGoo>(1, out GH_Structure<IGH_GeometricGoo> Cutters_);
                        //DA.GetDataTree<GH_Curve>(1, out GH_Structure<GH_Curve>  Cutters);
                        DA.GetDataTree<GH_Number>(2, out GH_Structure<GH_Number> Radius);
                        bool BrepOrMesh = true;
                        DA.GetData<bool>(3, ref BrepOrMesh);


                        Elements_.Simplify(GH_SimplificationMode.CollapseAllOverlaps);
                        Cutters_.Simplify(GH_SimplificationMode.CollapseAllOverlaps);

                        //Get Elements
                        DataTree<Brep> dtBrepElements = new DataTree<Brep>();
                        DataTree<Mesh> dtMeshElements = new DataTree<Mesh>();
                        GH_Structure<GH_Curve> dtCurveElements = new GH_Structure<GH_Curve>();

                        for (int i = 0; i < Elements_.PathCount; i++) {

                            for (int j = 0; j < Elements_[i].Count; j++) {
                                if (Elements_[i][j].TypeName == "Curve") {

                                    //Rhino.RhinoApp.WriteLine(Elements_[i][j].TypeName);
                                    Elements_[i][j].CastTo<Curve>(out Curve b);
                                    dtCurveElements.Append(new GH_Curve(b), Elements_.Paths[i]);
                                } else if (Elements_[i][j].TypeName == "Brep") {
                                    Elements_[i][j].CastTo<Brep>(out Brep b);
                                    dtBrepElements.Add(b, Elements_.Paths[i]);
                                    //Rhino.RhinoApp.WriteLine(Elements_[i][j].TypeName);
                                } else if (Elements_[i][j].TypeName == "Mesh") {
                                    Elements_[i][j].CastTo<Mesh>(out Mesh b);
                                    dtMeshElements.Add(b, Elements_.Paths[i]);
                                    //Rhino.RhinoApp.WriteLine(Elements_[i][j].TypeName);
                                }



                            }
                        }

                        //Get Cutters
                        DataTree<Brep> dtBrepCutters = new DataTree<Brep>();
                        DataTree<Mesh> dtMeshCutters = new DataTree<Mesh>();
                        GH_Structure<GH_Curve> dtCurveCutters = new GH_Structure<GH_Curve>();

                        for (int i = 0; i < Cutters_.PathCount; i++) {

                            for (int j = 0; j < Cutters_[i].Count; j++) {
                                if (Cutters_[i][j].TypeName == "Curve") {
                                    Cutters_[i][j].CastTo<Curve>(out Curve b);
                                    dtCurveElements.Append(new GH_Curve(b), Cutters_.Paths[i]);
                                    //Rhino.RhinoApp.WriteLine(Elements_[i][j].TypeName);
                                } else if (Cutters_[i][j].TypeName == "Brep") {
                                    Cutters_[i][j].CastTo<Brep>(out Brep b);
                                    dtBrepCutters.Add(b, Cutters_.Paths[i]);
                                    //Rhino.RhinoApp.WriteLine(Elements_[i][j].TypeName);
                                } else if (Cutters_[i][j].TypeName == "Mesh") {
                                    Cutters_[i][j].CastTo<Mesh>(out Mesh b);
                                    dtMeshCutters.Add(b, Cutters_.Paths[i]);
                                    //Rhino.RhinoApp.WriteLine(Elements_[i][j].TypeName);
                                }



                            }
                        }


                        //Rhino.RhinoApp.WriteLine(dtCurveElements.DataCount.ToString());

                        Rhino.RhinoApp.WriteLine("Init");
                            
                        dtLoftCurvesElements = dtBrepElements.DataCount > 0 ? dtBrepElements : this.LoftCurvesBrep(dtCurveElements, Radius);
                        dtLoftCurvesCutters = dtBrepCutters.DataCount > 0 ? dtBrepCutters : this.LoftCurvesBrep(dtCurveElements, Radius);
                        _results = new DataTree<Brep>();
                        Rhino.RhinoApp.WriteLine("EndInit");


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

                ////var dt = BrepBooleanDataTree(dtLoftCurvesElements, dtLoftCurvesCutters);
                //Tasks

                Rhino.RhinoApp.WriteLine("Counter:"+counter.ToString());
                Rhino.RhinoApp.WriteLine("Number of elements:"+ dtLoftCurvesElements.AllData().Count.ToString());
                Rhino.RhinoApp.WriteLine("Number of cutters:"+dtLoftCurvesCutters.AllData().Count.ToString());
                Rhino.RhinoApp.WriteLine("Number of results:" + _results.AllData().Count.ToString());
                if (_timer == null)
                    _timer = new Timer(new TimerCallback(Tick));



                ////if (Shape == null) return;
                ////if (Spacing <= 1e-3) return;

                //// We have data to assign. This data may be old, but we can assign it nonetheless.
                if (_results != null) {
                    DA.SetDataTree(0, dtLoftCurvesElements);
                    DA.SetDataTree(1, dtLoftCurvesCutters);
                    DA.SetDataTree(2, _results);
                }


                //// If the _startNewTask field is true, that means the inputs were changed.
                if (_task == null || _startNewTask) {
                    _task = null;
                     //_task = StartTask(Shape, Spacing);
                    _task = StartTask(dtLoftCurvesElements, dtLoftCurvesCutters);
                   _timer.Change(250, Timeout.Infinite);
                }

                _startNewTask = true;
            }






        }





    }


}

I fixed the preview, there was an issue with too high res input geometry.

1 Like

the kind folks at speckle released an open source pattern for asynchronous components: https://speckle.systems/blog/async-gh/

3 Likes

Thanks I will try it as well.
Jank is probably the best word to describe the UI thread lock;)
And it is great to see that speckle did this.
I am wondering what David Rutten thinks about such implementation and what are the possible issues.