后台选择集
我把20210510日志贴到此处了,并且删除了日志(些许编辑):
需要1,cad自带的函数ssget如果进行矩形范围选择,那么需要设置屏幕范围可见,API上面提供了w选和c选.
需求2,后台开图没有ssget.
这个时候不妨制作一个自己的选择模式,不用可见:
遍历模型块表记录,图元采样点集,有一点在矩形中就是c选,全部在矩形中就是w选.
你可能认为cad的图元显示应该有一个图元四叉树,显示切换通过树快速拿到对应图元.
但是我都用可行性来说话,首先出库入库经过的管理器和那棵树没开放API,其次每次调用显示就会经过很多层设备IO,
所以这些都是导致了设置可见反而比较慢,
那么就测试一下看看数据,e大的22万级图元遍历也就0.024秒,看这个以偏概全的测试,所以要相信大力也能出奇迹!
说不定cad真就没有这颗树,因为桌子考虑问题的方式一般都是,可能不是最快的结果,但是这个demo最小,能跑就行,否则复杂度上去了就会有连锁反应...(谁要优化小数点后面的秒数呢,其实可以从一些开源cad看这方面的显示处理...
如果要认真处理这个问题,要测试不同量级,计算IO的消耗和遍历比较.
因为遍历可以一次就可以分析多个矩形范围,所以越是多个矩形,那么它效率越高,相反多个IO和多次从树中取果,也增耗时.
例如
10万图元,五个区域(内部要放固定数量的图元),切换ssget的c模式和w模式
30万级.
50万级.
100万级.
从Acad2018版起,ssget不再可见范围了,而是全图.
所以本例更是适用在后台.
代码
本例没有进行耗时操作测试,只是单纯想知道后台选择集怎么制作,所以就敲了以下的代码...
也仅仅做了c选,没有w选...日后再仔细完成...不过能看懂的应该都可以自己完善了...
主要完成了核心: 多边形选择范围/嵌套块/参照线
命令
namespace JoinBox
{
public class 后台选择集
{
[CommandMethod("Test_SsgetEntity")]
public void Test_SsgetEntity()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;//当前的数据库
ed.WriteMessage("
****{惊惊盒子}后台选择集:");
// 获取当前空间块表记录
List<ObjectId> idsInit = new();
db.Action(tr =>
{
// 块表
var acBlkTbl = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
// 当前空间的块表记录
var acBlkTblRec = tr.GetObject(db.CurrentSpaceId, OpenMode.ForRead) as BlockTableRecord;
idsInit = acBlkTblRec.Toids();
}, false);
//选择集矩形范围
var pts = new List<Point3d>()
{
new Point3d(0, 0, 0),
new Point3d(0, 1000, 0) ,
new Point3d(1000, 1000, 0) ,
new Point3d(1000, 0, 0) ,
};
var idsOut = new List<ObjectId>();
db.Ssget(pts, idsInit, idsOut);
db.Action(tr =>
{
foreach (var id in idsOut)
{
var ent = id.ToEntity(tr);
ent.UpgradeOpen();
ent.ColorIndex = 11;
if (ent is BlockReference brf)
{
ChangColorIndex(brf, tr);
}
ent.DowngradeOpen();
}
});
}
// 递归块改颜色..
void ChangColorIndex(BlockReference brf, Transaction tr)
{
var btRec = tr.GetObject(brf.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord;
foreach (var item in btRec)
{
var entItem = item.ToEntity(tr);
if (entItem is BlockReference brfItem)
{
ChangColorIndex(brfItem, tr);//进入递归
}
entItem.UpgradeOpen();
entItem.ColorIndex = 0;//随块
entItem.DowngradeOpen();
entItem.Dispose();
}
}
}
}
函数
/*静态调用*/
namespace JoinBox
{
public static partial class GetEntityTool
{
/// <summary>
/// 后台选择集
/// </summary>
/// <param name="db">数据库</param>
/// <param name="pts">选择集的多段线范围</param>
/// <param name="idsInit">输入来筛选的ids,来自于当前空间</param>
/// <param name="idsOut">输出筛选后的id</param>
public static void Ssget(this Database db, IEnumerable<Point3d> pts, IEnumerable<ObjectId> idsInit, List<ObjectId> idsOut)
{
var ss = new JoinBoxSsget(db, idsInit, pts);
idsOut.AddRange(ss.IdsOut);
}
}
}
/*封装*/
namespace JoinBox
{
public class JoinBoxSsget
{
/// <summary>
/// 用于输出的id
/// </summary>
public List<ObjectId> IdsOut { get; private set; }
/// <summary>
/// 后台选择集
/// </summary>
/// <param name="db">数据库</param>
/// <param name="pts">边界点集</param>
/// <param name="idsInit">输入需要过滤的id,一般为当前空间</param>
public JoinBoxSsget(Database db, IEnumerable<ObjectId> idsInit, IEnumerable<Point3d> pts)
{
if (idsInit.IsNull())
{
throw new ArgumentNullException("SsgetRectangleEntity图元集合为空");
}
if (pts.IsNull())
{
throw new ArgumentNullException("SsgetRectangleEntity点集范围为空");
}
var ptsArr = pts.ToArray();
IdsOut = new List<ObjectId>();
db.Action(tr =>
{
foreach (var id in idsInit)
{
var ent = id.ToEntity(tr);
if (ent is BlockReference brf)
{
// 由于块外无法曲线取样,所以只能遍历块内图元,而遍历时候的块基点是0,0开始,
// 所以需要将[边界点集]从[块基点]平移到0,0
var ptsMove = MovePtsArr(ptsArr, brf);
var blockInternalIdsOut = new List<ObjectId>();//储存块内图元id
SetBlock(ptsMove, brf, tr, blockInternalIdsOut);//进入递归,要把块id输出,而不是块内图元id
if (blockInternalIdsOut.Count > 0)
{
IdsOut.Add(brf.ObjectId);//块id输出
}
}
else
{
SetEntity(ptsArr, ent, IdsOut);
}
ent.Dispose();
}
}, false);
}
/// <summary>
/// 边界点集变换到块内
/// </summary>
/// <param name="pts">边界点集</param>
/// <param name="brf">块参照</param>
/// <returns></returns>
private List<Point3d> MovePtsArr(IEnumerable<Point3d> pts, BlockReference brf)
{
var ptsMove = new List<Point3d>();
var enume = pts.GetEnumerator();
while (enume.MoveNext())
{
ptsMove.Add((enume.Current - brf.Position).ToPoint3d());
}
return ptsMove;
}
/// <summary>
/// 嵌套块用递归处理
/// </summary>
void SetBlock(IEnumerable<Point3d> pts, BlockReference brf, Transaction tr, List<ObjectId> idsOut)
{
var btRec = tr.GetObject(brf.BlockTableRecord, OpenMode.ForRead) as BlockTableRecord;
foreach (var item in btRec)
{
var entItem = item.ToEntity(tr);
if (entItem is BlockReference brfItem)
{
var ptmove = MovePtsArr(pts, brfItem);
SetBlock(ptmove, brfItem, tr, idsOut);//进入递归
}
else
{
SetEntity(pts, entItem, idsOut);
if (idsOut.Count > 0)//存在就表示这个块是选择集了
{
return;
}
}
}
}
/// <summary>
/// 非块处理,曲线采样
/// </summary>
void SetEntity(IEnumerable<Point3d> pts, Entity ent, List<ObjectId> idsOut)
{
if (ent is DBPoint bPoint)
{
if (new Point3d(bPoint.Position.X, bPoint.Position.Y, 0).IsPointInPL(pts))
{
idsOut.Add(ent.ObjectId);
}
}
else if (ent is Region region) //面域...还有一些非继承曲线的:遮罩/3D图元
{
//不是很想写了....
}
else if (ent is Xline xline)//参照线
{
// 判断参照线与边界的交点,且交点落在边界上,则为选中
bool yes = Intersection(pts, xline.BasePoint, xline.SecondPoint, false);
if (yes)
{
idsOut.Add(ent.ObjectId);
}
}
else if (ent is Ray ray)//射线
{
// 射线也是直线方程,只是交点位置如果是逆向量的话,就为不存在
bool yes = Intersection(pts, ray.StartPoint, ray.SecondPoint, true);
if (yes)
{
idsOut.Add(ent.ObjectId);
}
}
else if (ent is Curve curve)
{
try
{
var cs = new CurveSample<Point3d>(curve, 300);
foreach (var pt in cs.GetSamplePoints)
{
if (pt.IsPointInPL(pts))
{
idsOut.Add(ent.ObjectId);
break;
}
}
}
catch { }
}
}
/// <summary>
/// 直线方程判断参照线和射线是否在内部
/// </summary>
/// <param name="pts">边界点集</param>
/// <param name="xlPt1">线点</param>
/// <param name="xlPt2">线点</param>
/// <param name="isRay">是否射线</param>
/// <returns></returns>
private static bool Intersection(IEnumerable<Point3d> pts, Point3d xlPt1, Point3d xlPt2, bool isRay)
{
var ptsArr = pts.ToArray();//因为这里要取两个,就不获取枚举了
for (int i = 0; i < ptsArr.Length - 1; i++)
{
// 求两条直线的交点,如果平衡则是null
var jiaodian = JoinBoxCurrency.MathTool.GetIntersectionPoint(
new Pt2(xlPt1.ToArray()),
new Pt2(xlPt2.ToArray()),
new Pt2(ptsArr[i].ToArray()),
new Pt2(ptsArr[i + 1].ToArray()));
if (jiaodian != null)
{
// 在判断交点在选择集边线子段之间,用凸度0判断
var pt_jiaodian = new Point3d(jiaodian.X, jiaodian.Y, 0);
var bulge = MathTool.GetBulge(ptsArr[i], pt_jiaodian, ptsArr[i + 1]);
if (bulge == 0)
{
if (isRay)
{
// 求两射线交点是否成立(起点->尾点)与(起点->交点)的单位向量是同一个方向就加入,否则就是逆向,不加入
var rayPt1 = new Point2d(xlPt1.ToArray());
var rayPt2 = new Point2d(xlPt2.ToArray());
var rayVe1 = rayPt1.GetVectorTo(rayPt2).GetNormal();
var rayVe2 = rayPt1.GetVectorTo(new Point2d(jiaodian.ToArray())).GetNormal();
return rayVe1 == rayVe2;//这里精度居然是对的
}
else
{
return true;
}
}
}
}
return false;
}
}
}
缺省函数链接
JoinBoxCurrency.MathTool.GetIntersectionPoint 数学篇 求两条直线的交点,说明过程
MathTool.GetBulge 数学篇 cad.net 向量+点乘+叉乘+矩阵+凸度
CurveSample 数学篇 cad.net 葛立恒凸包算法和面积最小包围盒
IsPointInPL 射线法
(完)