Grasshopper删除重复圆孔问题


红色孔和白色孔各为一个图层,如果白色和红色孔出现在同一位置上,则优先删除白色孔。
由于有上万个孔,优先排除piont in curves电池(需要运算14min)
我自己的方法如下

想讨论看看有没有计算量更小的方法来实现,部分孔数据已经内置在gh文件里了,有兴趣的可以试一下,请注意保持孔始终在各自图层内。
0308.gh (571.2 KB)

获得圆心以后用点的closest point功能
能用低维度数据解决的,不要刻意使用高维度,能用数字判断的就数字,不行就点
还不行就曲线,再不行曲面,还是不行brep,实体等等
你这明明是一个点的问题,一上来如此迅猛地用上了brep。。。那就会非常慢了

2 个赞

感谢大鹿老师,我重新用closest point做了一下,可能是我对几个工具不够熟悉,目前可以筛选出重叠位置的孔,但是没有办法用Cull Index反回去删掉,原因应该是closest point之后的数据结构与一开始不一样了

谢谢,不过我想要的结果是这样的,如图,红色和白色分别在不同的组里。


可能你是想复杂了

3 个赞

什么叫同一位置?是指圆心和大小都重合,还是仅仅指圆心重合?

如果是圆心相同就要删减,那就直接取交集,然后再从需要删除的集合里删去交集。如果仅仅需要删除同圆心和同半径的,则将圆心和半径合并,形成新的集合,再按前面的方法求得结果,再分拆就可以了。

感谢回复,是的,求交集可以不用考虑数据的顺序直接求出来,是个好办法 :+1:

我试的这个方法应该还行

这个不需要考虑两圆心的距离精度问题,只要是包含关系,内部的圆都会删掉。

因为工作原因经常处理这种重孔的问题,检查圆心是否重叠经常需要遍历所有点,所以会导致运算时间很长,用C#解决了这个问题,在这里留个档,如果有碰到类似问题的小伙伴可以做个参考。程序都已经调试过了,已经优化到计算15万个点花费2秒。

using System;
using System.Collections.Generic;
using Rhino.Geometry;
using Rhino;

// 并查集(Union-Find)数据结构实现
public class UnionFind
{
    private int[] parent;
    private int[] rank;
    
    public UnionFind(int n)
    {
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++)
            parent[i] = i;
    }
    
    public int Find(int x)
    {
        if (parent[x] != x)
            parent[x] = Find(parent[x]);
        return parent[x];
    }
    
    public void Union(int x, int y)
    {
        int rootX = Find(x);
        int rootY = Find(y);
        
        if (rootX == rootY) return;
        
        if (rank[rootX] < rank[rootY])
        {
            parent[rootX] = rootY;
        }
        else if (rank[rootX] > rank[rootY])
        {
            parent[rootY] = rootX;
        }
        else
        {
            parent[rootY] = rootX;
            rank[rootX]++;
        }
    }
}

// 存储原始圆的信息
public class CircleInfo
{
    public Point3d Center { get; set; }
    public double Radius { get; set; }
    
    public CircleInfo(Point3d center, double radius)
    {
        Center = center;
        Radius = radius;
    }
}

// 主程序
{
    try
    {
        // 检查输入数据
        if (curves == null)
        {
            A = new List<Circle>();
            B = 0;
            C = 0;
            return;
        }
        
        // 开始计时
        var watch = System.Diagnostics.Stopwatch.StartNew();
        
        // 第一步:从曲线中提取圆心和半径信息
        var circleInfos = new List<CircleInfo>();
        foreach (var curve in curves)
        {
            if (curve == null) continue;
            
            // 只处理封闭曲线
            if (curve.IsClosed)
            {
                // 尝试获取圆的中心点和半径
                Arc arc;
                if (curve.TryGetArc(out arc))
                {
                    circleInfos.Add(new CircleInfo(arc.Center, arc.Radius));
                }
                else
                {
                    // 对于其他封闭曲线,使用包围盒中心作为近似圆心,使用平均尺寸作为半径
                    var bbox = curve.GetBoundingBox(true);
                    var center = bbox.Center;
                    var radius = (bbox.Diagonal.X + bbox.Diagonal.Y) / 4.0; // 近似半径
                    circleInfos.Add(new CircleInfo(center, radius));
                }
            }
        }
        
        // 如果没有圆或容差为0,直接返回
        if (circleInfos.Count == 0 || tolerance <= 0)
        {
            A = new List<Circle>();
            B = 0;
            C = 0;
            return;
        }
        
        // 提取圆心点
        var points = circleInfos.ConvertAll(ci => ci.Center);
        
        // 第二步:使用空间哈希加速查找接近的点
        var spatialDict = new Dictionary<Tuple<int, int>, List<int>>();
        double cellSize = tolerance * 2;  // 网格大小设置为容差的2倍
        
        // 将点分配到空间网格中
        for (int i = 0; i < points.Count; i++)
        {
            var point = points[i];
            // 计算点所在的网格单元
            var key = Tuple.Create((int)(point.X / cellSize), (int)(point.Y / cellSize));
            
            // 如果网格单元不存在,创建新列表
            if (!spatialDict.ContainsKey(key))
                spatialDict[key] = new List<int>();
            
            // 将点索引添加到网格单元
            spatialDict[key].Add(i);
        }
        
        // 第三步:使用并查集合并接近的点
        var uf = new UnionFind(points.Count);
        
        // 遍历所有网格单元
        foreach (var kvp in spatialDict)
        {
            int cellX = kvp.Key.Item1;
            int cellY = kvp.Key.Item2;
            
            // 检查当前单元格和相邻的8个单元格
            for (int dx = -1; dx <= 1; dx++)
            {
                for (int dy = -1; dy <= 1; dy++)
                {
                    // 计算相邻单元格的键
                    var neighborKey = Tuple.Create(cellX + dx, cellY + dy);
                    
                    // 如果相邻单元格存在
                    if (spatialDict.TryGetValue(neighborKey, out var neighborIndices))
                    {
                        // 检查当前单元格和相邻单元格中的点是否接近
                        foreach (var i in kvp.Value)
                        {
                            foreach (var j in neighborIndices)
                            {
                                // 避免与自身比较,且检查距离是否小于容差
                                if (i != j && points[i].DistanceTo(points[j]) < tolerance)
                                {
                                    uf.Union(i, j);  // 合并接近的点
                                }
                            }
                        }
                    }
                }
            }
        }
        
        // 第四步:分组结果并计算平均位置和平均半径
        var groups = new Dictionary<int, List<int>>();
        for (int i = 0; i < points.Count; i++)
        {
            int root = uf.Find(i);
            if (!groups.ContainsKey(root))
                groups[root] = new List<int>();
            groups[root].Add(i);
        }
        
        // 计算每组的新圆心(平均位置)和平均半径
        var mergedCircles = new List<Circle>();
        foreach (var group in groups.Values)
        {
            double avgX = 0, avgY = 0, avgRadius = 0;
            foreach (var index in group)
            {
                avgX += circleInfos[index].Center.X;
                avgY += circleInfos[index].Center.Y;
                avgRadius += circleInfos[index].Radius;
            }
            
            avgX /= group.Count;
            avgY /= group.Count;
            avgRadius /= group.Count;
            
            // 创建新的圆
            var center = new Point3d(avgX, avgY, 0);
            mergedCircles.Add(new Circle(center, avgRadius));
        }
        
        // 停止计时
        watch.Stop();
        
        // 第五步:设置输出
        A = mergedCircles;                     // 合并后的圆
        B = circleInfos.Count - mergedCircles.Count; // 去除的重复圆数量
        C = watch.Elapsed.TotalSeconds;       // 处理时间(秒)
    }
    catch (Exception)
    {
        // 静默处理异常
        // 确保输出不为 null
        A = new List<Circle>();
        B = 0;
        C = 0;
    }
}