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参数上面有这个选项.
(完)

posted @ 2022-03-13 22:55  惊惊  阅读(1587)  评论(1编辑  收藏  举报