cad.net 链式选择和断分标注
说明
由于cad自身的图元特性,
标注作为一个复合型图元却没有做到磁性选择,
我们非自定义图元的时候只能通过链式选择和断分标注模拟.
磁性选择:
当存在共同点的时候,可以直接吸附起来,跟磁铁特性一样,
不是组也不是块参照,而是基于之间的状态,
组可以被s命令点击移动单个图元.
块参照只能移动整体,块的特性页(ctrl+1)不显示标注内容.
所以才有磁铁体,它应该显示夹点和可以直接修改磁铁体内的图元,
我们可以利用选择集事件+维护缓存实现.
标注/引线/文字,自动吸附起,
解开才需要命令,可惜cad并没有这样的东西,比较失望.
链式选择:
先选择所有图元,
然后判断点击的图元共同点碰撞到下一个图元的公共点就选择起来.
断分标注:
直接看动图即可.
源泉的作者告诉阿惊说,
标注避让:
思路是上下左右移动文字,然后有优先排序的方式,
例如优先的是上下上下上下上下的排序.
但是阿惊现在已经没空完成这个函数了,若完成你就提供出来给大家吧.
动图演示
代码
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;
}
}
(完)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 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默认启动