cad.net 链式选择和断分标注

说明

由于cad自身的图元特性,
标注作为一个复合型图元却没有做到磁性选择,
我们非自定义图元的时候只能通过链式选择和断分标注模拟.

磁性选择:
当存在共同点的时候,可以直接吸附起来,跟磁铁特性一样,
不是组也不是块参照,而是基于之间的状态,
组可以被s命令点击移动单个图元.
块参照只能移动整体,块的特性页(ctrl+1)不显示标注内容.
所以才有磁铁体,它应该显示夹点和可以直接修改磁铁体内的图元,
我们可以利用选择集事件+维护缓存实现.

标注/引线/文字,自动吸附起,
解开才需要命令,可惜cad并没有这样的东西,比较失望.

链式选择:
先选择所有图元,
然后判断点击的图元共同点碰撞到下一个图元的公共点就选择起来.

断分标注:
直接看动图即可.

源泉的作者告诉阿惊说,
标注避让:
思路是上下左右移动文字,然后有优先排序的方式,
例如优先的是上下上下上下上下的排序.
但是阿惊现在已经没空完成这个函数了,若完成你就提供出来给大家吧.

动图演示

mac-lee的链式选择展示

img

代码

public class TestDivDim {
// 断分标注
[CommandMethod(nameof(JJ_dd), 
    CommandFlags.Modal | CommandFlags.UsePickSet | CommandFlags.Redraw)] 
    // 预选  | CommandFlags.DocExclusiveLock
public void JJ_dd() {
    var dm = Acap.DocumentManager;
    var doc = dm.MdiActiveDocument;
    var db = doc.Database;
    var ed = doc.Editor;
    ed.WriteMessage(Environment.NewLine + "****惊惊连盒-断分标注");

    PromptEntityOptions peo = new(Environment.NewLine + "点选标注:") {
        AllowObjectOnLockedLayer = false,
        AllowNone = false
    };
    PromptPointOptions ppo = new(
        Environment.NewLine + "选取[断分/延长点]或[输入等分段数,负数为改数值)]:<空格退出>") {
        AllowArbitraryInput = true, //任意输入
    };

    // 过滤并获取以此为基础的标注
    // 点选标注
    var res = ed.GetEntity(peo);
    if(res.Status != PromptStatus.OK) return;

    DimInfo entDimInfo;
    using (DBTrans tr = new()){
        using var ent = (Entity)tr.GetObject(res.ObjectId, OpenMode.ForRead);
        if(!(ent is RotatedDimension || ent is AlignedDimension))
            return;
        entDimInfo = DimInfo.Create(ent);
    }

    // 清空选择集
    ed.SetImpliedSelection(new ObjectId[0]);
    // 选择集过滤器,只选择标注
    var tvs = new TypedValue[] {
        new TypedValue((int)DxfCode.Start, "DIMENSION")
    };
    var ss = ed.SelectAll(new SelectionFilter(tvs));
    if (ss.Status != PromptStatus.OK) return;


    Tolerance oldtol = Tolerance.Global; //旧容差
    Tolerance.Global = new Tolerance(1e-6, 1e-6);//新容差

    using (DBTrans tr = new()){
        // 构造缓存,获取目标的链条亮显
        DimInfoCache.Create(ss.Value.GetObjectIds());

        var link = DimInfoCache.Find(entDimInfo);
        if(link is null) return;
        // 亮显
        tr.EntityRedraw(link.ToArray(), Bright.Highlight);
    }

    // 如果没有点击,需要关闭前需要改回暗显
    var pprA = ed.GetPoint(ppo);
    if (pprA.Status == PromptStatus.Keyword) {
        using DBTrans tr = new();
        using var ent = (Entity)tr.GetObject(entDimInfo.ObjectId, OpenMode.ForWrite);

        // 用户输入了数字,进行改变尺寸操作
        int.TryParse(pprA.StringResult, out int a);
        if (a < 0) {
            string str = (entDimInfo.DimLength / -a).ToString("#0.00") + "*" + (-a).ToString() + "=<>";
            if (ent is AlignedDimension dim)
                dim.DimensionText = str;
            else if (ent is RotatedDimension dim2)
                dim2.DimensionText = str;
        } else {
            // 根据等分份数生成标注
            double yifen = entDimInfo.DimLength / a;
        }
    }
    else if (pprA.Status == PromptStatus.OK) {
        // 断分标注
        DivideDim(ed, entDimInfo, pprA.Value);
        // todo 这里是留来写标注避让....
    }

    using (DBTrans tr = new()){
        // 找链条
        var link = DimInfoCache.Find(entDimInfo);
        if(link is null) return;
        // 暗显
        tr.EntityRedraw(link.ToArray(), Bright.Unhighlight);
    }

    Tolerance.Global = oldtol;
}

/// <summary>
/// 循环分裂标注
/// </summary>
/// <param name="ed">编辑器</param>
/// <param name="entDimInfo">选中的图元</param>
/// <param name="getpoint">分裂点</param>
void DivideDim(Editor ed, DimInfo entDimInfo, Point3d? getpoint) {
    ed.WriteMessage(Environment.NewLine + "继续分裂/延长点");
    ed.GetWcsUcs(out _, out CoordinateSystem3d ucs);
    PromptPointOptions ppo = new("") {
        AllowArbitraryInput = true, //任意输入
    };

    var link = new DimLink();
    link.TryAdd(entDimInfo);
    var angle = link.Angle;

    // 遍历链式选择集
    while (true) {
        // 一开始是有值的,所以第二次才执行
        if (getpoint is null) {
            var ppr = ed.GetPoint(ppo);
            if (ppr.Status != PromptStatus.OK) break;
            getpoint = ppr.Value;
        }

        using(DBTrans tr = new()){
            LinkChange(ucs.ZAxis, angle, entDimInfo, ref getpoint);
        }
        if(getpoint is not null) break;

        var link = DimInfoCache.Find(entDimInfo);
        if(link is null) break;
        using(DBTrans tr = new()){
            // 亮显,新增标注了.
            tr.EntityRedraw(link.ToArray(), Bright.Highlight);
        }
    }
}

void LinkChange(Vector3d vecRo, double angle, DimInfo entDimInfo, ref Point3d? getpoint) {
    var tr = DBTrans.Top;
    // 获取链条
    var link = DimInfoCache.Find(entDimInfo);
    if(link is null) return;

    // 需要裂开的标注
    DimInfo? dimDivide = null;
    // 生成集合,判断增点是否在范围外,是就补充尺寸,否就分裂尺寸
    List<Point3d[]> newly = new();

    // 获取链条点集,并旋转
    var dimMin = link.Points.First();
    var dimPointsRo = PointHelper.RotateBy(link.Points, angle, vecRo, dimMin).ToArray();
    var dimMaxRo = dimPointRo.Last();

    // 图元顶点作为新图元的延伸点
    var entXLine1PointRo = entDimInfo.XLine1Point.RotateBy(angle, vecRo, dimMin);
    // 增点旋转到x轴,用来判断方向
    var getpointRo = getpoint.Value.RotateBy(angle, vecRo, dimMin);
    // 增点在左外
    if (getpointRo.X < dimMin.X) {
        var tmp = new Point3d[] {
            new Point3d(dimMin.X, entXLine1PointRo.Y, entXLine1PointRo.Z),//标注边点2
            dimMin, // 标注确认点
        };
        newly.Add(tmp);
    } // 增点在右外
    else if (getpointRo.X > dimMaxRo.X) {
        var tmp = new Point3d[] {
            new Point3d(dimMaxRo.X, entXLine1PointRo.Y, entXLine1PointRo.Z), //标注边点2
            dimMaxRo,  // 标注确认点
        };
        newly.Add(tmp);
    } // 增点在中,点在标注链中间了
    else {
        // 把已经旋转的给它,获取中间位置的标注
        dimDivide = GetDivideEntiry(entDimInfo, getpointRo, dimPointsRo);
        if (dimDivide is not null) {
            // 获取标注信息
            var pt1 = dimDivide.XLine1Point.RotateBy(angle, vecRo, dimMin);
            var pt2 = dimDivide.XLine2Point.RotateBy(angle, vecRo, dimMin);

            // 添加到生成集合
            var tmp = new Point3d[] {
                new Point3d(pt1.X, entXLine1PointRo.Y, dimMin.Z),//标注边点2
                new Point3d(pt1.X, dimMin.Y, dimMin.Z)      //标注确认点
            };
            newly.Add(tmp);

            tmp = new Point3d[] {
                new Point3d(pt2.X, entXLine1PointRo.Y, dimMin.Z),//标注边点2
                new Point3d(pt2.X, dimMin.Y, dimMin.Z)      //标注确认点
            };
            newly.Add(tmp);
        }
    }
        
    // 不是分裂的时候就为空
    bool del = true;
    if (dimDivide is null) {
        dimDivide = entDimInfo;
        del = false;
    }

    // 标注边点1
    var bdq1 = new Point3d(getpointRo.X, entXLine1PointRo.Y, entXLine1PointRo.Z);
    // 相同表示点在了标注的脚上面,就不新建
    var isNewlyCreate = newly.FirstOrDefault(item => 
        bdq1.IsEqualTo(item[0], Tolerance.Global)) is null;

    if (isNewlyCreate) {
        var newlyIds = newly.Select(bdq => entDimInfo.AddCreateDimToMsPs(dimDivide, bdq1, bdq[0], bdq[1]))
            .Where(id => id != null).ToArray()

        // 新增标注加入链条
        foreach (var id in newlyIds) {
            using var dim = (Entity)tr.GetObject(id, OpenMode.ForRead);
            link.TryAdd(DimInfo.Create(dim));
        }
        // 新增标注旋转回去.
        tr.EntityRotate(newlyIds, -angle, vecRo, dimMin);

        if (del) {
            // 删除原本,在增设的时候不删,在分裂的时候删掉
            tr.EntityErase(new List<Point3d>(){dimDivide.ObjectId});
            link.Remove(dimDivide);
        }
    }
    // 设置为空才重新点选
    getpoint = null;
}

// 获取需要分裂的标注
DimInfo? GetDivideEntiry(DimInfo entDimInfo, Point3d getpointRo, Point3d[] dimPointsRo) {
    // 距离值数组,负值到正值,
    // 计算方式:链条全部确认点-目标点距离,判断分裂位置.
    // 第一个大于等于0就是目标:
    // 案例:距离值数组[-2,-1,  -1,0,  0,1,  1,2],表示四个标注八个距离
    // 第一个大于等于就是i=[3]的0,因此[i-1]和[i]两个点所在标注,
    // 图元集合[(3+1)/2]的第二个标注

    int i = 0;
    for (; i < dimPointsRo.Length; i++) {
        if (dimPointsRo[i].X - getpointRo.X >= 0) break;
    }
    // 链条与其相同点,就是分裂的图元
    var link = DimInfoCache.Find(entDimInfo);
    if(link is null) return null;
    return link.GetIndex((i+1) / 2);
}
}

标注信息结构

public interface IHasMidPoint {
    Point3d GetMidPoint();
}

/// <summary>
/// 标注信息
/// </summary>
public class DimInfo : IEquatable<DimInfo>,  IComparable<DimInfo>, IHasMidPoint {
    // 接口,用来提供给哈希网格
    public Point3d GetMidPoint() => MidPoint;

    public bool Equals(DimInfo other) {
        if (other is null) return false;
        return CompareTo(other) == 0;
    }
    public override bool Equals(object obj) {
        if (obj is not DimInfo other) return false;
        return Equals(other);
    }
    public override int GetHashCode() {
       return ObjectId.GetHashCode();
    }

    // 要实现IComparable<T>接口,而且不是鸭子类型
    public int CompareTo(DimInfo other) {
        if (ObjectId == other.ObjectId) return 0;
        // 如果比较确认点/未知点,可能相反,并且之后也要中点排序.
        // 中点排序
        if(MidPoint.IsEqualTo(other.MidPoint, Tolerance.Global))
            return 0;     
        // 如果X相同,比较Y,如果Y也相同,比较Z
        if (MidPoint.X != other.MidPoint.X) 
            return MidPoint.X.CompareTo(other.MidPoint.X);
        if (MidPoint.Y != other.MidPoint.Y) 
            return MidPoint.Y.CompareTo(other.MidPoint.Y);
        return MidPoint.Z.CompareTo(other.MidPoint.Z);
    }

    /// <summary>
    /// 标注的类型
    /// </summary>
    public string DimType { get; }
    /// <summary>
    /// 标注的第一点
    /// </summary>
    public Point3d XLine1Point { get; }
    /// <summary>
    /// 标注的第二点
    /// </summary>
    public Point3d XLine2Point { get; }
    /// <summary>
    /// 标注的确定点与XLine2Point垂直的,甚至重合
    /// </summary>
    public Point3d DimLinePoint { get; }
    /// <summary>
    /// 标注的xLine1Point下面的点
    /// </summary>
    public Point3d DimLinePointN { get; }
    /// <summary>
    /// 标注的样式 
    /// </summary>
    public ObjectId DimensionStyle { get; }
    /// <summary>
    /// 标注的测量距离
    /// </summary>
    public double DimLength { get; }
    /// <summary>
    /// 标注的线性比例 
    /// </summary>
    public double DimScale { get; }
    /// <summary>
    /// 标注的标注角度
    /// </summary>
    public double DimAngle { get; }
    /// <summary>
    /// 全局比例
    /// </summary>
    public double Dimscale { get; }
    public ObjectId ObjectId { get; }

    public int LinkId  { get; set; } = 0; // 标识符模式加速查找
    public Point3d MidPoint { get; }

    public DimInfo(AlignedDimension dim) {
        ObjectId = dim.ObjectId;
        DimType = nameof(AlignedDimension);
        XLine1Point = dim.XLine1Point;  // 标注脚点一
        XLine2Point = dim.XLine2Point;  // 标注脚点二
        DimLinePoint = dim.DimLinePoint; // 确认点,在第二点下 
        DimensionStyle = dim.DimensionStyle; // 标注样式
        DimLength = dim.Measurement;  // 测量距离
        DimScale = dim.Dimlfac; // 线性比例 
        DimAngle = -(Math.PI*2 - XLine1Point.GetAngle2XAxis(XLine2Point)); // 标注角度(和对齐标注一样)
        DimLinePointN = GetDimPointN(XLine1Point, DimLinePoint, DimAngle); // 求未知点 
        Dimscale = dim.Dimscale;//全局比例

        MidPoint = PointHelper.GetMidPoint(DimLinePoint, DimLinePointN);
    }

    public DimInfo(RotatedDimension dim) {
        ObjectId = dim.ObjectId;
        DimType = nameof(RotatedDimension);
        XLine1Point = dim.XLine1Point;  // 标注脚点一
        XLine2Point = dim.XLine2Point;  // 标注脚点二
        DimLinePoint = dim.DimLinePoint; // 确认点,在第二点下 
        DimensionStyle = dim.DimensionStyle; // 标注样式
        DimLength = dim.Measurement; // 测量距离
        DimScale = dim.Dimlfac;             // 线性比例 
        DimAngle = dim.Rotation;           // 标注角度 
        DimLinePointN = GetDimPointN(XLine1Point, DimLinePoint, DimAngle); // 求未知点
        Dimscale = dim.Dimscale; // 全局比例

        MidPoint = PointHelper.GetMidPoint(DimLinePoint, DimLinePointN);
    }

    /// <summary>
    /// 求标注的未知点
    /// </summary>
    /// <param name="xLine1Point">点1</param>
    /// <param name="dimLinePoint">旋转点</param>
    /// <param name="dimAngle">角度</param>
    /// <returns>未知点</returns>
    Point3d GetDimPointN(Point3d xLine1Point, Point3d dimLinePoint, double dimAngle) {
        // 求出标注第一点下面那点
        var pt = xLine1Point.RotateBy(-dimAngle, Vector3d.ZAxis, dimLinePoint);
        pt = new Point3d(pt.X, dimLinePoint.Y, dimLinePoint.Z);
        pt = pt.RotateBy(dimAngle, Vector3d.ZAxis, dimLinePoint);
        return pt;
    }

    /// <summary>
    /// 创建标注的信息
    /// </summary> 
    public static DimInfo Create(Entity ent) {
        DimInfo dim;
        if (ent is AlignedDimension al)// 对齐标注
            dim = new DimInfo(al);
        else if (ent is RotatedDimension ro)// 转角标注
            dim = new DimInfo(ro);
        else throw new("类型错误");
        return dim;
    }
}

标注信息缓存

字段用static表示跨文档

public static class DimInfoCache {
    public static Dictionary<Database, DimInfoDoc>  _dimInfoCache = new();
    public static DimLink Create(ObjectId[] ids) {
        var dm = Acap.DocumentManager;
        var doc = dm.MdiActiveDocument;
        var db = doc.Database;
        DimInfoDoc dlo;
        if(!_dimInfoCache.TryGetValue(db, out dlo)) {
           dlo = new DimInfoDoc();
           _dimInfoCache.Add(db, dlo);
        }
        return dlo.Create(ids);
    }
    public static void Clear() {
        var dm = Acap.DocumentManager;
        var doc = dm.MdiActiveDocument;
        var db = doc.Database;
        _dimInfoCache.TryGetValue(db, out DimInfoDoc dlo));
        dlo?.Clear();
    }
    public static DimLink? Find(DimInfo info){
        var dm = Acap.DocumentManager;
        var doc = dm.MdiActiveDocument;
        var db = doc.Database;
        _dimInfoCache.TryGetValue(db, out DimInfoDoc dlo));
        return dlo?.Find(info);
    }
    public static void Remove(DimInfo info){
        var dm = Acap.DocumentManager;
        var doc = dm.MdiActiveDocument;
        var db = doc.Database;
        _dimInfoCache.TryGetValue(db, out DimInfoDoc dlo));
        dlo?.Remove(info);
    }
    public static DimLink? Add(DimInfo info){
        var dm = Acap.DocumentManager;
        var doc = dm.MdiActiveDocument;
        var db = doc.Database;
        _dimInfoCache.TryGetValue(db, out DimInfoDoc dlo));
        return dlo?.Add(info);
    }
}


public class DimInfoDoc {
public DimInfoDoc(){}

/// <summary>
/// 构造缓存
/// </summary>
public void Create(ObjectId[] ids) {
    Debug.Assert(ids is not null, "参数不能为null");
    // todo 如果不订阅事件维护索引,每个命令要清空和重新构造缓存.
    DimInfoCache.Clear();
    // 1,区分同角度
    var tr = DBTrans.Top;
    foreach(var id in ids) {
        using var dim = (Entity)tr.GetObject(id, OpenMode.ForRead);
        Map1Add(DimInfo.Create(dim));
    }
    // 2,区分出同角度断开的
    GetBrokenLink();
}

// map1,角度缓存<角度,无序的标注>,同角度没有区分断开
SortedDictionary<int, List<DimInfo>> _dmap = new();

// map1优化,value改为投影
// 所有标注投影到x轴,连续的范围作为一个分块.
// https://www.cnblogs.com/JJBox/p/18629112
// SortedList<int, List<DimInfo>> _chunks = new();
vois InsertChunks(SortedList<int, List<DimInfo>> chunks, DimInfo info) {

}


// 弧度转角度,归一化到圆
int GetDimInfoDegree(DimInfo info, bool addPI = false) {
    const double PI2 = 2 * Math.PI;
    const double ONE_DEGREE = 180 / Math.PI; //一角度
    double angle = info.DimAngle;
    if (addPI) angle += Math.PI;
    angle %= PI2;
    if (angle < 0) angle += PI2;
    return (int)(angle * ONE_DEGREE);
}

// 根据夹角(角度制)进行分组,7字型碰撞链接要靠此区分.
void Map1Add(DimInfo info){
    Debug.Assert(info is not null, "参数不能为null");
    // 转角度制,把微小角度合并,并且容差3度
    // 同时逆向量也作为同组,也就是0和180度是同组.
    var a = GetDimInfoDegree(info);
    if(_dmap.ContainsKey(a)) {
        _dmap[a].Add(info); return; //todo 改为tryadd
    }
    if(_dmap.ContainsKey(a+1)) {
        _dmap[a+1].Add(info); return;
    }
    if(_dmap.ContainsKey(a-1)) {
        _dmap[a-1].Add(info); return;
    }

    a = GetDimInfoDegree(info, true);
    if(_dmap.ContainsKey(a)) {
        _dmap[a].Add(info); return;
    }
    if(_dmap.ContainsKey(a+1)) {
        _dmap[a+1].Add(info); return;
    }
    if(_dmap.ContainsKey(a-1)) {
        _dmap[a-1].Add(info); return;
    }
    _dmap.Add(a, new(){info});
}

// 链条DimLink: 内部是中点排序,不支持U型链条
// 组group: OrderList<DimLink>id排序的链条
// map2,角度缓存<角度,组>,转为链条,区分出同角度断开
SortedDictionary<int, OrderList<DimLink>> _map2 = new();

/*
map2优化:
大部分标注是0和90度,同角度太多,退化成遍历了.
OrderList在https://www.cnblogs.com/JJBox/p/18558660#_lab2_4_2

字典方案 <minXmaxX,组>
有序可重复,因为链条minX就是可以重复的,
但是重复非常少(万一有个死变态垂直排版?)
查询入参是中点,怎么找呢?
key头.MinX <= dimInfo.Mid.X <= key中.MaxX,
折半找寻,但是夹着目标可能是多条链,最后遍历它们.
好复杂啊...无论是结构还是流程都令人难以想象画面...

哈希网格方案
每条链条中点记录下来,存入网格中,
然后搜索的时候依照dimInfo中点寻找,
但是中点跨网格了...
*/

/*
并行方案:
GetBrokenLink改为并行有难度:
生成LinkId可以原子加,这个没啥问题.
每个线程上面遇到的每个标注都有可能是链条,
首先遇到标注的info.LinkId==0,设置info.LinkId,
然后另一个线程也遇到`同一条链的`也是id==0,
那么同一条链就有多个id了,那么就需要聚合,加入对方的容器.
所以为了降低这种并发问题:
1,为了尽可能并行,先把它们串行加入分块矩阵也就是哈希网格上面.
2,并行每个网格,但是网格内串行.
2,网格任务:先排序,再两两比较是否可以碰撞,碰撞的加入链条.
4,返回半成品链条,因为它还可能和邻近碰撞,再求邻近数组成员的碰撞.
*/

// 将相同夹角的区分成不同的链条,DimLink容器会依据中点排序.
void GetBrokenLink() {
    // 1,不同角度的
    foreach(var kv in _dmap) {
        // 2,分离出同角度不同链条的,每个标注都成为链条
        while(kv.Value.Count != 0) {
            var degree = kv.Key;
            var link = DimLinkCache.CreateLink(degree);
            if(_map2.ContainsKey(degree))
                _map2[degree].Add(link);
            else
                _map2.Add(degree, new(){ link });

            // 3,同链条的获取,分离出断开的
            // 成功加入一个就移除原本并重头开始遍历
            // 串行也应该先排序,否则连缓存都没有命中,真是糟糕...
start:
            var ator = kv.Value.GetEnumerator(); // 是List<DimInfo>
            while (ator.MoveNext()) {
                var info = ator.Current;
                if(link.TryAdd(info)) { // 相同确认点/未知点才加入,以此链式选择
                    info.LinkId = link.LinkId; // 倒序索引,依照dim找链条
                    ator.Dispose();
                    kv.Value.Remove(info);
                    goto start;
                }
            }
        }
    }
    _dmap.Clear();
}

public void Clear() {
    _map2.Clear();
    DimLinkCache.Clear();
}

(int Degree, OrderList<DimLink> Group)? FindGroup(DimInfo info){
    Debug.Assert(info is not null, "参数不能为null");
    // 转角度制,然后微小角度合并
    var a = GetDimInfoDegree(info);
    if(_map2.ContainsKey(a))
        return (a, _map2[a]);
    if(_map2.ContainsKey(a+1))
        return (++a, _map2[a]);
    if(_map2.ContainsKey(a-1))
        return (--a, _map2[a]);

    a = GetDimInfoDegree(info, true);
    if(_map2.ContainsKey(a))
        return (a, _map2[a]);
    if(_map2.ContainsKey(a+1))
        return (++a, _map2[a]);
    if(_map2.ContainsKey(a-1))
        return (--a, _map2[a]);

    return null;
}

// 传入的info.LinkId可能是0.
public DimLink? Find(DimInfo info){
    Debug.Assert(info is not null, "参数不能为null");
    if(info.LinkId > 0) 
        return DimLinkCache.Find(info.LinkId);
    var fg = FindGroup(info); // 通过夹角获取
    return fg?.Value.Group.FirstOrDefault(link => link.Contains(info));
}

public void Remove(DimInfo info){
    Debug.Assert(info is not null, "参数不能为null");
    DimLink? findLink = null;
    if(info.LinkId > 0) {
        findLink = DimLinkCache.Find(info.LinkId);
    }
    else {
       var fg = FindGroup(info);  // 通过夹角获取
       findLink = fg?.Value.Group.FirstOrDefault(link => link.Contains(info));
    }
    if (findLink is null) return;

    findLink.Remove(info);
    if(findLink.Count == 0) {
       _map2[findLink.Degree].Remove(findLink);
    }
    if(findLink.LinkId > 0) {
        DimLinkCache.EraseLink(findLink);
    }
}

// 外部加入,
// 根据夹角找到组,然后组内查找对象,
// 没有找到组,就新增链条,并添加到map2内
public DimLink? Add(DimInfo info){
    Debug.Assert(info is not null, "参数不能为null");
    if(info.LinkId > 0) {
        var findLink = DimLinkCache.Find(info.LinkId);
        if(findLink is not null
            && !findLink.Contains(info)
            && findLink.TryAdd(info))
            return findLink;
        return null;
    }

    // 转角度制,然后微小角度合并
    var degree = GetDimInfoDegree(info);
    var fg = FindGroup(info);  // 通过夹角获取
    if(fg is not null) {
        degree = fg.Value.Degree;
        var findlink = fg.Value.Group.FirstOrDefault(link => link.Contains(info));
        if(findlink is not null) return findlink
    }

    // 新增链条到map2上面
    var link = DimLinkCache.CreateLink(degree);
    if(_map2.ContainsKey(degree))
        _map2[degree].Add(link);
    else
        _map2.Add(degree, new(){ link });

    if(link.TryAdd(info)){
        info.LinkId = link.LinkId; // 倒序索引,依照dim找链条
    }
    return link;
}
}

链条缓存

字段用static表示跨文档

public static class DimLinkCache {
    public static Dictionary<Database, DimLinkDoc>  _dimLinkCache = new();
    public static DimLink CreateLink(int degree) {
        var dm = Acap.DocumentManager;
        var doc = dm.MdiActiveDocument;
        var db = doc.Database;
        DimLinkDoc dld;
        if(!_dimLinkCache.TryGetValue(db, out dld)) {
           dld = new DimLinkDoc();
           _dimLinkCache.Add(db, dld);
        }
        return dld.CreateLink(degree);        
    }
    public static void EraseLink(DimLink link) {
        var dm = Acap.DocumentManager;
        var doc = dm.MdiActiveDocument;
        var db = doc.Database;
        _dimLinkCache.TryGetValue(db, out DimLinkDoc dld);
        dld?.EraseLink(link);
    }
    public static void Clear() {
        var dm = Acap.DocumentManager;
        var doc = dm.MdiActiveDocument;
        var db = doc.Database;
        _dimLinkCache.TryGetValue(db, out DimLinkDoc dld);
        return dld?.Clear();
    }
    public static DimLink? Find(int linkId) {
        var dm = Acap.DocumentManager;
        var doc = dm.MdiActiveDocument;
        var db = doc.Database;
        _dimLinkCache.TryGetValue(db, out DimLinkDoc dld);
        return dld?.Find(linkId);
    }
}


public class DimLinkDoc {
    // 标识符模式: 0表示初始化,从1开始自增.删除取反
    int _incLinkId = 0;
    public int GetId() {
        if (_incLinkId == int.MaxValue)
            throw new InvalidOperationException("ID分配已耗尽");
        return System.Threading.Interlocked.Increment(ref _incLinkId);
    }

    // 缓存<链id,链条>,虽然夹角可以作为linkId,但是大部分夹角是0和90度.
    // 此处的KV是区间才对,不然和数组有什么区别?
    SortedDictionary<int, DimLink> _linkIdMap = new();

    public DimLinkDoc(){}

    public DimLink CreateLink(int degree) {
        var myLinkId = GetId();
        var link = new DimLink(degree, myLinkId);
        // if (!_linkIdMap.ContainsKey(myLinkId))
        _linkIdMap.Add(myLinkId, link); // 新增肯定不重复
        return link;
    }

    public void EraseLink(DimLink link) {
       if(link.LinkId > 0) {
           _linkIdMap.Remove(link.LinkId);
           // 取反id,没有引用好快就回收了
           link.LinkId = ~link.LinkId;
       }
    }

    public void Clear() {
        _linkIdMap.Clear();
    }

    // 查找
    public DimLink? Find(int linkId) {
        Debug.Assert(linkId != 0, "参数不能为0");
        _linkIdMap.TryGetValue(linkId, out DimLink link);
        return link;
    }
}

链条结构

所谓的链条有U型的,就不应该中点排序,
不过标注链条是一个向量,可以排序...

public class DimLink : IComparable<DimLink>, IHasMidPoint {
    // 接口,用来提供给哈希网格
    public Point3d GetMidPoint() => 
        new Point3d(_right/2 + _left/2, _bottom/2 + _top/2, 0) ;

    // 实现IComparable<T>接口,而且不是鸭子类型
    public int CompareTo(DimLink other) {
        if(LinkId > other.LinkId) return 1;
        if(LinkId < other.LinkId) return -1;
        return 0;
    }

    public double Left => _left;
    public double Top => _top;
    public double Right => _right;
    public double Bottom => _bottom;
    double _left = double.MaxValue;
    double _right = double.MinValue;
    double _top = double.MinValue;
    double _bottom = double.MaxValue;

    public int Degree {get;} // 它是用来分组的,不是真实角度
    public int LinkId {get; internal set;}
    OrderList<DimInfo> _dims; // 标注线中点排序
    OrderList<Point3d> _pts; // 要可重复排序,否则计算不对,因为获取分裂的标注需要
    public OrderList<Point3d> Points => _pts;
    public int Count => _dims.Count;
    public DimLink(int degree = 361, int linkId = -1) {
        Degree = degree;
        LinkId = linkId;
        _dims = new();
        _pts = new(new Point3dComparer());
    }

    public bool TryAdd(DimInfo info) {
        Debug.Assert(info is not null, "参数不能为null");
        // 不同角度已经区分了,这里都是同角度,可以少判断很多东西.
        if(_pts.Count == 0
            || _pts.Contains(info.DimLinePoint)
            || _pts.Contains(info.DimLinePointN)) {
            _dims.Add(info);
            _pts.Add(info.DimLinePoint);
            _pts.Add(info.DimLinePointN);

            // 更新包围盒左右
            _left = Math.Min(Math.Min(info.DimLinePoint.X, info.DimLinePointN.X), _left);
            _right = Math.Max(Math.Max(info.DimLinePoint.X, info.DimLinePointN.X), _right);
            // 更新包围盒上下
            _bottom = Math.Min(Math.Min(info.DimLinePoint.Y, info.DimLinePointN.Y), _bottom);
            _top = Math.Max(Math.Max(info.DimLinePoint.Y, info.DimLinePointN.Y), _top);

            return true;
        }
        return false;
    }

    public void Remove(DimInfo info) {
        Debug.Assert(info is not null, "参数不能为null");
        _dims.Remove(info);
        // 链条的确认点/未知点,会叠起来,所以只能移除其中一个点
        if(_pts.Contains(info.DimLinePoint) && _pts.Contains(info.DimLinePointN)) {
            _pts.RemoveAt(_pts.FindFirst(info.DimLinePoint));
            _pts.RemoveAt(_pts.FindFirst(info.DimLinePointN));
        }
    }

    public bool Contains(DimInfo info){
        Debug.Assert(info is not null, "参数不能为null");
        return _dims.Contains(info);
    }

    // 为了保证链条有序性,
    // 不提供直接set的功能的,所以不提供索引器.
    // 如果要做好,甚至这个返回的对象也要clone一份出去
    public DimInfo GetIndex(int index) {
        if (index >= _dims.Count)
            throw new IndexOutOfRangeException($"索引 {index} 超出了列表的有效范围"); 
        return _dims[index];
    }

    // 链条夹角是确认点/未知点的min开始的,标注信息是脚点角度.
    double _angle = double.NaN;
    public double Angle {
        get {
            if (_pts.Count == 0)
                throw new InvalidOperationException("pts集合不能为空");
            if (double.IsNaN(_angle)){
                _angle = -(_pts.First().GetAngle2XAxis(_pts.Last()));
            }
            return _angle;
        }
        internal set { _angle = value; }
    }
}

子函数

public class Point3dComparer : IComparer<Point3d> {
    public int Compare(Point3d a, Point3d b)  {
        // 容差一致
        if (a.IsEqualTo(b, Tolerance.Global)) return 0;
        // 如果X相同,比较Y,如果Y也相同,比较Z
        if (a.X != b.X) return a.X.CompareTo(b.X);            
        if (a.Y != b.Y) return a.Y.CompareTo(b.Y);    
        return a.Z.CompareTo(b.Z);
    }
}

public static class PointHelper {
    public static IEnumerable<Point3d> RotateBy(
        this List<Point3d> pts, double angle, Vector3d axis, Point3d center) {
        foreach (var pt in pts) {
            yield return pt.RotateBy(angle, axis, center);
        }
    }

    public static GetMidPoint(this Point3d a, Point3d b) {
        var x = a.X/2 + b.X/2;
        var y = a.Y/2 + b.Y/2;
        var z = a.Z/2 + b.Z/2;
        return new Point3d(x,y,z);    
    }
}

public static class EditorHelper{
/// <summary>
/// 获取坐标系统三维
/// </summary>
/// <param name="ed"></param>
/// <param name="wcs"></param>
/// <param name="ucs"></param>
public static void GetWcsUcs(this Editor ed,
    out CoordinateSystem3d wcs, out CoordinateSystem3d ucs) {
    Matrix3d Ide_wcs = Matrix3d.Identity;//获取世界坐标系
    wcs = Ide_wcs.CoordinateSystem3d;
    Matrix3d used_ucs = ed.CurrentUserCoordinateSystem;//当前用户坐标系
    ucs = used_ucs.CoordinateSystem3d;
}
}

(完)

posted @   惊惊  阅读(1184)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
历史上的今天:
2019-02-26 测试篇 c#获取所有已经安装的程序
2019-02-26 cad.net 设置Acad2008默认启动 win10设置默认cad2008默认启动
点击右上角即可分享
微信分享提示