找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 5932|回复: 1

[分享] Creating a selection filter that finds dynamic blocks in AutoCAD using .NET

[复制链接]

已领礼包: 6个

财富等级: 恭喜发财

发表于 2013-6-12 20:23:37 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

An interesting question came in via email from Rob Outman. He’s interested in applying a selection filter when the user selects dynamic blocks. This is straightforward for unmodified dynamic blocks – just as with standard blocks, you can filter on the block name very easily – but it works less well on dynamic blocks whose properties have been modified at an instance level.
Essentially what happens is this: if you select a block reference to a dynamic block in the AutoCAD editor and then use (for example) the Properties window to edit some of the custom properties associated with that block, the block definition gets duplicated as an anonymous block – with the modified properties, of course – and the reference gets updated to point to that. If you LIST the block reference, you can see that it still mentions the originating block by name, so it’s clear some connection still exists between the two block definitions, at the very least.
This makes it a little difficult to use a SelectionFilter to look for these BlockReference objects, as their associated BlockTableRecord has a name such as “*U24” rather than the name being searched for.
The answer to this riddle was actually reasonably straightforward, in the end. Using the very handy ArxDbg sample, it was easy to find out that the modified block definition contains XData linking back to the original – under the AcDbBlockRepBTag app name there’s an entry containing its handle – which we can use to compile a list of the (anonymous) names of modified blocks for which to look.
We can then create a conditional SelectionFilter – with an “or” clause listing each of these names – and use that to find any block meeting the condition.
Here’s the C# code that does all this:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using System.Collections.Generic;

namespace EntitySelection
{
  public class Commands
  {
    [CommandMethod("SDB")]
    static public void SelectDynamicBlocks()
    {
      var doc = Application.DocumentManager.MdiActiveDocument;
      var ed = doc.Editor;

      var pso =
        new PromptStringOptions(
          "\nName of dynamic block to search for"
        );
      pso.AllowSpaces = true;

      var pr = ed.GetString(pso);

      if (pr.Status != PromptStatus.OK)
        return;

      string blkName = pr.StringResult;

      List<string> blkNames = new List<string>();
      blkNames.Add(blkName);

      var tr = doc.TransactionManager.StartTransaction();
      using (tr)
      {
        var bt =
          (BlockTable)tr.GetObject(
            doc.Database.BlockTableId,
            OpenMode.ForRead
          );

        // Start by getting the handle of our block, if it exists

        if (!bt.Has(blkName))
        {
          ed.WriteMessage(
            "\nCannot find block called \"{0}\".", blkName
          );
          return;
        }

        var btr =
          (BlockTableRecord)tr.GetObject(
            bt[blkName], OpenMode.ForRead
          );

        var blkHand = btr.Handle;

        foreach (var bid in bt)
        {
          // We'll check each block in turn, to see if it has
          // XData pointing to our original block definition

          var btr2 =
            (BlockTableRecord)tr.GetObject(bid, OpenMode.ForRead);

          // Only check blocks that don't share the name :-)

          if (btr2.Name != blkName)
          {
            // And only check blocks with XData

            var xdata = btr2.XData;
            if (xdata != null)
            {
              // Get the XData as an array of TypeValues and loop
              // through it

              var tvs = xdata.AsArray();
              for (int i=0; i < tvs.Length; i++)
              {
                // The first value should be the RegAppName

                var tv = tvs;
                if (
                  tv.TypeCode == (int)DxfCode.ExtendedDataRegAppName
                )
                {
                  // If it's the one we care about...

                  if ((string)tv.Value == "AcDbBlockRepBTag")
                  {
                    // ... then loop through until we find a
                    // handle matching our blocks or otherwise
                    // another RegAppName

                    for (int j = i + 1; j < tvs.Length; j++)
                    {
                      tv = tvs[j];
                      if (
                        tv.TypeCode ==
                          (int)DxfCode.ExtendedDataRegAppName
                      )
                      {
                        // If we have another RegAppName, then
                        // we'll break out of this for loop and
                        // let the outer loop have a chance to
                        // process this section

                        i = j - 1;
                        break;
                      }

                      if (
                        tv.TypeCode ==
                          (int)DxfCode.ExtendedDataHandle
                      )
                      {
                        // If we have a matching handle...

                        if ((string)tv.Value == blkHand.ToString())
                        {
                          // ... then we can add the block's name
                          // to the list and break from both loops
                          // (which we do by setting the outer index
                          // to the end)

                          blkNames.Add(btr2.Name);
                          i = tvs.Length - 1;
                          break;
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }

        tr.Commit();
      }

      // Build a conditional filter list so that only
      // entities with the specified properties are
      // selected

      SelectionFilter sf =
        new SelectionFilter(CreateFilterListForBlocks(blkNames));
      PromptSelectionResult psr = ed.SelectAll(sf);

      ed.WriteMessage(
        "\nFound {0} entit{1}.",
        psr.Value.Count,
        (psr.Value.Count == 1 ? "y" : "ies")
      );
    }

    private static TypedValue[] CreateFilterListForBlocks(
      List<string> blkNames
    )
    {
      // If we don't have any block names, return null

      if (blkNames.Count == 0)
        return null;

      // If we only have one, return an array of a single value

      if (blkNames.Count == 1)
        return new TypedValue[] {
          new TypedValue(
            (int)DxfCode.BlockName,
           blkNames[0]
          )
        };

      // We have more than one block names to search for...

      // Create a list big enough for our block names plus
      // the containing "or" operators

      List<TypedValue> tvl =
        new List<TypedValue>(blkNames.Count + 2);

      // Add the initial operator

      tvl.Add(
        new TypedValue(
          (int)DxfCode.Operator,
          "<or"
        )
      );

      // Add an entry for each block name, prefixing the
      // anonymous block names with a reverse apostrophe

      foreach (var blkName in blkNames)
      {
        tvl.Add(
          new TypedValue(
            (int)DxfCode.BlockName,
            (blkName.StartsWith("*") ? "`" + blkName : blkName)
          )
        );
      }

      // Add the final operator

      tvl.Add(
        new TypedValue(
          (int)DxfCode.Operator,
          "or>"
        )
      );

      // Return an array from the list

      return tvl.ToArray();
    }
  }
}

An interesting point to note: we need to prefix any anonymous block name with an inverted apostrophe (“`”) character, as that seems to be the way to “escape” asterisks in this situation.
Let’s see this in action. We’ll start by loading the standard “Dynamic Blocks/Annotation – Metric.dwg” sample into AutoCAD, and inserting a number of “Elevation – Metric” dynamic blocks into the drawing:
We can then use the Properties window to edit the Custom properties on each:
Which leads to branched block definitions:
We can then use the SDB command to select our the block references for both the original dynamic block definition and its modified, branched copies.
Command: SDB
Name of dynamic block to search for: Elevation - Metric
Found 5 entities.

Update:
Thanks to Roland Feletic for pointing me to the GetAnonymousBlockIds() function that I’d manage to overlook or forget about. Roland passed on some code he’d received from Tony Tanzillo, so thanks to Tony, too (although all I took was the method name :-).
The use of this function does simplify the code nicely, as well as reducing the dependence on the XData link (which is ultimately an implementation detail).
Here’s the updated C# code, which works in the same way as the prior version:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using System.Collections.Generic;

namespace EntitySelection
{
  public class Commands
  {
    [CommandMethod("SDB")]
    static public void SelectDynamicBlocks()
    {
      var doc = Application.DocumentManager.MdiActiveDocument;
      var ed = doc.Editor;

      var pso =
        new PromptStringOptions(
          "\nName of dynamic block to search for"
        );
      pso.AllowSpaces = true;

      var pr = ed.GetString(pso);

      if (pr.Status != PromptStatus.OK)
        return;

      string blkName = pr.StringResult;

      List<string> blkNames = new List<string>();
      blkNames.Add(blkName);

      var tr = doc.TransactionManager.StartTransaction();
      using (tr)
      {
        var bt =
          (BlockTable)tr.GetObject(
            doc.Database.BlockTableId,
            OpenMode.ForRead
          );

        // Start by getting access to our block, if it exists

        if (!bt.Has(blkName))
        {
          ed.WriteMessage(
            "\nCannot find block called \"{0}\".", blkName
          );
          return;
        }

        // Get the anonymous block names
        var btr =
          (BlockTableRecord)tr.GetObject(
            bt[blkName], OpenMode.ForRead
          );

        if (!btr.IsDynamicBlock)
        {
          ed.WriteMessage(
            "\nCannot find a dynamic block called \"{0}\".", blkName
          );
          return;
        }

        // Get the anonymous blocks and add them to our list

        var anonBlks = btr.GetAnonymousBlockIds();
        foreach (ObjectId bid in anonBlks)
        {
          var btr2 =
            (BlockTableRecord)tr.GetObject(bid, OpenMode.ForRead);

          blkNames.Add(btr2.Name);
        }

        tr.Commit();
      }

      // Build a conditional filter list so that only
      // entities with the specified properties are
      // selected

      SelectionFilter sf =
        new SelectionFilter(CreateFilterListForBlocks(blkNames));
      PromptSelectionResult psr = ed.SelectAll(sf);

      ed.WriteMessage(
        "\nFound {0} entit{1}.",
        psr.Value.Count,
        (psr.Value.Count == 1 ? "y" : "ies")
      );
    }

    private static TypedValue[] CreateFilterListForBlocks(
      List<string> blkNames
    )
    {
      // If we don't have any block names, return null

      if (blkNames.Count == 0)
        return null;

      // If we only have one, return an array of a single value

      if (blkNames.Count == 1)
        return new TypedValue[] {
          new TypedValue(
            (int)DxfCode.BlockName,
           blkNames[0]
          )
        };

      // We have more than one block names to search for...

      // Create a list big enough for our block names plus
      // the containing "or" operators

      List<TypedValue> tvl =
        new List<TypedValue>(blkNames.Count + 2);

      // Add the initial operator

      tvl.Add(
        new TypedValue(
          (int)DxfCode.Operator,
          "<or"
        )
      );

      // Add an entry for each block name, prefixing the
      // anonymous block names with a reverse apostrophe

      foreach (var blkName in blkNames)
      {
        tvl.Add(
          new TypedValue(
            (int)DxfCode.BlockName,
            (blkName.StartsWith("*") ? "`" + blkName : blkName)
          )
        );
      }

      // Add the final operator

      tvl.Add(
        new TypedValue(
          (int)DxfCode.Operator,
          "or>"
        )
      );

      // Return an array from the list

      return tvl.ToArray();
    }
  }
}




评分

参与人数 1D豆 +2 收起 理由
ScmTools + 2 很给力!经验;技术要点;资料分享奖!

查看全部评分

论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!

已领礼包: 343个

财富等级: 日进斗金

发表于 2013-6-13 18:31:05 | 显示全部楼层
好像国内的标准不是这样的吧
论坛插件加载方法
发帖求助前要善用【论坛搜索】功能,那里可能会有你要找的答案;
如果你在论坛求助问题,并且已经从坛友或者管理的回复中解决了问题,请把帖子标题加上【已解决】;
如何回报帮助你解决问题的坛友,一个好办法就是给对方加【D豆】,加分不会扣除自己的积分,做一个热心并受欢迎的人!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|辽公网安备|晓东CAD家园 ( 辽ICP备15016793号 )

GMT+8, 2024-4-27 10:51 , Processed in 0.226876 second(s), 37 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表