cad.net 查找块定义的全部引用
查找块定义的全部引用
查找块定义的全部引用,也就是图纸上的同名块.
一共有两种方案,
1,遍历全图;
2,通过cad内部储存机制(内部的索引组织表);
遍历全图必然比自带的获取慢,虽然也没有慢多少就是了.
其中获取嵌套块的id,大家可以自己推导一下规律...
brRec要是块参照的,若是模型空间就什么都获取不了.
btRec.GetBlockReferenceIds(true,true);
参数一:true不获取嵌套的最外层.
参数二:一直是true用于旧图元修复.
画一个圆组块A,
复制块A直接再组一层块B.
命令选择块A:
下面是参数一改变引起返回值不同:
参数一为false,会一直上浮嵌套,直到最顶,用于删除根.
块名:A;;;Handle是:1E3 (块A,独立的)
块名:A;;;Handle是:1EA (块A,组在B内)
块名:B;;;Handle是:1EB (块B)
块名:B;;;Handle是:1F2 (块B,组在C内)
块名:C;;;Handle是:1F3 (块C)
块名:C;;;Handle是:1FC (块C,组在D内)
块名:D;;;Handle是:1FD (块D)
参数一为true,只会冒出本体id.
块名:A;;;Handle是:1E3 (块A,独立的)
块名:A;;;Handle是:1EA (块A,组在B内)
原理分析CSharp代码
#if !HC2020
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.EditorInput;
using Acap = Autodesk.AutoCAD.ApplicationServices.Application;
#else
using GrxCAD.DatabaseServices;
using GrxCAD.Runtime;
using GrxCAD.EditorInput;
using Acap = GrxCAD.ApplicationServices.Application;
#endif
using System;
namespace JoinBox;
public class GetBlockReferenceIds {
[CommandMethod("CmdTest_GetBlockReferenceIds")]
public void CmdTest_GetBlockReferenceIds()
{
Env.Editor.WriteMessage("\n根据块名获取所有插入块图元名列表:");
using DBTans tr = new();
PlanC();
}
// 方案一: cad自带的进行查询,比遍历块
public void PlanA() {
Env.Editor.WriteMessage("\n选择图元");
//定义选择集选项
var pso = new PromptSelectionOptions
{
RejectObjectsOnLockedLayers = true, //选择锁定图层对象
AllowDuplicates = true, //允许重复选择
};
var ssPsr = ed.GetSelection(pso);
if (ssPsr.Status != PromptStatus.OK)
return;
foreach (var id in ssPsr.Value.GetObjectIds()) {
if (!id.IsOk()) continue;
var ent = tr.GetObject(id,OpenMode.ForRead);
if (ent is not BlockReference brf) continue;
var btRec = (BlockTableRecord)tr.GetObject(brf.BlockTableRecord, OpenMode.ForRead);
ed.WriteMessage("\nfalse参数++++++++++++++++++++++++++");
var brfIds1 = btRec.GetBlockReferenceIds(false, true);
foreach (ObjectId item in brfIds1) {
if (tr.GetObject(item, OpenMode.ForRead) is BlockReference brf2)
ed.WriteMessage($"\n块名:{brf2.Name};;;Handle是:{brf2.Handle}");
}
ed.WriteMessage("\ntrue参数++++++++++++++++++++++++++");
var brfIds2 = btRec.GetBlockReferenceIds(true, true);
foreach (ObjectId item in brfIds2)
if (tr.GetObject(item, OpenMode.ForRead) is BlockReference brf2)
ed.WriteMessage($"\n块名:{brf2.Name};;;Handle是:{brf2.Handle}");
}
} //ss foreach
}
// 方案二:遍历模型空间,获取同名动态块数量
public void PlanB() {
var tr = DBTans.Top;
var id = SymbolUtilityServices.GetBlockModelSpaceId(db);
var modelSpace = (BlockTableRecord)tr.GetObject(id, OpenMode.ForRead);
var insert = RXObject.GetClass(typeof(BlockReference)).DxfName;
var blocks = modelSpace.Cast<ObjectId>()
//.Where(id => id.ObjectClass.DxfName == insert)
.Where(id => id.GetDxfName(tr) == insert) // 如果是块参照,通过DxfName会比typeName慢
.Select(id => (BlockReference)tr.GetObject(id, OpenMode.ForRead))//打开定义
.GroupBy(brf => ((BlockTableRecord)tr.GetObject(brf.DynamicBlockTableRecord, OpenMode.ForRead)).Name);//获取真实的块名
foreach (var group in blocks)
ed.WriteMessage($"\n{group.Key}: {group.Count()}");
}
测试区
本例子关联:
https://www.cnblogs.com/JJBox/p/12489648.html#_lab2_1_0
net35没有 .AsParallel 方法
// 方案三: 卡死
// 并行遍历块表,数量大会卡死,
// 可能因为读取超过OpenMode.Read 256限制.
public void PlanC() {
var tr = DBTans.Top;
// 查询图元不涉及数据库更改,改为并行查询.
// 限制并行操作为CPU核心数
int degreeOfParallelism = System.Environment.ProcessorCount;
// 获取块表
var table = (BlockTable)tr.GetObject(tr.Database.BlockTableId);
// 并行查询
var entitys = table.Cast<ObjectId>()
.AsParallel() //.AsEnumerable() 顺序执行
.WithDegreeOfParallelism(degreeOfParallelism)
.Where(btrId => btrId.IsOk())
.Select(btrId => (BlockTableRecord)tr.GetObject(btrId, OpenMode.ForRead))
.SelectMany(btr => btr.Cast<ObjectId>().Where(eid => eid.IsOk())) // 扁平化时候过滤条件,减少合并大小.
.Select(eid => tr.GetObject(eid, OpenMode.ForRead))
.Where(ent => ent is BlockReference); // 得到图元
// 名称分组,延迟执行,所以这里仍然是并行的
var blocks = entitys.GroupBy(brf => (
(BlockTableRecord)tr.GetObject(brf.DynamicBlockTableRecord, OpenMode.ForRead)
).Name
);//获取真实的块名
// 顺序执行,虽然打印是可以并行多线程的.
blocks.AsSequential().ForEach(group => {
Env.Editor.WriteMessage($"\n{group.Key}: {group.Count()}");
});
}
// 方案四: 重复命令失败
// 并行遍历是成功的,输出也是成功的,
// 问题一: 利用LinqSelect的话,缺少Dispose或者异步Dispose,画图会报错,提示已经打开.
// 问题二: 把Dispose写入之后,第一次有值,第二次为0
// 怀疑是通过块表迭代器的话,游离指针会错误定位,
// 就像fileStream.Position没有设置回0,导致重复命令后无法获取.
// 不过利用了句柄遍历也存在同样的问题,见本文本节最上面的链接.
// 替代方案:
// 1,改为串行遍历.
// 2,用开源DWG工程,只读模式打开并遍历,执行任务.
[CommandMethod(nameof(PlanD))] // CommandFlags.Session
public void PlanD() {
static bool IsOkEx(ObjectId id){
return !(id.IsNull || id.IsErased || id.IsEffectivelyErased);
}
static string GetBlockName(ObjectId eid) {
var ent = eid.Open(OpenMode.ForRead);
string name = "";
if (ent is BlockReference brf && IsOkEx(brf.DynamicBlockTableRecord)) {
var btr = (BlockTableRecord)brf.DynamicBlockTableRecord.Open(OpenMode.ForRead);
name = btr.Name;
btr.Dispose();
}
ent.Dispose();
return name;
}
static List<ObjectId> BtrToEntityIds(ObjectId btrId) {
List<ObjectId> retIds = [];
var btr = (BlockTableRecord)btrId.Open(OpenMode.ForRead);
foreach(var eid in btr){
if(IsOkEx(eid))
retIds.Add(eid);
}
btr.Dispose();
return retIds;
}
// 锁文档
// using DocumentLock docLocker = Env.Document.LockDocument();
// 查询图元不涉及数据库更改,改为并行查询.
// 限制并行操作为CPU核心数
int degreeOfParallelism = System.Environment.ProcessorCount;
// 获取块表
var table = (BlockTable)Env.Database.BlockTableId.Open(OpenMode.ForRead);
// 由于使用PLinq之后重复命令没有数据.
// 创建迭代器,避免无法重置迭代器.更为正确是迭代器内写Reset方法重置?
// 块表不能使用这个方法,因为它是C++的迭代器,这里只有接口.
// 反编译ARX迭代器的计数器,并设置回0.
// var iterator = table.AsEnumerable().Cast<ObjectId>()
// 并行查询
var map = table.Cast<ObjectId>()
.AsParallel()
.WithDegreeOfParallelism(degreeOfParallelism)
.Where(btrId => IsOkEx(btrId))
.SelectMany(btrId => BtrToEntityIds(btrId))
.GroupBy(eid => GetBlockName(eid)) // 名称分组
.ToList();
// 顺序执行,虽然打印是可以并行多线程的.
// ToList之后不用AsSequential()了
map.ForEach(group => {
Env.Editor.WriteMessage($"\n{group.Key}: {group.Count()}");
});
// 把迭代器归位到0.
// 并行之后提权,会报错eHadMultipleReaders
// 所以重新关闭再打开.
table.Close();
table.Dispose();
table = (BlockTable)Env.Database.BlockTableId.Open(OpenMode.ForRead);
table.UpgradeOpen();
int a = 0;
foreach(var item in table){
a++;
}
Env.Editor.WriteMessage($"\n迭代器剩余:{a}个");
table.DowngradeOpen();
table.Dispose();
}
}
清理案例
清理垃圾的案例,它很好地说明了使用方式.
https://adndevblog.typepad.com/autocad/daniel-du
[CommandMethod(nameof(ClearUnrefedBlocks))]
public void ClearUnrefedBlocks() {
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
using Transaction trans = db.TransactionManager.StartTransaction();
var bt = (BlockTable)trans.GetObject(db.BlockTableId, OpenMode.ForWrite);
foreach (ObjectId oid in bt) {
var btr = (BlockTableRecord)trans.GetObject(oid,OpenMode.ForWrite);
if (btr.GetBlockReferenceIds(false, false).Count == 0
&& !btr.IsLayout) {
btr.Erase();
}
}
trans.Commit();
}
原理分析Lisp代码
;;根据块名获取所有插入块图元名列表
(defun GetBlockReferenceIds (blkName / blkHeader dxf330 elist lst @sk_dxf)
(defun @sk_dxf (ent code)
(cdr (assoc code (entget ent)))
)
(if (setq blkHeader (tblobjname "block" blkname))
(progn
(setq dxf330 (@sk_dxf blkHeader 330))
(setq elist (entget dxf330))
(while (setq a (car elist))
(if (= (car a) 331)
(setq lst (cons (cdr a) lst))
)
(setq elist (cdr elist))
)
(reverse lst)
)
)
)
相关问题
0x01
GetNext得到的Entity,怎么能获取它所在的块参照呢?
ent.BlockId和ent.BlockName是块表记录.
答案:不可能.
块内图元直接get的话,因为是块表记录着图元,不是储存在模型同级的.
也就是每次在为编辑都拷贝一份出来,
然后建立一个映射表Map显示在模型(概念),
保存的时候拷贝数据回去.
图元和图元之间没有从属关系,
所以只有块表记录储存图元信息,因此不存在这样的操作...
要修改GetNext的图元也会作用在块表记录上(全体参照).
如果图元和图元之间有从属关系,
那么就会导致我修改这个图元所属,
便不需要打开另一个图元记录,造成权限溢出.
也会导致,不是块参照的图元拥有了另一个图元,造成歧义.
同时也是因为id就是用来避免菱形引用的.
(绑定是基于事件进行的,不是数据层进行的)
0x02
那么要怎么GetNext到块内块上面,而不是到entity.
答: GetNext参数上面有这个选项.
(完)