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;
}
}
}
}