Создание плагинов для AutoCAD с помощью .NET API (часть 6 – поиск и изменение объектов на чертеже)

  • Tutorial
Это шестая часть цикла про разработку плагинов для AutoCAD. В ней поговорим про поиск объектов на чертеже, а также про их изменение.

public static string disclaimer = "Автор не является профессиональным разработчиком и не обладает глубокими знаниями AutoCAD. Этот пост – просто небольшой рассказ о создании плагина.";


Введение


В первой части статьи мы рассмотрим поиск объектов на чертеже, во второй — коротко поговорим про их изменение. Однако прежде чем начать рассмотрение этих вопросов, давайте подготовим каркас тестового примера, который мы будем использовать на протяжении всей статьи.

Пример будет несложным, но довольно объемным. Мы создадим два слоя с именами «layer-1» и «layer-2» (итого вместе с нулевым слоем, который есть в каждом чертеже, получится три слоя). Также мы добавим два определения блока с именами «block-1» (он будет состоять из окружности, линии и полилинии) и «block-2» (он будет состоять из окружности и двух линий). После этого поместим на чертеж несколько графических объектов:
  • на нулевой слой: три линии, полилинию, окружность, текст;
  • на слой «layer-1»: две линии, окружность, текст, вхождение блока «block-1»;
  • на слой «layer-2»: линию, дугу, окружность, вхождение блока «block-1», вхождение блока «block-2».

Ну и для полного счастья мы раскрасим все это дело в розовый, голубой и салатовый цвета.

NB:
Выбор не слишком стандартных цветов обусловлен тем, что подготавливать примеры кода к статьям — тоска смертная, и хочется хоть как-то этот процесс оживить.
Раскрасив первые линии, я вдруг вспомнил про Радужный флаг и подумал даже, не вставить ли его в статью…
Но во-вторых, я не так уж сильно поддерживаю это движение, а во-первых, флаг — это сильно сложнее трех линеек. Ну его нафиг.

Код, реализующий этот план действий, приведен далее. Все выполняемые операции: создание слоев и определений блока, вставка текста, графических примитивов и вхождений блока — мы уже рассматривали в прошлых статьях цикла. Единственное, что еще не рассматривалось, — это задание слоя и цвета для объекта чертежа, но оно занимает буквально одну строку кода и, я надеюсь, сложностей не вызовет.

Итак, создаем проект, выполняем первоначальную настройку (указываем версию .NET, отключаем CopyLocal) и подключаем уже привычные библиотеки AcMgd и AcDbMgd. Далее помещаем туда функции, создающие объекты для нашего примера.

Код:
using System;
using System.Collections.Generic;

using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using acad = Autodesk.AutoCAD.ApplicationServices.Application;

namespace HabrPlug_SearchAndRescue
{
    public class ClassMyAutoCADDLL_SearchAndRescue
    {
        public class Commands : IExtensionApplication
        {
            // используемые цвета
            Autodesk.AutoCAD.Colors.Color color_Pink = Autodesk.AutoCAD.Colors.Color.FromRgb(255, 128, 255);
            Autodesk.AutoCAD.Colors.Color color_Blue = Autodesk.AutoCAD.Colors.Color.FromRgb(0, 200, 255);
            Autodesk.AutoCAD.Colors.Color color_LightGreen = Autodesk.AutoCAD.Colors.Color.FromRgb(128, 255, 64);

            // ID слоев "layer-1" и "layer-2"
            ObjectId layer_1;
            ObjectId layer_2;

            // создаем слои
            public void createLayers()
            {
                // получаем текущий документ и его БД
                Document acDoc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
                Database acCurDb = acDoc.Database;

                // блокируем документ
                using (DocumentLock docloc = acDoc.LockDocument())
                {
                    // начинаем транзакцию
                    using (Transaction tr = acCurDb.TransactionManager.StartTransaction())
                    {
                        // открываем таблицу слоев документа
                        LayerTable acLyrTbl = tr.GetObject(acCurDb.LayerTableId, OpenMode.ForWrite) as LayerTable;

                        // создаем новый слой и задаем ему имя
                        LayerTableRecord acLyrTblRec_1 = new LayerTableRecord();
                        acLyrTblRec_1.Name = "layer-1";
                        // заносим созданный слой в таблицу слоев, сохраняем ID созданной записи слоя
                        layer_1 = acLyrTbl.Add(acLyrTblRec_1);
                        // добавляем созданный слой в документ
                        tr.AddNewlyCreatedDBObject(acLyrTblRec_1, true);


                        // создаем новый слой и задаем ему имя
                        LayerTableRecord acLyrTblRec_2 = new LayerTableRecord();
                        acLyrTblRec_2.Name = "layer-2";
                        // заносим созданный слой в таблицу слоев, сохраняем ID созданной записи слоя
                        layer_2 = acLyrTbl.Add(acLyrTblRec_2);
                        // добавляем созданный слой в документ
                        tr.AddNewlyCreatedDBObject(acLyrTblRec_2, true);

                        // фиксируем транзакцию
                        tr.Commit();
                    }
                }
            }

            // создаем определение блока "block-1"
            public void createBlock_1()
            {
                // получаем ссылки на документ и его БД
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // имя создаваемого блока
                const string blockName = "block-1";

                // начинаем транзакцию
                Transaction tr = db.TransactionManager.StartTransaction();
                using (tr)
                {
                    // открываем таблицу блоков на запись
                    BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForWrite);

                    // проверяем, нет ли в таблице блока с таким именем; если есть - заканчиваем выполнение команды
                    if (bt.Has(blockName))
                    {
                        return;
                    }

                    // создаем новое определение блока, задаем ему имя
                    BlockTableRecord btr = new BlockTableRecord();
                    btr.Name = blockName;

                    // добавляем созданное определение блока в таблицу блоков и в транзакцию
                    bt.Add(btr);
                    tr.AddNewlyCreatedDBObject(btr, true);

                    // добавляем к блоку элементы

                    // создаем окружность
                    Circle acCircle = new Circle();
                    // устанавливаем параметры созданного объекта
                    acCircle.SetDatabaseDefaults();
                    acCircle.Center = Point3d.Origin;
                    acCircle.Radius = 25;
                    // добавляем созданный объект в определение блока и в транзакцию
                    btr.AppendEntity(acCircle);
                    tr.AddNewlyCreatedDBObject(acCircle, true);

                    // создаем линию
                    Line acLine = new Line(new Point3d(18, 18, 0), new Point3d(35, 35, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine.SetDatabaseDefaults();
                    // добавляем созданный объект в определение блока и в транзакцию
                    btr.AppendEntity(acLine);
                    tr.AddNewlyCreatedDBObject(acLine, true);

                    // создаем полилинию
                    Polyline acPolyline = new Polyline();
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acPolyline.SetDatabaseDefaults();
                    // добавляем к полилинии вершины
                    acPolyline.AddVertexAt(0, new Point2d(20, 35), 0, 0, 0);
                    acPolyline.AddVertexAt(1, new Point2d(35, 35), 0, 0, 0);
                    acPolyline.AddVertexAt(2, new Point2d(35, 20), 0, 0, 0);
                    // добавляем созданный объект в определение блока и в транзакцию
                    btr.AppendEntity(acPolyline);
                    tr.AddNewlyCreatedDBObject(acPolyline, true);

                    // фиксируем транзакцию
                    tr.Commit();
                }
            }

            // создаем определение блока "block-2"
            public void createBlock_2()
            {
                // получаем ссылки на документ и его БД
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // имя создаваемого блока
                const string blockName = "block-2";

                // начинаем транзакцию
                Transaction tr = db.TransactionManager.StartTransaction();
                using (tr)
                {
                    // открываем таблицу блоков на запись
                    BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForWrite);

                    // проверяем, нет ли в таблице блока с таким именем; если есть - заканчиваем выполнение команды
                    if (bt.Has(blockName))
                    {
                        return;
                    }

                    // создаем новое определение блока, задаем ему имя
                    BlockTableRecord btr = new BlockTableRecord();
                    btr.Name = blockName;

                    // добавляем созданное определение блока в таблицу блоков и в транзакцию
                    bt.Add(btr);
                    tr.AddNewlyCreatedDBObject(btr, true);

                    // добавляем к блоку элементы

                    // создаем окружность
                    Circle acCircle = new Circle();
                    // устанавливаем параметры созданного объекта
                    acCircle.SetDatabaseDefaults();
                    acCircle.Center = Point3d.Origin;
                    acCircle.Radius = 25;
                    // добавляем созданный объект определение блока и в транзакцию
                    btr.AppendEntity(acCircle);
                    tr.AddNewlyCreatedDBObject(acCircle, true);

                    // создаем первую линию
                    Line acLine_1 = new Line(new Point3d(0, -25, 0), new Point3d(0, -50, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine_1.SetDatabaseDefaults();
                    // добавляем созданный объект в определение блока и в транзакцию
                    btr.AppendEntity(acLine_1);
                    tr.AddNewlyCreatedDBObject(acLine_1, true);

                    // создаем вторую линию
                    Line acLine_2 = new Line(new Point3d(-7, -39, 0), new Point3d(7, -39, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine_2.SetDatabaseDefaults();
                    // добавляем созданный объект в определение блока и в транзакцию
                    btr.AppendEntity(acLine_2);
                    tr.AddNewlyCreatedDBObject(acLine_2, true);

                    // фиксируем транзакцию
                    tr.Commit();
                }
            }

            // создаем объекты на нулевом слое
            public void layer_0_createObjects()
            {
                // получаем текущий документ и его БД
                Document doc = acad.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // открываем таблицу блоков документа
                    BlockTable acBlkTbl;
                    acBlkTbl = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;

                    // открываем пространство модели (Model Space) - оно является одной из записей в таблице блоков документа
                    BlockTableRecord ms = tr.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;

                    // добавляем розовую линию
                    Line acLine_1 = new Line(new Point3d(225, 225, 0), new Point3d(225, 175, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine_1.SetDatabaseDefaults();
                    // устанавливаем для объекта нужный слой и цвет
                    acLine_1.Layer = "0";
                    acLine_1.Color = color_Pink;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_1);
                    tr.AddNewlyCreatedDBObject(acLine_1, true);

                    // добавляем голубую линию
                    Line acLine_2 = new Line(new Point3d(250, 225, 0), new Point3d(250, 175, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine_2.SetDatabaseDefaults();
                    // устанавливаем для объекта нужный слой и цвет
                    acLine_2.Layer = "0";
                    acLine_2.Color = color_Blue;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_2);
                    tr.AddNewlyCreatedDBObject(acLine_2, true);

                    // добавляем салатовую линию
                    Line acLine_3 = new Line(new Point3d(275, 225, 0), new Point3d(275, 175, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine_3.SetDatabaseDefaults();
                    // устанавливаем для объекта нужный слой и цвет
                    acLine_3.Layer = "0";
                    acLine_3.Color = color_LightGreen;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_3);
                    tr.AddNewlyCreatedDBObject(acLine_3, true);

                    // добавляем розовую полилинию
                    Polyline acPolyline = new Polyline();
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acPolyline.SetDatabaseDefaults();
                    // добавляем к полилинии вершины
                    acPolyline.AddVertexAt(0, new Point2d(300, 225), 0, 0, 0);
                    acPolyline.AddVertexAt(1, new Point2d(325, 175), 0, 0, 0);
                    acPolyline.AddVertexAt(2, new Point2d(350, 225), 0, 0, 0);
                    // устанавливаем для объекта нужный слой и цвет
                    acPolyline.Layer = "0";
                    acPolyline.Color = color_Pink;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acPolyline);
                    tr.AddNewlyCreatedDBObject(acPolyline, true);

                    // добавляем голубую окружность
                    Circle acCircle = new Circle();
                    // устанавливаем параметры созданного объекта
                    acCircle.SetDatabaseDefaults();
                    acCircle.Center = new Point3d(400, 200, 0);
                    acCircle.Radius = 25;
                    // устанавливаем для объекта нужный слой и цвет
                    acCircle.Layer = "0";
                    acCircle.Color = color_Blue;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acCircle);
                    tr.AddNewlyCreatedDBObject(acCircle, true);

                    // добавляем салатовый текст
                    DBText text = new DBText();
                    text.Position = new Point3d(450, 175, 0);
                    text.Height = 50;
                    text.TextString = "HABR!";
                    // устанавливаем для объекта нужный слой и цвет
                    text.Layer = "0";
                    text.Color = color_LightGreen;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(text);
                    tr.AddNewlyCreatedDBObject(text, true);

                    // фиксируем изменения
                    tr.Commit();
                }
            }

            // создаем объекты на слое "layer-1"
            public void layer_1_createObjects()
            {
                // получаем текущий документ и его БД
                Document doc = acad.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // запоминаем текущий слой и временно меняем его на нужный нам
                    // (это позволит не задавать слой отдельно для каждого создаваемого объекта)
                    ObjectId currentLayer = db.Clayer;
                    db.Clayer = layer_1;

                    // открываем таблицу блоков документа
                    BlockTable acBlkTbl;
                    acBlkTbl = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;

                    // открываем пространство модели (Model Space) - оно является одной из записей в таблице блоков документа
                    BlockTableRecord ms = tr.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;

                    // добавляем розовую линию
                    Line acLine_1 = new Line(new Point3d(225, 25, 0), new Point3d(225, -25, 0));
                    // устанавливаем параметры созданного объекта
                    acLine_1.SetDatabaseDefaults();
                    acLine_1.Color = color_Pink;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_1);
                    tr.AddNewlyCreatedDBObject(acLine_1, true);

                    // добавляем голубую линию
                    Line acLine_2 = new Line(new Point3d(250, 25, 0), new Point3d(250, -25, 0));
                    // устанавливаем параметры созданного объекта
                    acLine_2.SetDatabaseDefaults();
                    acLine_2.Color = color_Blue;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_2);
                    tr.AddNewlyCreatedDBObject(acLine_2, true);

                    // добавляем салатовую окружность
                    Circle acCircle = new Circle();
                    // устанавливаем параметры созданного объекта
                    acCircle.SetDatabaseDefaults();
                    acCircle.Center = new Point3d(300, 0, 0);
                    acCircle.Radius = 25;
                    acCircle.Color = color_LightGreen;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acCircle);
                    tr.AddNewlyCreatedDBObject(acCircle, true);

                    // добавляем розовый текст
                    DBText text = new DBText();
                    // устанавливаем параметры созданного объекта
                    text.Position = new Point3d(350, -25, 0);
                    text.Height = 50;
                    text.TextString = "HABR!";
                    text.Color = color_Pink;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(text);
                    tr.AddNewlyCreatedDBObject(text, true);

                    // добавляем вхождение блока "block-1"
                    // открываем таблицу блоков для чтения
                    BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                    // получаем ObjectID блока
                    ObjectId btrId = bt["block-1"];
                    // создаем новое вхождение блока, используя полученный ID определения блока
                    BlockReference br = new BlockReference(new Point3d(600, 0, 0), btrId);
                    // добавляем вхождение блока на пространство модели и в транзакцию
                    ms.AppendEntity(br);
                    tr.AddNewlyCreatedDBObject(br, true);

                    // возвращаем обратно старый текущий слой
                    db.Clayer = currentLayer;

                    // фиксируем изменения
                    tr.Commit();
                }
            }

            // создаем объекты на слое "layer-2"
            public void layer_2_createObjects()
            {
                // получаем текущий документ и его БД
                Document doc = acad.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // запоминаем текущий слой и временно меняем его на нужный нам
                    // (это позволит не задавать слой отдельно для каждого создаваемого объекта)
                    ObjectId currentLayer = db.Clayer;
                    db.Clayer = layer_2;

                    // открываем таблицу блоков документа
                    BlockTable acBlkTbl;
                    acBlkTbl = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;

                    // открываем пространство модели (Model Space) - оно является одной из записей в таблице блоков документа
                    BlockTableRecord ms = tr.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;

                    // добавляем розовую линию
                    Line acLine_1 = new Line(new Point3d(225, -175, 0), new Point3d(225, -225, 0));
                    // устанавливаем параметры созданного объекта
                    acLine_1.SetDatabaseDefaults();
                    acLine_1.Color = color_Pink;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_1);
                    tr.AddNewlyCreatedDBObject(acLine_1, true);

                    // добавляем голубую дугу
                    Arc acArc = new Arc(new Point3d(250, -200, 0), 25, -45 / 180.0 * Math.PI, 45 / 180.0 * Math.PI);
                    // устанавливаем параметры созданного объекта
                    acArc.SetDatabaseDefaults();
                    acArc.Color = color_Blue;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acArc);
                    tr.AddNewlyCreatedDBObject(acArc, true);

                    // добавляем салатовую окружность
                    Circle acCircle = new Circle();
                    // устанавливаем параметры созданного объекта
                    acCircle.SetDatabaseDefaults();
                    acCircle.Center = new Point3d(325, -200, 0);
                    acCircle.Radius = 25;
                    acCircle.Color = color_LightGreen;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acCircle);
                    tr.AddNewlyCreatedDBObject(acCircle, true);

                    // добавляем вхождение блока "block-1"
                    // открываем таблицу блоков для чтения
                    BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                    // получаем ObjectID блока
                    ObjectId btrId = bt["block-1"];
                    // создаем новое вхождение блока, используя полученный ID определения блока
                    BlockReference br = new BlockReference(new Point3d(400, -200, 0), btrId);
                    // добавляем вхождение блока на пространство модели и в транзакцию
                    ms.AppendEntity(br);
                    tr.AddNewlyCreatedDBObject(br, true);

                    // добавляем вхождение блока "block-2"
                    // получаем ObjectID блока
                    btrId = bt["block-2"];
                    // создаем новое вхождение блока, используя полученный ID определения блока
                    br = new BlockReference(new Point3d(475, -200, 0), btrId);
                    // добавляем вхождение блока на пространство модели и в транзакцию
                    ms.AppendEntity(br);
                    tr.AddNewlyCreatedDBObject(br, true);

                    // возвращаем обратно старый текущий слой
                    db.Clayer = currentLayer;

                    // фиксируем изменения
                    tr.Commit();
                }
            }

            // создаем объекты при загрузке плагина
            public void Initialize()
            {
                createLayers();
                createBlock_1();
                createBlock_2();
                layer_0_createObjects();
                layer_1_createObjects();
                layer_2_createObjects();
            }

            // функция Terminate() необходима, чтобы реализовать интерфейс IExtensionApplication
            public void Terminate()
            {

            }
        }
    }
}

Результат:


Код несложный и принципиальных вопросов вызывать не должен. Давайте быстро пробежимся по некоторым нюансам.

Момент первый: выполнение операций внутри метода Initialize()
Для начала заметим, что создание всех необходимых объектов происходят прямо во время загрузки плагина. Поскольку создаваемые объекты нужны нам для работы примеров и необходимы для работы любой из команд плагина, их вроде бы можно создавать в рамках метода Initialize().

Однако насколько это правильно и разумно — вопрос очень спорный. Во-первых, создавая что-то автоматически, мы тем самым лишаем пользователя выбора и ставим его перед веселым фактом: привет, чувак, у тебя на чертеже теперь десять новых слоев и сто новых объектов!

Весело будет, но не слишком. Особенно пользователю.

Вторая неприятность состоит в том, что пользователь может случайно или намеренно удалить с чертежа некоторые из созданных объектов. И в этом случае он никак не сможет заново создать эти объекты; для этого ему придется закрыть и заново запустить AutoCAD.

В общем, с выполнением действий при загрузке плагина надо быть «нежнее, еще нежнее» (с). Шутки с этим вполне допустимы в учебном примере, однако на практике стоит трижды подумать перед использованием Initialize(). В идеале здесь должны находиться только бетон и металлоконструкции те участки кода, которые:
  1. Никак не помешают пользователю и не создадут излишнюю нагрузку на чертеж.
  2. Никогда не потребуют повторного вызова (либо же у пользователя должен быть способ этот вызов сделать — вспомните пример из абзаца выше про случайное удаление объектов чертежа).

NB:
Сейчас с содроганием зашел в репозиторий с кодом реального проекта, зажмурился, открыл глаза…
Уф, все в порядке. Изложенные выше правила почти соблюдены.

Как там говорится-то? «Кто не может работать — учит»?


Момент второй: задание слоя объектов двумя способами
При заполнении объектами нулевого слоя (функция layer_0_createObjects()) принадлежность к этому слою явно указывается для каждого создаваемого объекта. Пример:

acLine_1.Layer = "0";

Это понятно и несложно, однако при работе с большим количеством объектов можно запросто забыть задать слой, что нехорошо.

NB:
Пример из жизни: автор статьи добросовестно скопипастил в функцию layer_1_createObjects() код для вставки на чертеж линии из функции layer_0_createObjects(), забыв поменять слой на новый. Поиски ошибки и ее исправление заняли какое-то время. Осознав, что так придется делать со всеми примитивами, автор выбесился и решил все сделать другим способом.
Так родилась эта часть статьи.)

Альтернативный способ, уменьшающий вероятность подобной ошибки, был кратко упомянут в одной из предыдущих статей, посвященной слоям. Это использование свойства Clayer.

Суть способа состоит в том, что если при создании объекта мы вообще не укажем ему слой, то он будет присвоен автоматически на основе значения свойства Clayer базы данных текущего документа. Просмотреть это значение можно так:

Document doc = acad.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
ObjectId currentLayer = db.Clayer;

Присваивается значение аналогично:

Document doc = acad.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
db.Clayer = layer_1; // layer_1 - ObjectID нужного слоя

А вот так можно получить ObjectID слоя, зная его имя:

Document acDoc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
Database acCurDb = acDoc.Database;
using (DocumentLock docloc = acDoc.LockDocument())
{
    using (Transaction tr = acCurDb.TransactionManager.StartTransaction())
    {
        LayerTable acLyrTbl = tr.GetObject(acCurDb.LayerTableId, OpenMode.ForWrite) as LayerTable;
        ObjectId layer_objID = acLyrTbl["someLayer"]
    }
}

В нашем примере, чтобы не обращаться каждый раз к таблице слоев, я создал две глобальные переменные — layer_1 и layer_2, в которые сохраняю ObjectID слоев «layer-1» и «layer-2» при их создании. А позже, в процедурах layer_1_createObjects() и layer_2_createObjects(), я использую следующую конструкцию:

// ...
ObjectId currentLayer = db.Clayer;
db.Clayer = layer_1;
// ...
// ДОБАВЛЯЕМ НОВЫЕ ОБЪЕКТЫ
// ...
db.Clayer = currentLayer;
// ...

Зачем нужно сохранять текущее значение Clayer и восстанавливать его в конце функции?
Ну, одна из причин — чтобы пользователь после работы нашей функции смог продолжить добавлять объекты на тот же слой, что и до работы нашей процедуры.
Вторая причина — чтобы самому не путаться и не выяснять потом, почему это в половине запусков объекты добавляются не туда.
Если подключить немого воображения, то, наверное, можно придумать и еще причины. Но зачем?


Момент третий: техника безопасности

Источник: «Ривелти групп»

Хотелось бы еще раз сделать акцент на том, что этот цикл статей рассказывает об основах создания плагинов. Для использования в реальном проекте приведенные примеры должны быть доработаны — в частности, необходимо позаботиться о том, что все используемые объекты реально существуют.

В нашем примере такие проверки не проводятся. Посмотрите: я помещаю графические объекты на созданные ранее слои, помещаю на чертеж вхождения созданных при загрузке блоков — но нигде не проверяю, что эти слои и определения блоков существуют. В реальных проектах пренебрегать такими проверками настоятельно не рекомендуется. Особенно это касается случаев, когда между получением ссылки на объект и операцией над объектом могут произойти какие-нибудь действия пользователя.

В разделе, посвященном редактированию объектов чертежа, я опишу еще несколько ошибок, которые также необходимо отслеживать.


Теперь, когда мы обсудили особенности примера, можно наконец-то перейти к поиску объектов на чертеже. Мы разберем два разных подхода: просмотр объектов чертежа через обращение к объекту ModelSpace и получение идентификаторов объектов чертежа с помощью метода Editor.SelectAll().

NB:
Можно искать объекты и другими способами. Но тут я уже ничем не помогу, ибо лично с ними не сталкивался.

Ближе к концу этой статьи будет ма-аленький бонусный нанораздел, где можно будет оценить масштабы извращений блестящих технических решений, которые приходится использовать людям программистам для оптимизации работы с большим количеством объектов.

1 Поиск объектов чертежа с помощью обращения к объекту ModelSpace


1.1 Итерация по всем объектам чертежа


Для начала давайте посмотрим, как можно перебрать в цикле все объекты, имеющиеся на чертеже.

Принцип простой: открываем пространство модели (ModelSpace) и получаем ссылки на все объекты внутри него. Затем приводим эти объекты к типу Entity и обрабатываем нужные нам свойства.

Пример почти полностью заимствован отсюда (англ.).

Код:
[CommandMethod("Habr_IterateThroughAllObjects_1")]
public void iterateThroughAllObjects()
{
    // получаем текущую БД 
    Database db = HostApplicationServices.WorkingDatabase;

    // начинаем транзакцию
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // получаем ссылку на пространство модели (ModelSpace)
        BlockTableRecord ms = (BlockTableRecord)tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForRead);

        // "пробегаем" по всем объектам в пространстве модели
        foreach (ObjectId id in ms)
        {
            // приводим каждый из них к типу Entity
            Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);
       
            // выводим в консоль слой (entity.Layer), тип (entity.GetType().ToString()) и цвет (entity.Color) каждого объекта
            acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
        }

        tr.Commit();
    }
}

Результат:



Результат, как можно видеть, полностью совпадает с ожиданиями: у нас 16 объектов, цвета которых чередуются.

Из небольших новшеств: в этом примере мы использовали незнакомое нам свойство WorkingDatabase класса HostApplicationServices. Оно позволяет получить БД документа, который активен (имеет фокус ввода) в настоящий момент. Еще одно новшество — метод GetBlockModelSpaceId() класса SymbolUtilityServices, который позволяет быстро получить ObjectId пространства модели (ModelSpace).

Вместо новых классов мы, безусловно, могли бы использовать уже знакомые:
// команда для итерации по объектам (подход 1)
[CommandMethod("Habr_IterateThroughAllObjects_1")]
public void iterateThroughAllObjects()
{
    // получаем текущий документ и его БД
    Document doc = acad.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;

    // начинаем транзакцию
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // получаем ссылку на пространство модели (ModelSpace)
        // открываем таблицу блоков документа
        BlockTable acBlkTbl = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;

        // открываем пространство модели (Model Space) - оно является одной из записей в таблице блоков документа
        BlockTableRecord ms = tr.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;

        // "пробегаем" по всем объектам в пространстве модели
        foreach (ObjectId id in ms)
        {
            // приводим каждый из них к типу Entity
            Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);
       
    		// выводим в консоль слой (entity.Layer), тип (entity.GetType().ToString()) и цвет (entity.Color) каждого объекта
            acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
        }

        tr.Commit();
    }
}

Никакой разницы.

NB:
С краткой хвалебной одой классу SymbolUtilityServices можно ознакомиться здесь (англ.).

Получив ссылку на пространство модели, мы просто пробегаемся по всем его объектам, приводим их к типу Entity (чтобы можно было просматривать их свойства) и выводим в консоль данные о слое, типе и цвете очередного объекта.

1.2 Поиск объектов заданного типа


Давайте попробуем выделить все окружности. Сделаем это на базе предыдущего примера.

Действовать будем так: просмотрим по порядку все объекты чертежа, определим тип каждого объекта; если объект окажется окружностью — выведем информацию о нем в консоль. В прошлой статье нам уже встречалась ссылка (англ.), где описаны пять способов узнать тип объекта. Как всегда, не будем отступать от традиций и используем один из самых простых способов:

if (entity.GetType() == typeof(Circle))
// если условие верно - значит, это окружность

Код:
[CommandMethod("Habr_FindCircles_1")]
public void findCircles_1()
{
    // получаем текущую БД
    Database db = HostApplicationServices.WorkingDatabase;

    // начинаем транзакцию
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // получаем ссылку на пространство модели (ModelSpace)
        BlockTableRecord ms = (BlockTableRecord)tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForRead);

        // "пробегаем" по всем объектам в пространстве модели
        foreach (ObjectId id in ms)
        {
            // приводим каждый из них к типу Entity
            Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

            // если это окружность - выводим в консоль слой, тип и цвет каждого объекта

            if (entity.GetType() == typeof(Circle))
            {
	            acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
	                entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
            }
        }

        tr.Commit();
    }
}

Результат:


Работает.

Аналогично можно попробовать другие типы. Например, найти все линии:

if (entity.GetType() == typeof(Line))

Найти все аннотации (текстовые элементы):

if (entity.GetType() == typeof(DBText))

Все вхождения блоков:

if (entity.GetType() == typeof(BlockReference))

Выглядит несложно.

А если вдруг подзабудете, как именно называется тип, соответствующий объекту — можно просто добавить такой объект на чертеж вручную и запустить пример итерации по объектам — он выведет название типа в консоль AutoCAD.

1.3 Поиск объектов с заданными свойствами


Попробуем вместо типа поискать какое-нибудь свойство — например, цвет. Найдем все салатовые объекты.

Это очень просто: строку

if (entity.GetType() == typeof(Circle))

меняем на

if (entity.Color == Autodesk.AutoCAD.Colors.Color.FromRgb(128, 255, 64))

и смотрим, что получается.)

Код:
// команда для поиска салатовых объектов (подход 1)
[CommandMethod("Habr_FindLightGreenObjects_1")]
public void findLightGreenObjects_1()
{
    // получаем текущую БД
    Database db = HostApplicationServices.WorkingDatabase;

    // начинаем транзакцию
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // получаем ссылку на пространство модели (ModelSpace)
        BlockTableRecord ms = (BlockTableRecord)tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForRead);

        // "пробегаем" по всем объектам в пространстве модели
        foreach (ObjectId id in ms)
        {
            // приводим каждый из них к типу Entity
            Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

            // если цвет объекта - салатовый, то выводим в консоль слой, тип и цвет каждого объекта
            if (entity.Color == Autodesk.AutoCAD.Colors.Color.FromRgb(128, 255, 64))
            {
                acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                    entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
            }
        }

        tr.Commit();
    }
}

Результат:


Аналогично можно осуществлять поиск и по другим атрибутам — например, по слою:

if (entity.Layer == "0")

NB:
Сам я глубоко в свойства объектов не залезал. Советую при работе в Visual Studio просто набрать "Entity test; test." — после этого IntelliSense высветит все доступные программисту свойства и методы. Можно пробежаться по этому списку и ознакомиться со свойствами и методами класса Entity:


Класс Entity задает свойства, характерные для любого объекта чертежа: слой, цвет и т. п. Если требуется осуществить поиск по свойствам, специфичным для какого-то конкретного класса объектов, то необходимо вначале найти все объекты такого типа, затем выполнить приведение к этому типу и просмотреть значение нужного свойства.

Для примера давайте найдем все вхождения блока «block-1». Класс Entity не содержит сведений об имени определения блока; зато эта информация есть в свойстве Name класса BlockReference.

Код:
[CommandMethod("Habr_FindBlocks_1")]
public void findBlocks_1()
{
    // получаем текущую БД
    Database db = HostApplicationServices.WorkingDatabase;

    // начинаем транзакцию
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // получаем ссылку на пространство модели (ModelSpace)
        BlockTableRecord ms = (BlockTableRecord)tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForRead);

        // "пробегаем" по всем объектам в пространстве модели
        foreach (ObjectId id in ms)
        {
            // приводим каждый из них к типу Entity
            Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

            // проверяем, является ли объект вхождением блока
            if (entity.GetType() == typeof(BlockReference))
            {
                // если является - приводим его к типу BlockReference
                BlockReference br = (BlockReference)entity;

                // если имя соответствующего определения блока - "block-1", то выводим в консоль слой, тип и цвет каждого объекта
                if (br.Name == "block-1")
                {
                    acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                        entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
                }
            }
        }

        tr.Commit();
    }
}

Мы найдем два вхождения блока «block-1».

2 Поиск объектов чертежа с помощью метода Editor.SelectAll()


2.1 Итерация по всем объектам чертежа


Немного истории: упоминание этого метода есть в одной из статей Kean Walmsley (англ.). Кроме того, описание есть на AutoCAD DevBlog (англ.) и форуме Сообщества программистов Autodesk в СНГ (rus).

Если в предыдущем варианте мы просматривали каждый объект в отдельности, то здесь концепция меняется: мы сразу получаем массив идентификаторов всех интересующих нас объектов, при необходимости отфильтровывая нужные.

Начнем, как обычно, с примера итерации по всем объектам.

Код:
// команда для итерации по объектам (подход 2)
[CommandMethod("Habr_IterateThroughAllObjects_2")]
public void iterateThroughAllObjects_2()
{
    // получаем БД и Editor текущего документа
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;
    Editor ed = doc.Editor;

    // пытаемся получить ссылки на все объекты
    // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
    PromptSelectionResult selRes = ed.SelectAll();

    // если произошла ошибка - сообщаем о ней
    if (selRes.Status != PromptStatus.OK)
    {
        ed.WriteMessage("\nError!\n");
        return;
    }

    // получаем массив ID объектов
    ObjectId[] ids = selRes.Value.GetObjectIds();

    // начинаем транзакцию
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // "пробегаем" по всем полученным объектам
        foreach (ObjectId id in ids)
        {
            // приводим каждый из них к типу Entity
            Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

            // выводим в консоль слой (entity.Layer), тип (entity.GetType().ToString()) и цвет (entity.Color) каждого объекта
            acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
        }

        tr.Commit();
    }
}

Результат:


Мы вызываем метод Editor.SelectAll(). Поскольку никаких фильтров не задано, нам должны вернуться идентификаторы (ObjectID) всех объектов на чертеже. Они записываются в переменную типа PromptSelectionResult. На всякий случай нужно убедиться, что метод отработал корректно — для этого мы проверяем статус результата (PromptSelectionResult.Status). Если что-то не в порядке, значение этого свойства будет отлично от PromptStatus.OK — в этом случае мы завершаем выполнение функции.

Если же метод Editor.SelectAll() отработал корректно, мы получаем идентификаторы всех объектов, возвращенных этим методом. Для этого мы используем метод PromptSelectionResult.Value.GetObjectIds(). После этого мы просто обрабатываем все объекты в цикле — точь-в-точь как в первом разделе, когда мы обращались к ModelSpace.

Важный момент! Согласно своему описанию (англ.), метод Editor.SelectAll() должен возвращать только объекты на слоях, которые НЕ являются заблокированными (locked) или замороженными (frozen). Однако в этом случае документация, похоже, привирает: Editor.SelectAll() всегда возвращает все объекты на чертеже, вне зависимости от состояний слоев, на которых они находятся. Подробнее почитать про эту забавность можно в блоге Kean Walmsley (англ.), в AutoCAD Devblog (англ.), на форумах сообщества Autodesk (англ.).

В общем, мои выводы: у меня в AutoCAD 2010 этот подход работает. Возможно, он заработает в более новых версиях .NET API. Но что это — ошибка в документации или баг в API, который однажды, быть может, пофиксят, — я сказать не могу.

Краткий итог: аккуратнее с этим.

Другой важный момент. Для использования метода Editor.SelectAll(), очевидно, необходимо иметь объект класса Editor. При работе с документом, который непосредственно открыт в AutoCAD, проблем не возникнет; но использовать данный метод для обработки БД сторонних документов (не открытых в настоящий момент в AutoCAD) не получится (англ.).

2.2 Использование фильтров


Давайте снова попробуем выделить все окружности. Разумеется, можно сделать все аналогично предыдущему разделу: получить идентификаторы всех объектов и смотреть тип каждого объекта — это сработает. Однако при использовании метода Editor.SelectAll() есть возможность действовать по-другому.

Принцип такой: вначале мы задаем фильтр для объектов с помощью класса SelectionFilter, а затем применяем этот фильтр в методе Editor.SelectAll(). В результате у нас остаются только удовлетворяющие условию фильтра объекты.

Код:
[CommandMethod("Habr_FindCircles_2")]
public void findCircles_2()
{
    // получаем БД и Editor текущего документа
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;
    Editor ed = doc.Editor;

    // создаем переменную, в которой будут содержаться данные для фильтра
    TypedValue[] filterlist = new TypedValue[1];

    // первый аргумент (0) указывает, что мы задаем тип объекта
    // второй аргумент ("CIRCLE") - собственно тип
    filterlist[0] = new TypedValue(0, "CIRCLE");

    // создаем фильтр
    SelectionFilter filter = new SelectionFilter(filterlist);

    // пытаемся получить ссылки на объекты с учетом фильтра
    // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
    PromptSelectionResult selRes = ed.SelectAll(filter);

    // если произошла ошибка - сообщаем о ней
    if (selRes.Status != PromptStatus.OK)
    {
        ed.WriteMessage("\nError!\n");
        return;
    }

    // получаем массив ID объектов
    ObjectId[] ids = selRes.Value.GetObjectIds();

    // начинаем транзакцию
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // "пробегаем" по всем полученным объектам
        foreach (ObjectId id in ids)
        {
            // приводим каждый из них к типу Entity
            Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

            // выводим в консоль слой, тип и цвет каждого объекта
            acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
        }

        tr.Commit();
    }
}

Результат:


Работает. Теперь давайте разберемся, как.)

Итак, метод Editor.SelectAll() может принимать на вход объект типа SelectionFilter, который и задает фильтр. Этот фильтр инициализируется с помощью массива объектов типа TypedValue. Конструктор TypedValue принимает на вход два параметра:

public TypedValue(int typeCode, object value);

Эти значения связаны с форматом DXF (rus). Вот что говорит по этому поводу документация (англ.):
The DXF format is a tagged data representation of all the information contained in an AutoCAD drawing file of a specific version. Tagged data means that each data element in the file is preceded by an integer number that is called a group code. A group code's value indicates what type of data element follows. It also indicates the meaning of a data element for a given object (or record) type. Virtually all user-specified information in a drawing file can be represented in DXF format. The DXF format is essentially the same when used with applications (AutoLISP and ARX). However, there are a few minor differences for some data groups.

Гугл-транслейт:
Формат DXF позволяет представить всю информацию в чертеже AutoCAD определенной версии в виде тегированных данных. Под тегированными данными понимается то, что каждый элемент данных содержит в начале целое число, которое называется групповым кодом. Значение группового кода позволяет узнать, данные какого типа содержатся в этом элементе. Также оно отражает смысл элемента данных для соответствующего типа объекта (или записи). Практически вся добавленная пользователем на чертеж информация может быть представлена в формате DXF. В целом формат DXF один и тот же и для AutoLISP, и для ARX, однако для некоторых групп данных возможны небольшие различия.

В рассмотренном примере мы указали в качестве typeCode значение 0. Откуда оно взялось и что означает — можно посмотреть в списке (англ.)

На всякий случай я продублирую список здесь:
-5: APP: persistent reactor chain
-4: APP: conditional operator (used only with ssget)
-3: APP: extended data (XDATA) sentinel (fixed)
-2: APP: entity name reference (fixed)
-1: APP: entity name. This changes each time a drawing is opened. It is never saved. (fixed)
0: Text string indicating the entity type (fixed)
1: Primary text value for an entity
2: Name (attribute tag, block name, and so on)
3-4: Other textual or name values
5: Entity handle. Text string of up to 16: hexadecimal digits (fixed)
6: Linetype name (fixed)
7: Text style name (fixed)
8: Layer name (fixed)
9: DXF: variable name identifier (used only in HEADER section of the DXF file).
10: Primary point. This is the start point of a line or text entity, center of a circle, and so on.
DXF: X value of the primary point (followed by Y and Z value codes 20: and 30)
APP: 3D point (list of three reals)
11-18: Other points.
DXF: X value of other points (followed by Y value codes 21-28: and Z value codes 31-38)
APP: 3D point (list of three reals)
20, 30: DXF: Y and Z values of the primary point
21-28, 31-37: DXF: Y and Z values of other points
38: DXF: entity's elevation if nonzero.
39: Entity's thickness if nonzero (fixed)
40-48: Floating-point values (text height, scale factors, and so on)
48: Linetype scale. Floating-point scalar value. Default value is defined for all entity types.
49: Repeated floating-point value. Multiple 49: groups may appear in one entity for variable-length tables (such as the dash lengths in the LTYPE table). A 7x group always appears before the first 49 group to specify the table length.
50-58: Angles (output in degrees to DXF files and radians through AutoLISP and ARX applications).
60: Entity visibility. Integer value. Absence or 0: indicates visibility; 1 indicates invisibility.
62: Color number (fixed)
66: "Entities follow" flag (fixed)
67: Space--that is, model or paper space (fixed)
68: APP: identifies whether viewport is on but fully off screen; is not active or is off.
69: APP: viewport identification number.
70-78: Integer values, such as repeat counts, flag bits, or modes
90-99: 32-bit integer values
100: Subclass data marker (with derived class name as a string). Required for all objects and entity classes that are derived from another concrete class to segregate data defined by different classes in the inheritance chain for the same object.
This is in addition to the requirement for DXF names for each distinct concrete class derived from ARX (see "Subclass Markers").
102: Control string, followed by "{<arbitrary name>" or "}". Similar to the xdata 1002: group code, except that when the string begins with "{", it can be followed by an arbitrary string whose interpretation is up to the application. The only other allowable control string is "}" as a group terminator. As noted before, AutoCAD does not interpret these strings except during drawing audit operations; they are for application use.
105: DIMVAR symbol table entry object handle
210: Extrusion direction (fixed).
DXF: X value of extrusion direction
APP: 3D extrusion direction vector
220, 230: DXF: Y and Z values of the extrusion direction
280-289: 8-bit integer values
300-309: Arbitrary text strings
310-319: Arbitrary binary chunks with same representation and limits as 1004: group codes: hexadecimal strings of up to 254 characters represent data chunks of up to 127 bytes.
320-329: Arbitrary object handles. Handle values that are taken "as is." They are not translated during INSERT and XREF operations.
330-339: Soft-pointer handle. Arbitrary soft pointers to other objects within same DXF file or drawing. Translated during INSERT and XREF operations.
340-349: Hard-pointer handle. Arbitrary hard pointers to other objects within same DXF file or drawing. Translated during INSERT and XREF operations.
350-359: Soft-owner handle. Arbitrary soft ownership links to other objects within same DXF file or drawing. Translated during INSERT and XREF operations.
360-369: Hard-owner handle. Arbitrary hard ownership links to other objects within same DXF file or drawing. Translated during INSERT and XREF operations.
999: DXF: The 999: group code indicates that the line following it is a comment string. DXFOUT does not include such groups in a DXF output file, but DXFIN honors them and ignores the comments. You can use the 999 group to include comments in a DXF file that you've edited.
1000: ASCII string (up to 255: bytes long) in extended data.
1001: Registered application name (ASCII string up to 31: bytes long) for extended data.
1002: Extended data control string ("{"or "}").
1003: Extended data layer name.
1004: Chunk of bytes (up to 127: bytes long) in extended data.
1005: Entity handle in extended data. Text string of up to 16: hexadecimal digits
1010: A point in extended data
DXF: X value (followed by 1020: and 1030 groups)
APP: 3D point
1020, 1030: DXF: Y and Z values of a point
1011: A 3D world space position in extended data
DXF: X value (followed by 1021: and 1031 groups)
APP: 3D point
1021, 1031: DXF: Y and Z values of a World space position
1012: A 3D world space displacement in extended data
DXF: X value (followed by 1022: and 1032 groups)
APP: 3D vector
1022, 1032: DXF: Y and Z values of a World space displacement
1013: A 3D world space direction in extended data.
DXF: X value (followed by 1022: and 1032 groups)
APP: 3D vector
1023, 1033: DXF: Y and Z values of a World space direction
1040: Extended data floating-point value.
1041: Extended data distance value.
1042: Extended data scale factor.
1070: Extended data 16-bit signed integer.
1071: Extended data 32-bit signed long.

Пятой шестой строкой в списке идет эта:
0: Text string indicating the entity type (fixed)

Таким образом, коду «0» соответствует тип объекта.

NB:
Вместо зубодробительных числовых констант можно использовать любезно предоставленное разработчиками API перечисление Autodesk.AutoCAD.DatabaseServices.DxfCode:

[Wrapper("AcDb::DxfCode")]
public enum DxfCode
{
    Invalid = -9999,
    XDictionary = -6,
    PReactors = -5,
    Operator = -4,
    XDataStart = -3,
    FirstEntityId = -2,
    HeaderId = -2,
    End = -1,
    Start = 0,
    XRefPath = 1,
    Text = 1,
    AttributeTag = 2,
    ShapeName = 2,
    BlockName = 2,
    SymbolTableName = 2,
    MlineStyleName = 2,
    SymbolTableRecordName = 2,
    Description = 3,
    TextFontFile = 3,
    AttributePrompt = 3,
    LinetypeProse = 3,
    DimStyleName = 3,
    DimPostString = 3,
    CLShapeName = 4,
    DimensionAlternativePrefixSuffix = 4,
    TextBigFontFile = 4,
    SymbolTableRecordComments = 4,
    Handle = 5,
    DimensionBlock = 5,
    LinetypeName = 6,
    DimBlk1 = 6,
    DimBlk2 = 7,
    TextStyleName = 7,
    LayerName = 8,
    CLShapeText = 9,
    XCoordinate = 10,
    YCoordinate = 20,
    ZCoordinate = 30,
    Elevation = 38,
    Thickness = 39,
    TxtSize = 40,
    ViewportHeight = 40,
    Real = 40,
    ViewWidth = 41,
    TxtStyleXScale = 41,
    ViewportAspect = 41,
    TxtStylePSize = 42,
    ViewLensLength = 42,
    ViewFrontClip = 43,
    ViewBackClip = 44,
    ShapeXOffset = 44,
    ViewHeight = 45,
    ShapeYOffset = 45,
    ShapeScale = 46,
    PixelScale = 47,
    LinetypeScale = 48,
    DashLength = 49,
    MlineOffset = 49,
    LinetypeElement = 49,
    ViewportSnapAngle = 50,
    Angle = 50,
    ViewportTwist = 51,
    Visibility = 60,
    LayerLinetype = 61,
    Color = 62,
    HasSubentities = 66,
    ViewportVisibility = 67,
    ViewportActive = 68,
    ViewportNumber = 69,
    Int16 = 70,
    ViewMode = 71,
    TxtStyleFlags = 71,
    RegAppFlags = 71,
    CircleSides = 72,
    LinetypeAlign = 72,
    ViewportZoom = 73,
    LinetypePdc = 73,
    ViewportIcon = 74,
    ViewportSnap = 75,
    ViewportGrid = 76,
    ViewportSnapStyle = 77,
    ViewportSnapPair = 78,
    Int32 = 90,
    Subclass = 100,
    EmbeddedObjectStart = 101,
    ControlString = 102,
    DimVarHandle = 105,
    UcsOrg = 110,
    UcsOrientationX = 111,
    UcsOrientationY = 112,
    XReal = 140,
    ViewBrightness = 141,
    ViewContrast = 142,
    Int64 = 160,
    XInt16 = 170,
    NormalX = 210,
    NormalY = 220,
    NormalZ = 230,
    XXInt16 = 270,
    Int8 = 280,
    RenderMode = 281,
    Bool = 290,
    XTextString = 300,
    BinaryChunk = 310,
    ArbitraryHandle = 320,
    SoftPointerId = 330,
    HardPointerId = 340,
    SoftOwnershipId = 350,
    HardOwnershipId = 360,
    LineWeight = 370,
    PlotStyleNameType = 380,
    PlotStyleNameId = 390,
    ExtendedInt16 = 400,
    LayoutName = 410,
    ColorRgb = 420,
    ColorName = 430,
    Alpha = 440,
    GradientObjType = 450,
    GradientPatType = 451,
    GradientTintType = 452,
    GradientColCount = 453,
    GradientAngle = 460,
    GradientShift = 461,
    GradientTintVal = 462,
    GradientColVal = 463,
    GradientName = 470,
    Comment = 999,
    ExtendedDataAsciiString = 1000,
    ExtendedDataRegAppName = 1001,
    ExtendedDataControlString = 1002,
    ExtendedDataLayerName = 1003,
    ExtendedDataBinaryChunk = 1004,
    ExtendedDataHandle = 1005,
    ExtendedDataXCoordinate = 1010,
    ExtendedDataWorldXCoordinate = 1011,
    ExtendedDataWorldXDisp = 1012,
    ExtendedDataWorldXDir = 1013,
    ExtendedDataYCoordinate = 1020,
    ExtendedDataWorldYCoordinate = 1021,
    ExtendedDataWorldYDisp = 1022,
    ExtendedDataWorldYDir = 1023,
    ExtendedDataZCoordinate = 1030,
    ExtendedDataWorldZCoordinate = 1031,
    ExtendedDataWorldZDisp = 1032,
    ExtendedDataWorldZDir = 1033,
    ExtendedDataReal = 1040,
    ExtendedDataDist = 1041,
    ExtendedDataScale = 1042,
    ExtendedDataInteger16 = 1070,
    ExtendedDataInteger32 = 1071,
}

Например, в рассмотренном примере мы могли бы вместо

filterlist[0] = new TypedValue(0, "CIRCLE");

написать

filterlist[0] = new TypedValue((int)DxfCode.Start, "CIRCLE");

И это сработает!

Вот только не надо спрашивать меня, почему это значению 0 соответствует элемент перечисления с названием «Start». Понятия не имею.

В общем, можете забить весь свой код непонятными числовыми константами (что, конечно, хреново). Или можете забить его соответствующими элементами перечисления Autodesk.AutoCAD.DatabaseServices.DxfCode с не менее непонятными, взятыми с потолка названиями (что, разумеется, хреново). Можете также создать свое собственное перечисление с необходимыми константами и использовать его (что, сами понимаете, хреново). Добро пожаловать в увлекательный мир программирования! Налево пойдешь — коня потеряешь, и так далее.

Поскольку в любом случае у вас получится нечитаемое спагетти — можете выбрать любой способ или даже использовать все три. А если сойти с ума через полгода не входит в ваши планы — пишите комментарии, это поможет продержаться чуть дольше.

Быстро-быстро пробежимся по простым возможностям фильтров.

Разумеется, искать мы можем не только окружности. Найдем все линии:

filterlist[0] = new TypedValue((int)DxfCode.Start, "LINE");

Найдем все вхождения блоков:

filterlist[0] = new TypedValue((int)DxfCode.Start, "INSERT");

NB:
Список некоторых имен классов объектов можно найти тут (англ.).

Вот он, на всякий случай:
3DFACE
3DSOLID
ACAD_PROXY_ENTITY
ARC
ARCALIGNEDTEXT
ATTDEF
ATTRIB
BODY
CIRCLE
DIMENSION
ELLIPSE
HATCH
IMAGE
INSERT
LEADER
LINE
LWPOLYLINE
MLINE
MTEXT
OLEFRAME
OLE2FRAME
POINT
POLYLINE
RAY
REGION
RTEXT
SEQEND
SHAPE
SOLID
SPLINE
TEXT
TOLERANCE
TRACE
VERTEX
VIEWPORT
WIPEOUT
XLINE

Именно отсюда было взято значение "INSERT".

Найдем все объекты на слое «layer-1»:

filterlist[0] = new TypedValue((int)DxfCode.LayerName, "layer-1");

В одном объекте TypedValue можно перечислить несколько имен через запятую. В этом случае условия будут объединены операцией «ИЛИ» («OR»).

Давайте найдем все объекты, которые являются линиями ИЛИ окружностями:

filterlist[0] = new TypedValue((int)DxfCode.Start, "LINE,CIRCLE");

Найдем все объекты, которые находятся на слое «layer-1» ИЛИ «layer-2»:

filterlist[0] = new TypedValue((int)DxfCode.LayerName, "layer-1,layer-2");

ВАЖНО: после запятой между объединяемыми параметрами НЕ ДОЛЖНО быть пробела!

Наконец, можно добавить несколько объектов TypedValue к массиву условий. В этом случае условия будут объединены операцией «И» («AND»).

Найдем все объекты, которые являются линиями И находятся на нулевом слое:

TypedValue[] filterlist = new TypedValue[2];
filterlist[0] = new TypedValue((int)DxfCode.Start, "CIRCLE");
filterlist[1] = new TypedValue((int)DxfCode.LayerName, "0");

Найдем все вхождения блока «block-1»:

TypedValue[] filterlist = new TypedValue[2];
filterlist[0] = new TypedValue((int)DxfCode.Start, "INSERT");
filterlist[1] = new TypedValue((int)DxfCode.BlockName, "block-1");

Найдем все объекты, которые являются линиями И находятся на нулевом слое ИЛИ слое «layer-1»:

TypedValue[] filterlist = new TypedValue[2];
filterlist[0] = new TypedValue((int)DxfCode.Start, "LINE");
filterlist[1] = new TypedValue((int)DxfCode.LayerName, "0,layer-1");

Ну вот, вроде и не больно… Было. Пока что.

2.3 Фильтры посложнее


Давайте взглянем на украденный творчески переработанный (я написал перевел комментарии) пример из блога Kean Walmsley.

Пусть нам надо найти все линии на слое «layer-1» и все круги на слое «layer-2». Очевидно, мы не сможем этого сделать, просто добавив несколько объектов TypedValue: максимум, чего можно добиться этим способом, — это найти все линии и все круги на обоих слоях сразу.

Итак, нам нужно реализовать выбор по такому условию:

((СЛОЙ == «layer-1») И (ТИП == «Линия»)) ИЛИ ((СЛОЙ == «layer-2») И (ТИП == «Окружность»))

В синтаксисе AutoCAD наше условие можно записать так:

  • <OR
    • <AND
      • Layer == «layer-1»
      • Entity type == «LINE»
    • AND>
    • <AND
      • Layer == «layer-2»
      • Entity type == «CIRCLE»
    • AND>
  • OR>

А вот так это условие перепишется в виде массива элементов TypedValue:

TypedValue[] filterlist = new TypedValue[10];
filterlist[0] = new TypedValue((int)DxfCode.Operator, "<OR");
filterlist[1] = new TypedValue((int)DxfCode.Operator, "<AND");
filterlist[2] = new TypedValue((int)DxfCode.LayerName, "layer-1");
filterlist[3] = new TypedValue((int)DxfCode.Start, "LINE");
filterlist[4] = new TypedValue((int)DxfCode.Operator, "AND>");
filterlist[5] = new TypedValue((int)DxfCode.Operator, "<AND");
filterlist[6] = new TypedValue((int)DxfCode.LayerName, "layer-2");
filterlist[7] = new TypedValue((int)DxfCode.Start, "CIRCLE");
filterlist[8] = new TypedValue((int)DxfCode.Operator, "AND>");
filterlist[9] = new TypedValue((int)DxfCode.Operator, "OR>");

После применения фильтра на базе этих условий мы увидим следующий вывод в консоли AutoCAD:
Layer:layer-2; Type:Autodesk.AutoCAD.DatabaseServices.Circle; Color: 128,255,64
Layer:layer-1; Type:Autodesk.AutoCAD.DatabaseServices.Line; Color: 0,200,255
Layer:layer-1; Type:Autodesk.AutoCAD.DatabaseServices.Line; Color: 255,128,255

Принцип простой: осознаем, какое условие нам нужно, разбиваем его на элементарные части, а затем объединяем эти части с помощью операторов OR, AND, NOT, XOR…

NB:
Серьезно? XOR? Сколько лет, сколько зим… Кажется, последний раз я тебя видел на лабе по информатике.
Как жизнь, родимый? Где-то ты сейчас? Кто все эти люди, которые тебя используют?

Почитать подробнее про сложные условия можно здесь (англ.).

Все, хватит уже о фильтрах. Сколько можно…

3 Альтернативные способы поиска объектов на чертеже


Помимо Editor.SelectAll(), есть ряд других способов поиска (точнее, выделения) объектов на чертеже. Вот ссылки: английская документация, русский перевод.

Как можно увидеть, существуют еще десять более узконаправленных вариантов, чем Editor.SelectAll(). Зато мы рассмотрели самый мощный из них.

Оффтоп: обещанные кровавые подробности (можно пропустить)
Я некоторое время думал, стоит ли вообще включать это в статью. В итоге, решил, что пусть будет — вот только зачем?

В общем, если у вас есть чертеж с огромным количеством объектов, и нужно все их обработать, и выхода нет, и скоро рассвет — то вот вам ссылочка (англ.), где Андрей Бушман hwd, Александр Ривилис и зарубежные эксперты развлекаются, оптимизируя эту задачку.

В этом примере мы получаем доступ не к видимому чертежу (ModelSpace), а ко всей БД документа целиком. Таким образом, мы увидим не только изображенные на чертеже объекты, но и слои, определения блоков и т.п.

Код:
// команда для итерации по объектам (подход 3)
// Осторожно! Используется черная магия!
[CommandMethod("Habr_IterateThroughAllObjects_3")]
public void iterateThroughAllObjects_3()
{
    Database db = HostApplicationServices.WorkingDatabase;
    Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
    long amount = 0;
    Dictionary<string, int> d = new Dictionary<string, int>();
    ObjectId id = ObjectId.Null;
    for (long i = db.BlockTableId.Handle.Value; i < db.Handseed.Value; i++)
    {
        Handle h = new Handle(i);
        if (db.TryGetObjectId(h, out id) && !id.IsNull && id.IsValid && !id.IsErased)
            {
                string t = id.ObjectClass.DxfName;
                amount++;
                if(d.ContainsKey(t))
                    d[t]++;
                else
                    d.Add(t, 1);
            }
    }
    foreach(KeyValuePair<string,int> kvp in d)
        ed.WriteMessage("\n{0}: {1} ", kvp.Key, kvp.Value);
    ed.WriteMessage("\nTotal {0} objects in drawing\n", amount);
}

Результат (вывод в консоль):
Command: Habr_IterateThroughAllObjects_3
TABLE: 11
DICTIONARY: 16
ACDBDICTIONARYWDFLT: 1
ACDBPLACEHOLDER: 1
LAYER: 3
STYLE: 2
APPID: 7
LTYPE: 3
MLINESTYLE: 1
BLOCK_RECORD: 5
BLOCK: 5
ENDBLK: 5
LAYOUT: 3
DIMSTYLE: 3
DICTIONARYVAR: 7
TABLESTYLE: 1
VPORT: 1
MATERIAL: 3
VISUALSTYLE: 19
SCALE: 17
MLEADERSTYLE: 2
XRECORD: 4
FONT_TABLE_RECORD: 2
CIRCLE: 5
LINE: 9
LWPOLYLINE: 2
TEXT: 2
INSERT: 3
ARC: 1
Total 144 objects in drawing

Тут применено какое-то особо сильное колдунство с прямым доступом к объектам БД по их хендлам. Я вначале попытался было вкурить код, но вовремя вспомнил, что уже давно завязал, и делать это совершенно необязательно. Если вы — программист, то welcome! Что же касается меня… Пожалуй, разбор этого элементарного фрагмента кода оставим читателям в качестве несложного домашнего задания.

UPD.: В комментариях Андрей Бушман привел ссылку на наиболее рациональный способ итерации по объектам.

4 Модификация объектов


Мне кажется, что модификация объектов происходит в разы проще поиска. Вот основной принцип: получив ObjectID, мы открываем сам объект, приводим его к нужному типу и вносим необходимые изменения в свойства. Ну или удаляем объект с чертежа, используя соответствующий метод.

4.1 Удаление объекта с чертежа


Для удаления объекта с чертежа необходимо:
  1. открыть объект на запись;
  2. вызвать метод Erase();
  3. зафиксировать транзакцию.

Ссылки: документация (англ.), зеркало (англ.).

Для примера давайте удалим с чертежа все окружности.

Код:
[CommandMethod("Habr_EraseCircles_2")]
public void eraseCircles_2()
{
    // получаем БД и Editor текущего документа
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;
    Editor ed = doc.Editor;

    // создаем переменную, в которой будут содержаться данные для фильтра
    TypedValue[] filterlist = new TypedValue[1];

    // первый аргумент (0) указывает, что мы задаем тип объекта
    // второй аргумент ("CIRCLE") - собственно тип
    filterlist[0] = new TypedValue(0, "CIRCLE");

    // создаем фильтр
    SelectionFilter filter = new SelectionFilter(filterlist);

    // пытаемся получить ссылки на объекты с учетом фильтра
    // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
    PromptSelectionResult selRes = ed.SelectAll(filter);

    // если произошла ошибка - сообщаем о ней
    if (selRes.Status != PromptStatus.OK)
    {
        ed.WriteMessage("\nError!\n");
        return;
    }

    // получаем массив ID объектов
    ObjectId[] ids = selRes.Value.GetObjectIds();

    // начинаем транзакцию
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // "пробегаем" по всем полученным объектам
        foreach (ObjectId id in ids)
        {
            // приводим каждый из них к типу Entity
            Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

            // открываем приговоренный объект на запись
            entity.UpgradeOpen();

            // удаляем объект
            entity.Erase();
        }

        tr.Commit();
    }
}

Результат:


Конечно, в данном случае можно было и не использовать метод UpgradeOpen(), а сразу открывать объект на запись и удалять:

Entity entity = (Entity)tr.GetObject(id, OpenMode.ForWrite);
entity.Erase();

Важно! Если объект находится на заблокированном слое — доступ к нему на запись получить не удастся, и мы сможем полюбоваться на такое сообщение:



Поэтому еще раз напомню о необходимости как минимум упаковывать все требующие доступа на запись операции в конструкцию try...catch с последующим перехватыванием исключений. То есть что-то вроде такого:

Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);
try
{
    entity.UpgradeOpen();
    entity.Erase();
}
catch ()
{
    ed.WriteMessage("\nSomething went wrong...\n");
}

В этом случае мы вместо системного исключения увидим простое сообщение в консоли AutoCAD.

NB:
Разумеется, использование try...catch в таком виде — это тот еще быдлокод. Ведь мы же знаем, что в случае заблокированного слоя нас ждет ошибка eOnLockedLayer, так что можем перехватывать не все ошибки подряд, а только Autodesk.AutoCAD.Runtime.Exception с соответствующим кодом ошибки (ErrorStatus) — как-то так:

catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
    if (ex.ErrorStatus == ErrorStatus.OnLockedLayer)
    {
        ...
    }
}

Тут подумайте сами — либо решите, что «яжпрограммист», и обрабатывайте наиболее вероятные ошибки по отдельности, либо решите, что путешествия в дебри кода — это не для вас, и глубокая фильтрация ошибок НЕ_НУЖНА. По-хорошему, такое решение должно приниматься с учетом поставленной задачи, оценки рисков и т. д.

Но — подчеркну еще раз — подобную ситуацию нужно иметь в виду в любом случае.

UPD.: Андрей Бушман подсказал, что метод GetObject, вообще говоря, имеет следующую сигнатуру:

GetObject(ObjectId id, OpenMode mode, Boolean openErased, Boolean forceOpenOnLockedLayer)

Последний параметр позволяет в том числе работать с заблокированными слоями. Для примера, вот такой код у меня корректно отработал даже с заблокированными слоем:

Entity entity = (Entity)tr.GetObject(id, OpenMode.ForWrite, false, true);
entity.Erase();

После завершения транзакции состояние слоя, на котором расположен объект, не изменяется.

В общем, похоже, что вместо конструкции try...catch можно использовать параметр forceOpenOnLockedLayer. В этом случае мы отказываемся от метода UpgradeOpen() и сразу открываем объект методом GetObject() на запись (OpenMode.ForWrite) с установленным флагом forceOpenOnLockedLayer.

4.2 Изменение типовых свойств объектов


Под «типовыми свойствами» я имею в виду те свойства, которые присущи всем объектам на чертеже AutoCAD. Это, например, слой и цвет.

Здесь все так же:
  1. открываем объект на запись;
  2. модифицируем нужное свойство;
  3. фиксируем транзакцию.

Давайте сделаем все наши фигуры оранжевыми.

Код:
[CommandMethod("Habr_RepaintOrange_2")]
public void repaintOrange_2()
{
    // получаем БД и Editor текущего документа
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;
    Editor ed = doc.Editor;

    // пытаемся получить ссылки на все объекты
    // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
    PromptSelectionResult selRes = ed.SelectAll();

    // если произошла ошибка - сообщаем о ней
    if (selRes.Status != PromptStatus.OK)
    {
        ed.WriteMessage("\nError!\n");
        return;
    }

    // получаем массив ID объектов
    ObjectId[] ids = selRes.Value.GetObjectIds();

    // начинаем транзакцию
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // "пробегаем" по всем полученным объектам
        foreach (ObjectId id in ids)
        {
            // приводим каждый из них к типу Entity
            Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

            // открываем объект на запись
            entity.UpgradeOpen();

            // изменяем цвет на оранжевый
            entity.Color = Autodesk.AutoCAD.Colors.Color.FromRgb(255, 128, 0);
        }

        tr.Commit();
    }
}

Результат:


Цвет всех объектов изменен на оранжевый. Однако объекты внутри вхождений блоков цвет не поменяли и остались черными — несмотря на то, что для всех вхождений блоков был успешно задан оранжевый цвет:

Доказательство - значение поля Color:


Нужно иметь эту особенность в виду.

Рассмотренный способ можно использовать для модификации всех изменяемых свойств, которые доступны в классе Entity. Но как и в предыдущем случае, вы должны быть на 146% уверены в том, что плагину удастся получить доступ к объекту на запись. Если степень уверенности меньше — используйте флаг forceOpenOnLockedLayer или средства индивидуальной защиты конструкцию try...catch и перехватывайте возможные исключения.

4.3 Изменение специфичных свойств объектов


Допустим, мы хотим поменять радиус окружности. Используя класс Entity, мы не сможем это сделать, поскольку у него нет свойства, отвечающего за радиус. В данном случае нужно приводить объект к типу Circle:

Код:
[CommandMethod("Habr_ModifyCircles_2")]
public void modifyCircles_2()
{
    // получаем БД и Editor текущего документа
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;
    Editor ed = doc.Editor;

    // создаем переменную, в которой будут содержаться данные для фильтра
    TypedValue[] filterlist = new TypedValue[1];

    // первый аргумент (0) указывает, что мы задаем тип объекта
    // второй аргумент ("CIRCLE") - собственно тип
    filterlist[0] = new TypedValue(0, "CIRCLE");

    // создаем фильтр
    SelectionFilter filter = new SelectionFilter(filterlist);

    // пытаемся получить ссылки на объекты с учетом фильтра
    // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
    PromptSelectionResult selRes = ed.SelectAll(filter);

    // если произошла ошибка - сообщаем о ней
    if (selRes.Status != PromptStatus.OK)
    {
        ed.WriteMessage("\nError!\n");
        return;
    }

    // получаем массив ID объектов
    ObjectId[] ids = selRes.Value.GetObjectIds();

    // начинаем транзакцию
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // "пробегаем" по всем полученным объектам
        foreach (ObjectId id in ids)
        {
            // приводим каждый из них к типу Circle
            Circle cir = (Circle)tr.GetObject(id, OpenMode.ForRead);

            // открываем объект на запись
            cir.UpgradeOpen();
            
            // увеличиваем радиус
            cir.Radius = cir.Radius * 2;
        }

        tr.Commit();
    }
}

Результат:


Работает.

Разумеется, необходимо убедиться, что мы имеем дело именно с окружностями. Попытка привести к типу Circle, например, вхождение блока добром не кончится:



Используйте конструкцию try...catch! Или по крайней мере обеспечивайте фильтрацию, как в нашем примере, либо же проверяйте тип непосредственно перед приведением:

Entity ent = (Entity)tr.GetObject(id, OpenMode.ForRead);
if (ent.GetType() == typeof(Circle))
{
    Circle cir = (Circle)tr.GetObject(id, OpenMode.ForRead);
}

Подчеркну, что ни фильтр, ни проверка не обезопасят от ситуации с заблокированным слоем и ошибкой eOnLockedLayer. Для борьбы с ней используйте либо флаг forceOpenOnLockedLayer, либо конструкцию try...catch, либо фильтры / проверки, которые отсекут заблокированные объекты.

4.4 Перемещение простых объектов


Если положение объекта на чертеже задается одной точкой (как, например, у окружности или блока), то для перемещения этой фигуры достаточно изменить соответствующие свойство. Для примера давайте переместим все вхождения блоков в начало координат:

Код:
[CommandMethod("Habr_MoveBlocks_2")]
public void moveBlocks_2()
{
    // получаем БД и Editor текущего документа
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;
    Editor ed = doc.Editor;

    // создаем переменную, в которой будут содержаться данные для фильтра
    TypedValue[] filterlist = new TypedValue[1];

    // первый аргумент (0) указывает, что мы задаем тип объекта
    // второй аргумент ("CIRCLE") - собственно тип
    filterlist[0] = new TypedValue(0, "INSERT");

    // создаем фильтр
    SelectionFilter filter = new SelectionFilter(filterlist);

    // пытаемся получить ссылки на объекты с учетом фильтра
    // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
    PromptSelectionResult selRes = ed.SelectAll(filter);

    // если произошла ошибка - сообщаем о ней
    if (selRes.Status != PromptStatus.OK)
    {
        ed.WriteMessage("\nError!\n");
        return;
    }

    // получаем массив ID объектов
    ObjectId[] ids = selRes.Value.GetObjectIds();

    // начинаем транзакцию
    using (Transaction tr = db.TransactionManager.StartTransaction())
    {
        // "пробегаем" по всем полученным объектам
        foreach (ObjectId id in ids)
        {
            // приводим каждый из них к типу BlockReference
            BlockReference br = (BlockReference)tr.GetObject(id, OpenMode.ForRead);

            // открываем объект на запись
            br.UpgradeOpen();

            // перемещаем вхождение блока
            br.Position = Point3d.Origin;
        }

        tr.Commit();
    }
}

Результат:


Аналогом свойства Position для окружности является свойство Center.

4.5 Сложные операции над объектами


AutoCAD .NET API позволяет осуществлять масштабирование и поворот объектов. Наверное, даже можно как-то модифицировать вершины многоугольников и ломаных. К сожалению, я с такими вещами не работал и ничего по этому поводу сказать не могу.

Начать поиск информации, если уж придется, можно с руководства по .NET API (ссылка (англ.), зеркало (англ.)). Ну и форумы AutoCAD в помощь. Можно также задать вопрос на форумах Сообщества программистов Autodesk в СНГ.

Приложение


На всякий случай - вот финальная версия кода, содержащая основные из рассмотренных примеров
using System;
using System.Collections.Generic;

using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using acad = Autodesk.AutoCAD.ApplicationServices.Application;

namespace HabrPlug_SearchAndRescue
{
    public class ClassMyAutoCADDLL_SearchAndRescue
    {
        public class Commands : IExtensionApplication
        {
            // используемые цвета
            Autodesk.AutoCAD.Colors.Color color_Pink = Autodesk.AutoCAD.Colors.Color.FromRgb(255, 128, 255);
            Autodesk.AutoCAD.Colors.Color color_Blue =  Autodesk.AutoCAD.Colors.Color.FromRgb(0, 200, 255);
            Autodesk.AutoCAD.Colors.Color color_LightGreen = Autodesk.AutoCAD.Colors.Color.FromRgb(128, 255, 64);

            // ID слоев "layer-1" и "layer-2"
            ObjectId layer_1;
            ObjectId layer_2;

            // создаем слои
            public void createLayers()
            {
                // получаем текущий документ и его БД
                Document acDoc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
                Database acCurDb = acDoc.Database;

                // блокируем документ
                using (DocumentLock docloc = acDoc.LockDocument())
                {
                    // начинаем транзакцию
                    using (Transaction tr = acCurDb.TransactionManager.StartTransaction())
                    {
                        // открываем таблицу слоев документа
                        LayerTable acLyrTbl = tr.GetObject(acCurDb.LayerTableId, OpenMode.ForWrite) as LayerTable;

                        // создаем новый слой и задаем ему имя
                        LayerTableRecord acLyrTblRec_1 = new LayerTableRecord();
                        acLyrTblRec_1.Name = "layer-1";
                        // заносим созданный слой в таблицу слоев, сохраняем ID созданной записи слоя
                        layer_1 = acLyrTbl.Add(acLyrTblRec_1);
                        // добавляем созданный слой в документ
                        tr.AddNewlyCreatedDBObject(acLyrTblRec_1, true);


                        // создаем новый слой и задаем ему имя
                        LayerTableRecord acLyrTblRec_2 = new LayerTableRecord();
                        acLyrTblRec_2.Name = "layer-2";
                        // заносим созданный слой в таблицу слоев, сохраняем ID созданной записи слоя
                        layer_2 = acLyrTbl.Add(acLyrTblRec_2);
                        // добавляем созданный слой в документ
                        tr.AddNewlyCreatedDBObject(acLyrTblRec_2, true);

                        // фиксируем транзакцию
                        tr.Commit();
                    }
                }
            }

            // создаем определение блока "block-1"
            public void createBlock_1()
            {
                // получаем ссылки на документ и его БД
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // имя создаваемого блока
                const string blockName = "block-1";

                // начинаем транзакцию
                Transaction tr = db.TransactionManager.StartTransaction();
                using (tr)
                {
                    // открываем таблицу блоков на запись
                    BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForWrite);

                    // проверяем, нет ли в таблице блока с таким именем; если есть - заканчиваем выполнение команды
                    if (bt.Has(blockName))
                    {
                        return;
                    }

                    // создаем новое определение блока, задаем ему имя
                    BlockTableRecord btr = new BlockTableRecord();
                    btr.Name = blockName;

                    // добавляем созданное определение блока в таблицу блоков и в транзакцию
                    bt.Add(btr);
                    tr.AddNewlyCreatedDBObject(btr, true);

                    // добавляем к блоку элементы

                    // создаем окружность
                    Circle acCircle = new Circle();
                    // устанавливаем параметры созданного объекта
                    acCircle.SetDatabaseDefaults();
                    acCircle.Center = Point3d.Origin;
                    acCircle.Radius = 25;
                    // добавляем созданный объект в определение блока и в транзакцию
                    btr.AppendEntity(acCircle);
                    tr.AddNewlyCreatedDBObject(acCircle, true);

                    // создаем линию
                    Line acLine = new Line(new Point3d(18, 18, 0), new Point3d(35, 35, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine.SetDatabaseDefaults();
                    // добавляем созданный объект в определение блока и в транзакцию
                    btr.AppendEntity(acLine);
                    tr.AddNewlyCreatedDBObject(acLine, true);

                    // создаем полилинию
                    Polyline acPolyline = new Polyline();
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acPolyline.SetDatabaseDefaults();
                    // добавляем к полилинии вершины
                    acPolyline.AddVertexAt(0, new Point2d(20, 35), 0, 0, 0);
                    acPolyline.AddVertexAt(1, new Point2d(35, 35), 0, 0, 0);
                    acPolyline.AddVertexAt(2, new Point2d(35, 20), 0, 0, 0);
                    // добавляем созданный объект в определение блока и в транзакцию
                    btr.AppendEntity(acPolyline);
                    tr.AddNewlyCreatedDBObject(acPolyline, true);

                    // фиксируем транзакцию
                    tr.Commit();
                }
            }

            // создаем определение блока "block-2"
            public void createBlock_2()
            {
                // получаем ссылки на документ и его БД
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // имя создаваемого блока
                const string blockName = "block-2";

                // начинаем транзакцию
                Transaction tr = db.TransactionManager.StartTransaction();
                using (tr)
                {
                    // открываем таблицу блоков на запись
                    BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForWrite);

                    // проверяем, нет ли в таблице блока с таким именем; если есть - заканчиваем выполнение команды
                    if (bt.Has(blockName))
                    {
                        return;
                    }

                    // создаем новое определение блока, задаем ему имя
                    BlockTableRecord btr = new BlockTableRecord();
                    btr.Name = blockName;

                    // добавляем созданное определение блока в таблицу блоков и в транзакцию
                    bt.Add(btr);
                    tr.AddNewlyCreatedDBObject(btr, true);

                    // добавляем к блоку элементы

                    // создаем окружность
                    Circle acCircle = new Circle();
                    // устанавливаем параметры созданного объекта
                    acCircle.SetDatabaseDefaults();
                    acCircle.Center = Point3d.Origin;
                    acCircle.Radius = 25;
                    // добавляем созданный объект определение блока и в транзакцию
                    btr.AppendEntity(acCircle);
                    tr.AddNewlyCreatedDBObject(acCircle, true);

                    // создаем первую линию
                    Line acLine_1 = new Line(new Point3d(0, -25, 0), new Point3d(0, -50, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine_1.SetDatabaseDefaults();
                    // добавляем созданный объект в определение блока и в транзакцию
                    btr.AppendEntity(acLine_1);
                    tr.AddNewlyCreatedDBObject(acLine_1, true);

                    // создаем вторую линию
                    Line acLine_2 = new Line(new Point3d(-7, -39, 0), new Point3d(7, -39, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine_2.SetDatabaseDefaults();
                    // добавляем созданный объект в определение блока и в транзакцию
                    btr.AppendEntity(acLine_2);
                    tr.AddNewlyCreatedDBObject(acLine_2, true);

                    // фиксируем транзакцию
                    tr.Commit();
                }
            }

            // создаем объекты на нулевом слое
            public void layer_0_createObjects()
            {
                // получаем текущий документ и его БД
                Document doc = acad.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // открываем таблицу блоков документа
                    BlockTable acBlkTbl;
                    acBlkTbl = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;

                    // открываем пространство модели (Model Space) - оно является одной из записей в таблице блоков документа
                    BlockTableRecord ms = tr.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;

                    // добавляем розовую линию
                    Line acLine_1 = new Line(new Point3d(225, 225, 0), new Point3d(225, 175, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine_1.SetDatabaseDefaults();
                    // устанавливаем для объекта нужный слой и цвет
                    acLine_1.Layer = "0";
                    acLine_1.Color = color_Pink;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_1);
                    tr.AddNewlyCreatedDBObject(acLine_1, true);

                    // добавляем голубую линию
                    Line acLine_2 = new Line(new Point3d(250, 225, 0), new Point3d(250, 175, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine_2.SetDatabaseDefaults();
                    // устанавливаем для объекта нужный слой и цвет
                    acLine_2.Layer = "0";
                    acLine_2.Color = color_Blue;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_2);
                    tr.AddNewlyCreatedDBObject(acLine_2, true);

                    // добавляем салатовую линию
                    Line acLine_3 = new Line(new Point3d(275, 225, 0), new Point3d(275, 175, 0));
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine_3.SetDatabaseDefaults();
                    // устанавливаем для объекта нужный слой и цвет
                    acLine_3.Layer = "0";
                    acLine_3.Color = color_LightGreen;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_3);
                    tr.AddNewlyCreatedDBObject(acLine_3, true);

                    // добавляем розовую полилинию
                    Polyline acPolyline = new Polyline();
                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acPolyline.SetDatabaseDefaults();
                    // добавляем к полилинии вершины
                    acPolyline.AddVertexAt(0, new Point2d(300, 225), 0, 0, 0);
                    acPolyline.AddVertexAt(1, new Point2d(325, 175), 0, 0, 0);
                    acPolyline.AddVertexAt(2, new Point2d(350, 225), 0, 0, 0);
                    // устанавливаем для объекта нужный слой и цвет
                    acPolyline.Layer = "0";
                    acPolyline.Color = color_Pink;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acPolyline);
                    tr.AddNewlyCreatedDBObject(acPolyline, true);

                    // добавляем голубую окружность
                    Circle acCircle = new Circle();
                    // устанавливаем параметры созданного объекта
                    acCircle.SetDatabaseDefaults();
                    acCircle.Center = new Point3d(400, 200, 0);
                    acCircle.Radius = 25;
                    // устанавливаем для объекта нужный слой и цвет
                    acCircle.Layer = "0";
                    acCircle.Color = color_Blue;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acCircle);
                    tr.AddNewlyCreatedDBObject(acCircle, true);

                    // добавляем салатовый текст
                    DBText text = new DBText();
                    text.Position = new Point3d(450, 175, 0);
                    text.Height = 50;
                    text.TextString = "HABR!";
                    // устанавливаем для объекта нужный слой и цвет
                    text.Layer = "0";
                    text.Color = color_LightGreen;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(text);
                    tr.AddNewlyCreatedDBObject(text, true);

                    // фиксируем изменения
                    tr.Commit();
                }
            }

            // создаем объекты на слое "layer-1"
            public void layer_1_createObjects()
            {
                // получаем текущий документ и его БД
                Document doc = acad.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // запоминаем текущий слой и временно меняем его на нужный нам
                    // (это позволит не задавать слой отдельно для каждого создаваемого объекта)
                    ObjectId currentLayer = db.Clayer;
                    db.Clayer = layer_1;

                    // открываем таблицу блоков документа
                    BlockTable acBlkTbl;
                    acBlkTbl = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;

                    // открываем пространство модели (Model Space) - оно является одной из записей в таблице блоков документа
                    BlockTableRecord ms = tr.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;

                    // добавляем розовую линию
                    Line acLine_1 = new Line(new Point3d(225, 25, 0), new Point3d(225, -25, 0));
                    // устанавливаем параметры созданного объекта
                    acLine_1.SetDatabaseDefaults();
                    acLine_1.Color = color_Pink;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_1);
                    tr.AddNewlyCreatedDBObject(acLine_1, true);

                    // добавляем голубую линию
                    Line acLine_2 = new Line(new Point3d(250, 25, 0), new Point3d(250, -25, 0));
                    // устанавливаем параметры созданного объекта
                    acLine_2.SetDatabaseDefaults();
                    acLine_2.Color = color_Blue;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_2);
                    tr.AddNewlyCreatedDBObject(acLine_2, true);

                    // добавляем салатовую окружность
                    Circle acCircle = new Circle();
                    // устанавливаем параметры созданного объекта
                    acCircle.SetDatabaseDefaults();
                    acCircle.Center = new Point3d(300, 0, 0);
                    acCircle.Radius = 25;
                    acCircle.Color = color_LightGreen;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acCircle);
                    tr.AddNewlyCreatedDBObject(acCircle, true);

                    // добавляем розовый текст
                    DBText text = new DBText();
                    // устанавливаем параметры созданного объекта
                    text.Position = new Point3d(350, -25, 0);
                    text.Height = 50;
                    text.TextString = "HABR!";
                    text.Color = color_Pink;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(text);
                    tr.AddNewlyCreatedDBObject(text, true);

                    // добавляем вхождение блока "block-1"
                    // открываем таблицу блоков для чтения
                    BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                    // получаем ObjectID блока
                    ObjectId btrId = bt["block-1"];
                    // создаем новое вхождение блока, используя полученный ID определения блока
                    BlockReference br = new BlockReference(new Point3d(600, 0, 0), btrId);
                    // добавляем вхождение блока на пространство модели и в транзакцию
                    ms.AppendEntity(br);
                    tr.AddNewlyCreatedDBObject(br, true);

                    // возвращаем обратно старый текущий слой
                    db.Clayer = currentLayer;

                    // фиксируем изменения
                    tr.Commit();
                }
            }

            // создаем объекты на слое "layer-2"
            public void layer_2_createObjects()
            {
                // получаем текущий документ и его БД
                Document doc = acad.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // запоминаем текущий слой и временно меняем его на нужный нам
                    // (это позволит не задавать слой отдельно для каждого создаваемого объекта)
                    ObjectId currentLayer = db.Clayer;
                    db.Clayer = layer_2;

                    // открываем таблицу блоков документа
                    BlockTable acBlkTbl;
                    acBlkTbl = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;

                    // открываем пространство модели (Model Space) - оно является одной из записей в таблице блоков документа
                    BlockTableRecord ms = tr.GetObject(acBlkTbl[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;

                    // добавляем розовую линию
                    Line acLine_1 = new Line(new Point3d(225, -175, 0), new Point3d(225, -225, 0));
                    // устанавливаем параметры созданного объекта
                    acLine_1.SetDatabaseDefaults();
                    acLine_1.Color = color_Pink;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acLine_1);
                    tr.AddNewlyCreatedDBObject(acLine_1, true);

                    // добавляем голубую дугу
                    Arc acArc = new Arc(new Point3d(250, -200, 0), 25, -45 / 180.0 * Math.PI, 45 / 180.0 * Math.PI); 
                    // устанавливаем параметры созданного объекта
                    acArc.SetDatabaseDefaults();
                    acArc.Color = color_Blue;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acArc);
                    tr.AddNewlyCreatedDBObject(acArc, true);

                    // добавляем салатовую окружность
                    Circle acCircle = new Circle();
                    // устанавливаем параметры созданного объекта
                    acCircle.SetDatabaseDefaults();
                    acCircle.Center = new Point3d(325, -200, 0);
                    acCircle.Radius = 25;
                    acCircle.Color = color_LightGreen;
                    // добавляем созданный объект в пространство модели и в транзакцию
                    ms.AppendEntity(acCircle);
                    tr.AddNewlyCreatedDBObject(acCircle, true);

                    // добавляем вхождение блока "block-1"
                    // открываем таблицу блоков для чтения
                    BlockTable bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
                    // получаем ObjectID блока
                    ObjectId btrId = bt["block-1"];
                    // создаем новое вхождение блока, используя полученный ID определения блока
                    BlockReference br = new BlockReference(new Point3d(400, -200, 0), btrId);
                    // добавляем вхождение блока на пространство модели и в транзакцию
                    ms.AppendEntity(br);
                    tr.AddNewlyCreatedDBObject(br, true);

                    // добавляем вхождение блока "block-2"
                    // получаем ObjectID блока
                    btrId = bt["block-2"];
                    // создаем новое вхождение блока, используя полученный ID определения блока
                    br = new BlockReference(new Point3d(475, -200, 0), btrId);
                    // добавляем вхождение блока на пространство модели и в транзакцию
                    ms.AppendEntity(br);
                    tr.AddNewlyCreatedDBObject(br, true);

                    // возвращаем обратно старый текущий слой
                    db.Clayer = currentLayer;

                    // фиксируем изменения
                    tr.Commit();
                }
            }

            // создаем объекты при загрузке плагина
            public void Initialize()
            {
                createLayers();
                createBlock_1();
                createBlock_2();
                layer_0_createObjects();
                layer_1_createObjects();
                layer_2_createObjects();
            }

            // функция Terminate() необходима, чтобы реализовать интерфейс IExtensionApplication
            public void Terminate()
            {

            }
            

            //////////////////////////////////////////////////////////////
            // ПЕРВЫЙ ВАРИАНТ РЕАЛИЗАЦИИ ПОИСКА (<<MODEL SPACE>>)
            //////////////////////////////////////////////////////////////

            // команда для итерации по объектам (подход 1)
            [CommandMethod("Habr_IterateThroughAllObjects_1")]
            public void iterateThroughAllObjects_1()
            {
                // получаем текущую БД
                Database db = HostApplicationServices.WorkingDatabase;

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // получаем ссылку на пространство модели (ModelSpace)
                    BlockTableRecord ms = (BlockTableRecord)tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForRead);

                    // "пробегаем" по всем объектам в пространстве модели
                    foreach (ObjectId id in ms)
                    {
                        // приводим каждый из них к типу Entity
                        Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

                        // выводим в консоль слой (entity.Layer), тип (entity.GetType().ToString()) и цвет (entity.Color) каждого объекта
                        acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                            entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
                    }

                    tr.Commit();
                }
            }

            // команда для поиска окружностей (подход 1)
            [CommandMethod("Habr_FindCircles_1")]
            public void findCircles_1()
            {
                // получаем текущую БД
                Database db = HostApplicationServices.WorkingDatabase;

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // получаем ссылку на пространство модели (ModelSpace)
                    BlockTableRecord ms = (BlockTableRecord)tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForRead);

                    // "пробегаем" по всем объектам в пространстве модели
                    foreach (ObjectId id in ms)
                    {
                        // приводим каждый из них к типу Entity
                        Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

                        // если это окружность - выводим в консоль слой, тип и цвет каждого объекта

                        if (entity.GetType() == typeof(Circle))
                        {
                            acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                                entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
                        }
                    }

                    tr.Commit();
                }
            }

            // команда для поиска салатовых объектов (подход 1)
            [CommandMethod("Habr_FindLightGreenObjects_1")]
            public void findLightGreenObjects_1()
            {
                // получаем текущую БД
                Database db = HostApplicationServices.WorkingDatabase;

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // получаем ссылку на пространство модели (ModelSpace)
                    BlockTableRecord ms = (BlockTableRecord)tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForRead);

                    // "пробегаем" по всем объектам в пространстве модели
                    foreach (ObjectId id in ms)
                    {
                        // приводим каждый из них к типу Entity
                        Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);
                        
                        // если цвет объекта - салатовый, то выводим в консоль слой, тип и цвет каждого объекта
                        if (entity.Color == Autodesk.AutoCAD.Colors.Color.FromRgb(128, 255, 64))
                        {
                            acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                                entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
                        }
                    }

                    tr.Commit();
                }
            }

            // команда для поиска всех вхождений блока "block-1" (подход 1)
            [CommandMethod("Habr_FindBlocks_1")]
            public void findBlocks_1()
            {
                // получаем текущую БД
                Database db = HostApplicationServices.WorkingDatabase;

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // получаем ссылку на пространство модели (ModelSpace)
                    BlockTableRecord ms = (BlockTableRecord)tr.GetObject(SymbolUtilityServices.GetBlockModelSpaceId(db), OpenMode.ForRead);

                    // "пробегаем" по всем объектам в пространстве модели
                    foreach (ObjectId id in ms)
                    {
                        // приводим каждый из них к типу Entity
                        Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

                        // проверяем, является ли объект вхождением блока
                        if (entity.GetType() == typeof(BlockReference))
                        {
                            // если является - приводим его к типу BlockReference
                            BlockReference br = (BlockReference)entity;

                            // если имя соответствующего определения блока - "block-1", то выводим в консоль слой, тип и цвет каждого объекта
                            if (br.Name == "block-1")
                            {
                                acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                                    entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
                            }
                        }
                    }

                    tr.Commit();
                }
            }


            //////////////////////////////////////////////////////////////
            // ВТОРОЙ ВАРИАНТ РЕАЛИЗАЦИИ ПОИСКА (<<EDITOR.SELECTALL>>)
            //////////////////////////////////////////////////////////////

            // команда для итерации по объектам (подход 2)
            [CommandMethod("Habr_IterateThroughAllObjects_2")]
            public void iterateThroughAllObjects_2()
            {
                // получаем БД и Editor текущего документа
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;
                Editor ed = doc.Editor;

                // пытаемся получить ссылки на все объекты
                // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
                PromptSelectionResult selRes = ed.SelectAll();

                // если произошла ошибка - сообщаем о ней
                if (selRes.Status != PromptStatus.OK)
                {
                    ed.WriteMessage("\nError!\n");
                    return;
                }

                // получаем массив ID объектов
                ObjectId[] ids = selRes.Value.GetObjectIds();

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // "пробегаем" по всем полученным объектам
                    foreach (ObjectId id in ids)
                    {
                        // приводим каждый из них к типу Entity
                        Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

                        // выводим в консоль слой (entity.Layer), тип (entity.GetType().ToString()) и цвет (entity.Color) каждого объекта
                        acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                            entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
                    }

                    tr.Commit();
                }
            }

            // команда для поиска окружностей (подход 2)
            [CommandMethod("Habr_FindCircles_2")]
            public void findCircles_2()
            {
                // получаем БД и Editor текущего документа
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;
                Editor ed = doc.Editor;

                // создаем переменную, в которой будут содержаться данные для фильтра
                TypedValue[] filterlist = new TypedValue[1];

                // первый аргумент (0) указывает, что мы задаем тип объекта
                // второй аргумент ("CIRCLE") - собственно тип
                filterlist[0] = new TypedValue(0, "CIRCLE");
                
                // создаем фильтр
                SelectionFilter filter = new SelectionFilter(filterlist);

                // пытаемся получить ссылки на объекты с учетом фильтра
                // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
                PromptSelectionResult selRes = ed.SelectAll(filter);

                // если произошла ошибка - сообщаем о ней
                if (selRes.Status != PromptStatus.OK)
                {
                    ed.WriteMessage("\nError!\n");
                    return;
                }

                // получаем массив ID объектов
                ObjectId[] ids = selRes.Value.GetObjectIds();

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // "пробегаем" по всем полученным объектам
                    foreach (ObjectId id in ids)
                    {
                        // приводим каждый из них к типу Entity
                        Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

                        // выводим в консоль слой, тип и цвет каждого объекта
                        acad.DocumentManager.MdiActiveDocument.Editor.WriteMessage(string.Format("\nLayer:{0}; Type:{1}; Color: {2},{3},{4}\n",
                            entity.Layer, entity.GetType().ToString(), entity.Color.Red.ToString(), entity.Color.Green.ToString(), entity.Color.Blue.ToString()));
                    }

                    tr.Commit();
                }
            }


            
            //////////////////////////////////////////////////////////////
            // РЕДАКТИРОВАНИЕ ОБЪЕКТОВ
            //////////////////////////////////////////////////////////////

            // команда для удаления окружностей
            [CommandMethod("Habr_EraseCircles_2")]
            public void eraseCircles_2()
            {
                // получаем БД и Editor текущего документа
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;
                Editor ed = doc.Editor;

                // создаем переменную, в которой будут содержаться данные для фильтра
                TypedValue[] filterlist = new TypedValue[1];

                // первый аргумент (0) указывает, что мы задаем тип объекта
                // второй аргумент ("CIRCLE") - собственно тип
                filterlist[0] = new TypedValue(0, "CIRCLE");

                // создаем фильтр
                SelectionFilter filter = new SelectionFilter(filterlist);

                // пытаемся получить ссылки на объекты с учетом фильтра
                // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
                PromptSelectionResult selRes = ed.SelectAll(filter);

                // если произошла ошибка - сообщаем о ней
                if (selRes.Status != PromptStatus.OK)
                {
                    ed.WriteMessage("\nError!\n");
                    return;
                }

                // получаем массив ID объектов
                ObjectId[] ids = selRes.Value.GetObjectIds();

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // "пробегаем" по всем полученным объектам
                    foreach (ObjectId id in ids)
                    {
                        // приводим каждый из них к типу Entity
                        Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

                        // открываем приговоренный объект на запись
                        entity.UpgradeOpen();

                        // удаляем объект
                        entity.Erase();
                    }

                    tr.Commit();
                }
            }


            // команда для перекраски всех объектов в оранжевый цвет
            [CommandMethod("Habr_RepaintOrange_2")]
            public void repaintOrange_2()
            {
                // получаем БД и Editor текущего документа
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;
                Editor ed = doc.Editor;

                // пытаемся получить ссылки на все объекты
                // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
                PromptSelectionResult selRes = ed.SelectAll();

                // если произошла ошибка - сообщаем о ней
                if (selRes.Status != PromptStatus.OK)
                {
                    ed.WriteMessage("\nError!\n");
                    return;
                }

                // получаем массив ID объектов
                ObjectId[] ids = selRes.Value.GetObjectIds();

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // "пробегаем" по всем полученным объектам
                    foreach (ObjectId id in ids)
                    {
                        // приводим каждый из них к типу Entity
                        Entity entity = (Entity)tr.GetObject(id, OpenMode.ForRead);

                        // открываем объект на запись
                        entity.UpgradeOpen();

                        // изменяем цвет на оранжевый
                        entity.Color = Autodesk.AutoCAD.Colors.Color.FromRgb(255, 128, 0);
                    }

                    tr.Commit();
                }
            }


            // команда для изменения радиуса окружностей
            [CommandMethod("Habr_ModifyCircles_2")]
            public void modifyCircles_2()
            {
                // получаем БД и Editor текущего документа
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;
                Editor ed = doc.Editor;

                // создаем переменную, в которой будут содержаться данные для фильтра
                TypedValue[] filterlist = new TypedValue[1];

                // первый аргумент (0) указывает, что мы задаем тип объекта
                // второй аргумент ("CIRCLE") - собственно тип
                filterlist[0] = new TypedValue(0, "CIRCLE");

                // создаем фильтр
                SelectionFilter filter = new SelectionFilter(filterlist);

                // пытаемся получить ссылки на объекты с учетом фильтра
                // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
                PromptSelectionResult selRes = ed.SelectAll(filter);

                // если произошла ошибка - сообщаем о ней
                if (selRes.Status != PromptStatus.OK)
                {
                    ed.WriteMessage("\nError!\n");
                    return;
                }

                // получаем массив ID объектов
                ObjectId[] ids = selRes.Value.GetObjectIds();

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // "пробегаем" по всем полученным объектам
                    foreach (ObjectId id in ids)
                    {
                        // приводим каждый из них к типу Circle
                        Circle cir = (Circle)tr.GetObject(id, OpenMode.ForRead);

                        // открываем объект на запись
                        cir.UpgradeOpen();
                        
                        // увеличиваем радиус
                        cir.Radius = cir.Radius * 2;
                    }

                    tr.Commit();
                }
            }

            // команда для перемещения блоков
            [CommandMethod("Habr_MoveBlocks_2")]
            public void moveBlocks_2()
            {
                // получаем БД и Editor текущего документа
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;
                Editor ed = doc.Editor;

                // создаем переменную, в которой будут содержаться данные для фильтра
                TypedValue[] filterlist = new TypedValue[1];

                // первый аргумент (0) указывает, что мы задаем тип объекта
                // второй аргумент ("INSERT") - собственно тип
                filterlist[0] = new TypedValue(0, "INSERT");

                // создаем фильтр
                SelectionFilter filter = new SelectionFilter(filterlist);

                // пытаемся получить ссылки на объекты с учетом фильтра
                // ВНИМАНИЕ! Нужно проверить работоспособность метода с замороженными и заблокированными слоями!
                PromptSelectionResult selRes = ed.SelectAll(filter);

                // если произошла ошибка - сообщаем о ней
                if (selRes.Status != PromptStatus.OK)
                {
                    ed.WriteMessage("\nError!\n");
                    return;
                }

                // получаем массив ID объектов
                ObjectId[] ids = selRes.Value.GetObjectIds();

                // начинаем транзакцию
                using (Transaction tr = db.TransactionManager.StartTransaction())
                {
                    // "пробегаем" по всем полученным объектам
                    foreach (ObjectId id in ids)
                    {
                        // приводим каждый из них к типу BlockReference
                        BlockReference br = (BlockReference)tr.GetObject(id, OpenMode.ForRead);

                        // открываем объект на запись
                        br.UpgradeOpen();

                        // перемещаем вхождение блока
                        br.Position = Point3d.Origin;
                    }

                    tr.Commit();
                }
            }



            //////////////////////////////////////////////////////////////
            // БОНУСНЫЙ ВАРИАНТ РЕАЛИЗАЦИИ ПОИСКА (<<ЧЕРНАЯ МАГИЯ>>)
            //////////////////////////////////////////////////////////////

            // команда для итерации по объектам (подход 3)
            // Осторожно! Используется черная магия!
            [CommandMethod("Habr_IterateThroughAllObjects_3")]
            public void iterateThroughAllObjects_3()
            {
                Database db = HostApplicationServices.WorkingDatabase;
                Editor ed = Application.DocumentManager.MdiActiveDocument.Editor;
                long amount = 0;
                Dictionary<string, int> d = new Dictionary<string, int>();
                ObjectId id = ObjectId.Null;
                for (long i = db.BlockTableId.Handle.Value; i < db.Handseed.Value; i++)
                {
                    Handle h = new Handle(i);
                    if (db.TryGetObjectId(h, out id) && !id.IsNull && id.IsValid && !id.IsErased)
                    {
                        string t = id.ObjectClass.DxfName;
                        amount++;
                        if (d.ContainsKey(t))
                            d[t]++;
                        else
                            d.Add(t, 1);
                    }
                }
                foreach (KeyValuePair<string, int> kvp in d)
                    ed.WriteMessage("\n{0}: {1} ", kvp.Key, kvp.Value);
                ed.WriteMessage("\nTotal {0} objects in drawing\n", amount);
            }
        }
    }
}


Как всегда, буду рад любым отзывам и комментариям.

Комментарии 11

    0
    В общем, если у вас есть чертеж с огромным количеством объектов, и нужно все их обработать, и выхода нет, и скоро рассвет — то вот вам ссылочка (англ.), где Андрей Бушман hwd, Александр Ривилис и зарубежные эксперты развлекаются, оптимизируя эту задачку.

    Наиболее рациональный и быстрый способ итерации показан здесь в методе, имеющем сигнатуру:

    public static Db.ObjectId[] GetDBObjectIds(this Db.Database db, Func<Db.ObjectId, Boolean> filter).
      0
      Спасибо, дополнил статью.
      0
      Важно! Если объект находится на заблокированном слое — доступ к нему на запись получить не удастся, и мы сможем полюбоваться на такое сообщение:

      Используйте конструкцию try...catch!

      // Обрати внимание на последний параметр
      DBObject obj = tr.GetObject(id, OpenMode.ForWrite, false, true);
        0
        Действительно, сигнатура

        GetObject(ObjectId id, OpenMode mode, Boolean openErased, Boolean forceOpenOnLockedLayer)

        выглядит многообещающе.

        Документацию я посмотрел, но не нашел там, осуществляется ли блокировка слоя при выходе из транзакции. Не подскажете, будет слой потом блокироваться обратно?
          0
          Документацию я посмотрел

          Это не документация. Документация — это содержимое подкаталога docs распакованного архива с ObjectARX SDK, а так же на официальном сайте в разделе Documentation.
          Не подскажете, будет слой потом блокироваться обратно?

          Что мешает проверить самому? ;)
            0
            Ничего, кроме прирожденной лени...))

            Действительно, код

            Circle cir = (Circle)tr.GetObject(id, OpenMode.ForWrite, false, true);
            cir.Radius = cir.Radius * 2;

            отрабатывает корректно даже в случае заблокированного слоя. После отрабатывания кода исходный слой остается в том же состоянии, в котором был до запуска команды.

            Интересная штука, не слышал о такой. Добавлю в статью, спасибо.
        0
        Кстати, официальная документация далеко не идеальна (к сожалению). В виду этого очень полезно просматривать реальный состав библиотек AutoCAD .NET API через Object Browser. Можно найти много интересного и полезного из того, что в документации отсутствует, в то время как по факту тот или иной функционал в API присутствует уже не один год (не раз сталкивался с этим).
          0
          Но ведь в этом случае о назначении функции и ее параметров можно будет только гадать, просматривая сигнатуры?
            0
            Во-первых, нередко имена параметров говорят об их назначении. Во-вторых, если это имя тебе ни о чём не говорит, то никто не запрещает посмотреть код метода при помощи либо бесплатного ILSpy, либо платного .NET Reflector.
          0
          Спасибо за статьи по AutoCAD API! Очень помогли.
            0
            Спасибо за отзыв! Рад, что пригодились.)

            Вообще было у меня в планах еще несколько статей — про динамические блоки, про пример взаимодействия с внешним приложением — но что-то времени стало не хватать катастрофически. Не знаю, когда теперь закончу и закончу ли вообще.
            Честно говоря, я изрядно удивлен, что шесть-то смог написать.))

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое