using System.IO;
using System.Linq;
using System.Threading.Tasks;
private void RunScript(List<Curve> curve, ref object A)
{
System.Threading.Tasks.Parallel.For(0, curve.Count, i =>
// System.Threading.Tasks.Parallel.ForEach(curve, i =>
{
for (int j = curve.Count - 1;j > i; j--){
var b = Curve.GeometryEquals(curve[i], curve[j]);
if(b)
curve.RemoveAt(j);
}
});
A = curve;
}
Think of your list of curves as a deck of cards. You and your friends are trying to remove any duplicate cards from the deck.
In your original code, you all start removing cards at the same time. But this causes a problem. Imagine if you’re looking at a card and at the same time, one of your friends removes a card from the deck. Now, the deck has changed and you might lose track of which card you were looking at. This is why your original code was giving an error. I did not check wether it will work in terms of logic or not, but I doubt It will.
Anyway to solve this specific threading issue you must use ConcurrentBag.
private void RunScript(List<Curve> curve, ref object A)
{
var distinctCurves = new ConcurrentBag<Curve>();
System.Threading.Tasks.Parallel.ForEach(curve, currentCurve =>
{
if (!distinctCurves.Any(c => Curve.GeometryEquals(c, currentCurve)))
{
distinctCurves.Add(currentCurve);
}
});
A = distinctCurves.ToList();
}
Keep in mind this code is still not thread safe the Any method and the Add method are two separate operations. During parallel processing it’s possible for another thread to add an equivalent curve to distinctCurves after the Any check but before the Add method is called.
This could result in duplicate curves in distinctCurves .
Generally I avoid parallel computing in Rhino unless It is absolutely necessary, which for me means never.
" Keep in mind this code is still not thread safe the Any method and the Add method are two separate operations. During parallel processing it’s possible for another thread to add an equivalent curve to distinctCurves after the Any check but before the Add method is called.
This could result in duplicate curves in distinctCurves "
Thanks @Brian_Washburn and @Brian_Washburn
its useful i try to find best and faster way to remove duplicate curves and if enyone better suggestions share it
And i use this method that forLine is ok (and very fast)but for Curves not work:
This:
For Line this code is faster than Kangaroo components for remove dup Line but befor we must flip all normal Line to a guide Vector
using System.Collections.Generic;
using System.Linq;
// Define a custom equality comparer for Line objects
public class LineEqualityComparer : IEqualityComparer<Line>
{
public bool Equals(Line x, Line y)
{
// Compare the start and end points of the lines
return x.From.Equals(y.From) && x.To.Equals(y.To);
}
public int GetHashCode(Line obj)
{
// Generate a hash code based on the start and end points
return obj.From.GetHashCode() ^ obj.To.GetHashCode();
}
}
// Remove duplicate lines from the list
List<Line> uniqueLines = lines.Distinct(new LineEqualityComparer()).ToList();
And this Code for Curves don’t result!
And please suggestions for this code to work!
using System.Collections.Generic;
using Rhino.Geometry;
using System.Collections.Generic;
using Rhino.Geometry;
// Input list of curves
List<Curve> curveList = new List<Curve>(); // Add your list of curves here
// Create a HashSet to store unique curves
HashSet<Curve> uniqueCurves = new HashSet<Curve>();
// Iterate through the curve list and add unique curves to the HashSet
for (int i = 0; i < curveList.Count; i++)
{
Curve currentCurve = curveList[i];
// Check if the curve is unique
if (!uniqueCurves.Contains(currentCurve))
{
// Add the curve to the HashSet
uniqueCurves.Add(currentCurve);
}
}
// Convert the HashSet back to a list of curves
List<Curve> uniqueCurveList = new List<Curve>(uniqueCurves);
Hi @Rh-3d-p
I wasn’t familiar with HashSets prior to this post, but I asked ChatGPT for some help and got a functional result. It happens to be very close to the code posted by @RadovanG here:
The initial code by ChatGPT is this:
HashSet Check
private void RunScript(List<Curve> curves, ref object A)
{
A = CurveUtility.RemoveDuplicateCurves(curves);
}
// <Custom additional code>
public static class CurveUtility
{
public static List<Curve> RemoveDuplicateCurves(List<Curve> curves)
{
var uniqueCurves = new List<Curve>();
var hashSet = new HashSet<uint>();
foreach (var curve in curves)
{
// Reverse curve direction where necessary
if (curve.PointAtStart > curve.PointAtEnd)
curve.Reverse();
var hash = CreateCurveHashCodeX3(curve.ToNurbsCurve(), 101);
if (!hashSet.Contains(hash))
{
hashSet.Add(hash);
uniqueCurves.Add(curve);
}
}
return uniqueCurves;
}
private static uint CreateCurveHashCodeX3(NurbsCurve crv, int salt)
{
int myHash = 0;
unchecked
{
///Comment or uncomment myHash for varing levels of duplication check
for (int i = 0; i < crv.Points.Count; i++)
{
myHash ^= saltHash(crv.Points[i].Location.GetHashCode(), salt) * (31 * i + 1);
myHash ^= saltHash(crv.Points[i].Weight.GetHashCode(), salt) * (97 * i + 1);
}
for (int i = 0; i < crv.Knots.Count; i++)
{
//myHash ^= crv.Knots[i].GetHashCode() * (2 * i + salt);
}
myHash ^= crv.Degree * 1559;
myHash ^= crv.Dimension * 2029;
//myHash ^= saltHash(crv.Domain.GetHashCode(), salt) * 1699;
myHash ^= saltHash(crv.IsClosed.GetHashCode(), salt) * 4099;
myHash ^= saltHash(crv.IsPeriodic.GetHashCode(), salt) * 4447;
myHash ^= saltHash(crv.IsValid.GetHashCode(), salt) * 5557;
myHash ^= crv.SpanCount * 9199;
}
return (uint) myHash;
}
private static int saltHash(int hash, int divisor)
{
int remainder;
Math.DivRem(hash, divisor, out remainder);
return hash + remainder;
}
}
And when I asked for a multithreaded version it looked like this:
Threaded HashSet Check
private void RunScript(List<Curve> curves, ref object A)
{
A = CurveUtility.RemoveDuplicateCurves(curves);
}
// <Custom additional code>
public static class CurveUtility
{
public static List<Curve> RemoveDuplicateCurves(List<Curve> curves)
{
var uniqueCurves = new List<Curve>();
var hashSet = new HashSet<uint>();
Parallel.ForEach(curves, curve =>
{
// Reverse curve direction where necessary
if (curve.PointAtStart > curve.PointAtEnd)
curve.Reverse();
var hash = CreateCurveHashCodeX3(curve.ToNurbsCurve(), 101);
lock (hashSet)
{
if (!hashSet.Contains(hash))
{
hashSet.Add(hash);
lock (uniqueCurves)
{
uniqueCurves.Add(curve);
}
}
}
});
return uniqueCurves;
}
private static uint CreateCurveHashCodeX3(NurbsCurve crv, int salt)
{
int myHash = 0;
unchecked
{
///Comment or uncomment myHash for varing levels of duplication check
for (int i = 0; i < crv.Points.Count; i++)
{
myHash ^= saltHash(crv.Points[i].Location.GetHashCode(), salt) * (31 * i + 1);
myHash ^= saltHash(crv.Points[i].Weight.GetHashCode(), salt) * (97 * i + 1);
}
for (int i = 0; i < crv.Knots.Count; i++)
{
//myHash ^= crv.Knots[i].GetHashCode() * (2 * i + salt);
}
myHash ^= crv.Degree * 1559;
myHash ^= crv.Dimension * 2029;
//myHash ^= saltHash(crv.Domain.GetHashCode(), salt) * 1699;
myHash ^= saltHash(crv.IsClosed.GetHashCode(), salt) * 4099;
myHash ^= saltHash(crv.IsPeriodic.GetHashCode(), salt) * 4447;
myHash ^= saltHash(crv.IsValid.GetHashCode(), salt) * 5557;
myHash ^= crv.SpanCount * 9199;
}
return (uint) myHash;
}
private static int saltHash(int hash, int divisor)
{
int remainder;
Math.DivRem(hash, divisor, out remainder);
return hash + remainder;
}
}
Finally, to get an extra speed bump, you can put the code into a custom component to avoid the C# component casting. This ends up being about 20x faster than the threaded Curve.GeometryEquals() method posted above.
Amazing! @Brian_Washburn
can you send this*. gh file Or (code) for Custom GH Threaded HashSet Check ?[speed= 81 ms] and does you chek it by NurbsCurves?(not Line)
And can we use similar mechanism this code for Remove dup Brep or other Geometry?
Yes, this code works with nurbs curves and will actually work slower with lines because it will need to convert the lines to curves. In theory this mechanism would work for other geometry types like Breps by creating hash codes around the different Brep parameters. You could then build a switch case statement for the different geometry types.
This is the code for the custom GH component. Change the namespace, component category and GUID as needed.
RemoveDuplicateCurves
using Grasshopper.Kernel;
using Rhino.Geometry;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace HeronSandbox
{
public class RemoveDuplicateCurves : GH_Component
{
public RemoveDuplicateCurves()
: base("RemoveDuplicateCurves", "RDC",
"Description",
"Heron", "Subcategory")
{
}
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
pManager.AddCurveParameter("Curves", "C", "List of curves from which to remove duplicates.", GH_ParamAccess.list);
}
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
{
pManager.AddCurveParameter("Culled Curves", "CC", "List of curves without duplicates.", GH_ParamAccess.list);
}
protected override void SolveInstance(IGH_DataAccess DA)
{
List<Curve> curves = new List<Curve>();
DA.GetDataList(0, curves);
DA.SetDataList(0, CurveUtility.RemoveDuplicateCurvesParallel(curves));
}
public static class CurveUtility
{
public static List<Curve> RemoveDuplicateCurvesParallel(List<Curve> curves)
{
var uniqueCurves = new List<Curve>();
var hashSet = new HashSet<uint>();
Parallel.ForEach(curves, curve =>
{
// Reverse curve direction where necessary
if (curve.PointAtStart > curve.PointAtEnd)
curve.Reverse();
var hash = CreateCurveHashCodeX3(curve.ToNurbsCurve(), 101);
lock (hashSet)
{
if (!hashSet.Contains(hash))
{
hashSet.Add(hash);
lock (uniqueCurves)
{
uniqueCurves.Add(curve);
}
}
}
});
return uniqueCurves;
}
public static List<Curve> RemoveDuplicateCurves(List<Curve> curves)
{
var uniqueCurves = new List<Curve>();
var hashSet = new HashSet<uint>();
foreach (var curve in curves)
{
// Reverse curve direction where necessary
if (curve.PointAtStart > curve.PointAtEnd)
curve.Reverse();
var hash = CreateCurveHashCodeX3(curve.ToNurbsCurve(), 101);
if (!hashSet.Contains(hash))
{
hashSet.Add(hash);
uniqueCurves.Add(curve);
}
}
return uniqueCurves;
}
private static uint CreateCurveHashCodeX3(NurbsCurve crv, int salt)
{
int myHash = 0;
unchecked
{
for (int i = 0; i < crv.Points.Count; i++)
{
myHash ^= saltHash(crv.Points[i].Location.GetHashCode(), salt) * (31 * i + 1);
myHash ^= saltHash(crv.Points[i].Weight.GetHashCode(), salt) * (97 * i + 1);
}
for (int i = 0; i < crv.Knots.Count; i++)
{
//myHash ^= crv.Knots[i].GetHashCode() * (2 * i + salt);
}
myHash ^= crv.Degree * 1559;
myHash ^= crv.Dimension * 2029;
//myHash ^= saltHash(crv.Domain.GetHashCode(), salt) * 1699;
myHash ^= saltHash(crv.IsClosed.GetHashCode(), salt) * 4099;
myHash ^= saltHash(crv.IsPeriodic.GetHashCode(), salt) * 4447;
myHash ^= saltHash(crv.IsValid.GetHashCode(), salt) * 5557;
myHash ^= crv.SpanCount * 9199;
}
return (uint)myHash;
}
private static int saltHash(int hash, int divisor)
{
int remainder;
Math.DivRem(hash, divisor, out remainder);
return hash + remainder;
}
}
protected override System.Drawing.Bitmap Icon
{
get
{
return null;
}
}
public override Guid ComponentGuid
{
get { return new Guid("144CA80D-B6FC-42F0-8D05-E2FAEA170828"); }
}
}
}
hi @Brian_Washburn thanks
For closeCurvesReverse()RemoveDuplicateCurves doesn’t support but I add some line to the code also still not correct result also by Curve.Orientation ();
if(curve.IsClosed)
{
var c = curve.DuplicateCurve();
c.Domain =new Interval(0, 1);
c = c.Trim(new Interval(0, 0.9998));
if (c.PointAtStart >= c.PointAtEnd)
curve.Reverse();}
else{
// Reverse curve direction where necessary
if (curve.PointAtStart > curve.PointAtEnd)
curve.Reverse();}