...

пятница, 5 июня 2015 г.

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

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


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

Важно уметь четко разграничивать два понятия: определение блока и вхождение блока. Подробное описание различий приведено здесь (англ.) и тут (rus). Вкратце изложу суть: когда мы создаем новый блок, AutoCAD помещает его описание в специальную таблицу блоков. Это описание называется определением блока (block definition). Определение блока существует исключительно в таблице блоков и на чертеже не отображается. Непосредственно на поле чертежа AutoCAD помещает вхождение блока (block reference) — ссылку на определение блока. При изменении определения блока все вхождения блока повторюят эти изменения. Далее в статье я иногда буду опускать первое слово («определение» или «вхождение») и писать просто «блок».

NB:

Должен сказать, что лично мне перевод «экземпляр блока» нравится гораздо больше, чем «вхождение блока». Но раз уж так написано в документации…
Как говорится, почитай отца своего, почитай мать свою и почитай руководство по эксплуатации.


В процессе общения с Autocad я сталкивался с двумя видами блоков: обычными и динамическими. Главная особенность вторых — возможность задать их элементам некоторые «настраиваемые» параметры (например, длину линии в блоке или угол ее наклона). Главный минус динамических блоков в том, что их невозможно создать средствами .NET API.

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


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

В командной строке AutoCAD выполним команду CIRCLE, затем укажем центр и радиус. На чертеже появится окружность:

Выделим окружность и выполним команду BLOCK. На экране появится меню создания блока. К аналогичному результату приведет нажатие кнопки «Create» на панели «Block»:

Зададим блоку имя, выставим единицы измерения «Unitless» и снимем флажок «Open in Block Editor», после чего нажмем «ОК»:

NB:

Выбор единиц измерения позволяет задать блоку размер, который будет автоматически пересчитываться в разных системах измерения (европейской, с метрами и миллиметрами — и американской, с футами и дюймами). Если выставить значение «Unitless», то блок будет использовать те единицы измерения, которые установлены в чертеже. Я всегда выставлял значение «Unitless»; хорошо это или плохо — не знаю.


После этого AutoCAD попросит указать на чертеже точку, которая будет являться для блока базовой. Обычно в качестве этой точки задают центр блока или его нижний левый угол. Укажем в качестве базовой точки центр окружности.

Мы завершили создание блока. Первое вхождение блока будет добавлено на чертеж автоматически и заменит собой элементы, из которых был создан этот блок. Чтобы добавить на чертеж несколько вхождений созданного блока, можно использовать команду INSERT или одноименную кнопку на панели «Block»:

После выполнения команды или нажатия на кнопку на экране появится окно вставки блока:

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

Для редактирования блоков используется редактор, который вызывается командой BEDIT или соответствующей кнопкой на панели «Block»:

После выполнения команды или нажатия на упомянутую кнопку на экране появится окно выбора определения блока:

В этом окне необходимо выбрать определение, которое мы будем редактировать, и нажать «OK». После этого AutoCAD откроет редактор блоков. Пробежимся по самым, на мой взгляд, важным кнопкам панели редактора:

  • Save Block — сохранить все сделанные изменения. После закрытия редактора блоков все вхождения блока, размещенные на чертеже, будут обновлены в соответствии с изменениями определения блока.
  • Authoring Palettes — показать или скрыть окно атрибутов. Это окно пригодится при работе с динамическими блоками.
  • Point — в этом выпадающем подменю находится пункт «Basepoint», с помощью которого можно задать блоку новую базовую точку.
  • Close Block Editor — закрыть редактор блоков. Все несохраненные изменения будут потеряны.

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

На этом краткий экскурс в создание блоков в AutoCAD завершен. Можно переходить к коду. :-)

Пример создания простого блока


Этот раздел базируется на соответствующем посте в блоге Kean Walmsley (наш пример по сравнению с первоисточником будет заметно облегчен). Мы будем создавать простейший блок, состоящий из полилинии и окружности.
немного о колбасных обрезках
Кстати, раз уж зашел разговор про колбасные обрезки…
За последние пару лет (боже, как время-то летит!) произошло много всего хорошего. В частности, Самая Лучшая И Величайшая Компания В Мире все-таки выпустила бесплатную профессиональную версию Visual Studio.
Да, Visual Studio Community Edition занимает больше места, чем Express, и интерфейс у нее чуть посложнее — но рано или поздно у разработчика обычно возникает необходимость работы с дополнениями IDE, а они в Express-версиях не поддерживаются. Такие дела.
Пример из жизни: автор этих строк весело писал плагин в VS Express, и ничто не предвещало беды… пока не встала задача добавить программу установки (msi-файл, который создается с помощью технологии Wix). В итоге весь код инсталлятора пришлось писать в Notepad++, ежеминутно сверяясь с документацией, и компилировать вручную.
А в Visual Studio Professional можно было бы просто подключить плагин для работы с Wix и делать все прямо в IDE — с подсказками, автодополнением кода и компиляцией прямо в проекте. Можно. Было бы…
В общем, если вы работаете в компании, у которой не больше 250 ПК и годовой доход менее $1 000 000, то очень рекомендую при наличии пары-тройки лишних, бессмысленных часов жизни потратить их на установку этого продукта и далее работать с ним.
Если же бессмысленных часов в вашей жизни нет, то осознайте, что все — тлен, жизнь — боль, а влачить свое существование без установленной Visual Studio Community Edition — значит лишь увеличивать энтропию этого мира.

Чтобы создать блок и поместить его вхождение на чертеж, необходимо сделать следующее:
  1. Создать в таблице блоков новую запись (определение блока).
  2. Добавить в определение блока необходимые геометрические объекты.
  3. Добавить на чертеж вхождение блока.

Итак, приступим.

Я уже не буду подробно останавливаться на таких мелких особенностях создания плагина, как подключение внешних ссылок, запрет CopyLocal, указание версии .NET и тому подобное — это неоднократно обговаривалось в прошлых постах цикла. В этом примере нам потребуются ссылки на библиотеки AcMgd и AcDBMgd. Кода будет немного; сразу приведу его весь, а потом разберем детали.

Код:
using System;
using System.IO;
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_SimpleBlock
{
    public class ClassMyAutoCADDLL_SimpleBlock
    {
        public class Commands : IExtensionApplication
        {
            // эта функция будет вызываться при выполнении в AutoCAD команды "HabrCommand"
            [CommandMethod("HabrCommand")]
            public void HabrCommand()
            {
                // получаем ссылки на документ и его БД
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // поле документа "Editor" понадобится нам для вывода сообщений в окно консоли AutoCAD
                Editor ed = doc.Editor;

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

                // начинаем транзакцию
                Transaction tr = db.TransactionManager.StartTransaction();
                using (tr)
                {
                    //***
                    // ШАГ 1 - создаем новую запись в таблице блоков
                    //***

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

                    // вначале проверяем, нет ли в таблице блока с таким именем
                    // если есть - выводим сообщение об ошибке и заканчиваем выполнение команды
                    if (bt.Has(blockName))
                    {
                        ed.WriteMessage("\nA block with the name \"" + blockName + "\" already exists.");
                        return;
                    }

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

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

                    //***
                    // ШАГ 2 - добавляем к созданной записи необходимые геометрические примитивы
                    //***

                    // создаем полилинию
                    Polyline poly = new Polyline();
                    poly.SetDatabaseDefaults();
                    poly.AddVertexAt(0, new Point2d(-50, -125), 0, 0, 0);
                    poly.AddVertexAt(1, new Point2d(-50, 105), 0, 0, 0);
                    poly.AddVertexAt(2, new Point2d(-20, 125), 0, 0, 0);
                    poly.AddVertexAt(3, new Point2d(20, 125), 0, 0, 0);
                    poly.AddVertexAt(4, new Point2d(50, 105), 0, 0, 0);
                    poly.AddVertexAt(5, new Point2d(50, -125), 0, 0, 0);
                    poly.AddVertexAt(6, new Point2d(-50, -125), 0, 0, 0);

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

                    // создаем окружность
                    Circle cir = new Circle();
                    cir.SetDatabaseDefaults();
                    cir.Center = new Point3d(0, 90, 0);
                    cir.Radius = 15;

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

                    // создаем текст
                    DBText text = new DBText();
                    text.Position = new Point3d(-25, -95, 0);
                    text.Height = 35;
                    text.TextString = "BC";

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

                    //***
                    // ШАГ 3 - добавляем вхождение созданного блока на чертеж
                    //***

                    // открываем пространство модели на запись
                    BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);

                    // создаем новое вхождение блока, используя ранее сохраненный ID определения блока
                    BlockReference br = new BlockReference(Point3d.Origin, btrId);

                    // добавляем созданное вхождение блока на пространство модели и в транзакцию
                    ms.AppendEntity(br);
                    tr.AddNewlyCreatedDBObject(br, true);

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

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

            }

            public void Terminate()
            {

            }
        }
    }
}


Поcле загрузки плагина и выполения команды HabrCommand получим такую картинку:

Теперь давайте разбираться, что происходит в коде.

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

Разыскать подробную информацию по классу Editor я с ходу не смог. Буду рад, если кто-то из более знающих людей подскажет ссылку на вменяемое описание этого класса и его методов (или хотя бы где его найти в ObjectARX Reference). В этом примере класс Editor используется только для вывода сообщения о том, что блок с таким именем уже существует в БД. В AutoCAD это сообщение выглядит так:

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

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

  1. открываем таблицу блоков (запрашиваем доступ на запись, поскольку будем вносить изменения);
  2. проверяем, нет ли в таблице блока с таким именем (при попытке создать блок с именем, которое уже есть в таблице блоков, мы получим исключение eDuplicateRecordName, которое нам совершенно не нужно);
  3. создаем новое определение блока;
  4. задаем созданному определению блока имя;
  5. добавляем созданное определение блока в таблицу блоков и в транзакцию.

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

Добавление на чертеж полилинии и окружности разбиралось в предыдущей статье; с текстом все происходит аналогично. Добавление к блоку графического объекта обычно происходит в три приема:

  1. создается необходимый объект;
  2. задаются свойства этого объекта;
  3. созданный объект добавляется в определение блока и в транзакцию.

Это применимо как к полилинии и окружности, так и к тексту. Какая часть кода за какой пункт отвечает — вполне очевидно. Почитать подробнее про добавление текста на чертеж можно здесь (англ.) и тут (rus).

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

  1. открывается на запись пространство модели;
  2. создается новое вхождение блока;
  3. созданное вхождение блока добавляется на пространство модели и в транзакцию.

Остановимся чуть подробнее на конструкторе класса BlockReference, используемом при создании нового вхождения блока. Данный конструктор принимает два параметра: точку размещения блока (с этой точкой будет совмещена базовая точка вхождения блока) и ID определения блока. Иными словами, мы должны сообщить AutoCAD, какой блок мы хотим увидеть и где. В нашем примере в качестве точки размещения блока указано начало координат (Point3d.Origin), а ID определения блока мы сохранили еще на первом шаге, при создании этого определения.

Использование блоков, уже имеющихся в таблице блоков документа


Если в предыдущем примере выполнить команду HabrCommand, а затем удалить с чертежа появившийся блок и выполнить команду HabrCommand еще раз, то мы увидим сообщение «A block with the name „pvtBlock“ already exists», и на чертеже ничего не появится. Чтобы использовать ранее созданное определение блока, нужно узнать ObjectID этого определения. В нашем случае код можно переписать так:
Код команды:
[CommandMethod("HabrCommand")]
public void HabrCommand()
{
    // получаем ссылки на документ и его БД
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;

    // поле документа "Editor" понадобится нам для вывода сообщений в окно консоли AutoCAD
    Editor ed = doc.Editor;

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

    // начинаем транзакцию
    Transaction tr = db.TransactionManager.StartTransaction();
    using (tr)
    {
        //***
        // ШАГ 1 - создаем новую запись в таблице блоков (или получаем ID существующей)
        //***

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

        // проверяем, нет ли в таблице блока с таким именем
        // если есть - используем его ID
        ObjectId btrId;
        if (bt.Has(blockName))
        {
            btrId = bt[blockName];
        }
        else
        {
            // открываем таблицу блоков на запись
            bt.UpgradeOpen();

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

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

            //***
            // ШАГ 2 - добавляем к созданной записи необходимые геометрические примитивы
            //***

            // создаем полилинию
            Polyline poly = new Polyline();
            poly.SetDatabaseDefaults();
            poly.AddVertexAt(0, new Point2d(-50, -125), 0, 0, 0);
            poly.AddVertexAt(1, new Point2d(-50, 105), 0, 0, 0);
            poly.AddVertexAt(2, new Point2d(-20, 125), 0, 0, 0);
            poly.AddVertexAt(3, new Point2d(20, 125), 0, 0, 0);
            poly.AddVertexAt(4, new Point2d(50, 105), 0, 0, 0);
            poly.AddVertexAt(5, new Point2d(50, -125), 0, 0, 0);
            poly.AddVertexAt(6, new Point2d(-50, -125), 0, 0, 0);

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

            // создаем окружность
            Circle cir = new Circle();
            cir.SetDatabaseDefaults();
            cir.Center = new Point3d(0, 90, 0);
            cir.Radius = 15;

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

            // создаем текст
            DBText text = new DBText();
            text.Position = new Point3d(-33, -95, 0);
            text.Height = 35;
            text.TextString = "BC";

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

        //***
        // ШАГ 3 - добавляем вхождение созданного блока на чертеж
        //***

        // открываем пространство модели на запись
        BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);

        // создаем новое вхождение блока, используя ранее сохраненный ID определения блока
        BlockReference br = new BlockReference(Point3d.Origin, btrId);

        // добавляем вхождение блока на пространство модели и в транзакцию
        ms.AppendEntity(br);
        tr.AddNewlyCreatedDBObject(br, true);

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


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

В рассмотренном примере я для разнообразия использовал метод UpgradeOpen(), который довольно часто фигурирует в примерах и документации. Давайте разберем, как он работает.

Если в первом примере мы сразу открывали таблицу блоков на запись (OpenMode.ForWrite), то теперь мы вначале открываем ее только на чтение (OpenMode.ForRead). Этого нам хватит, чтобы просмотреть таблицу и выяснить, содержится ли в ней блок с нашим именем. Если такой блок найдется, то мы можем прочитать его ObjectID, для чего нам опять-таки хватит доступа только на чтение. Если же блока с нашим именем в таблице нет, то нам нужно его добавить, и для этого уже потребуется доступ на запись. Чтобы получить такой доступ, используется метод UpgradeOpen() — после вызова этого метода мы можем работать с таблицей блоков, как будто открыли ее с уровнем доступа OpenMode.ForWrite.

NB:

Использование UpgradeOpen(), наверное, является хорошей практикой. По крайней мере, Kean Walmsley точно так делает. Но подробных данных о том, какие сокровища и жизненные блага достаются тем, кто использует UpgradeOpen(), у меня нет. Сам я в работе эту конструкцию не использовал и особых огорчений по этому поводу не испытывал. Если кто-то может представить свою позицию по использованию UpgradeOpen() — рад буду увидеть ее в комментариях или ЛС.


Изменение позиции вставки блока


В каком месте чертежа появится блок, заранее предсказать невозможно зависит от двух вещей:
  • от базовой точки, заданной в определении блока;
  • от точки вставки, заданной во вхождении блока.

В нашем примере базовая точка расположена в центре блока.
Картинка:

Заметим, что при создании определения блока мы нигде не задавали базовую точку в явном виде — в подобном случае AutoCAD считает, что базовой точкой является начало координат. В нашем примере «центр» фигуры был специально подобран так, чтобы он совпал с центром координат.

Изменить базовую точку определения блока можно вручную с помощью редактора блоков (как это сделать, говорилось в первом разделе поста) или средствами .NET API. В последнем случае для этой цели используется свойство Origin класса BlockTableRecord. В качестве примера давайте перенесем базовую точку определения блока в нижний левый угол. Для этого после указания имени блока зададим свойство Origin:

// задаем имя блока
btr.Name = blockName;

// задаем базовую точку
btr.Origin = new Point3d(-50, -125, 0);


Числа -50 и -125 — это минимальные координаты нашего блока по осям X и Y соответственно (такие координаты имеет нижняя левая вершина полилинии, образующей внешнюю границу нашего блока).
Результат:

Блок сдвинулся.


Теперь разберемся с точкой вставки блока. Она задается для каждого вхождения блока и показывает, где после вставки блока окажется его базовая точка. Это свойство задается в конструкторе класса BlockReference — например, так:
BlockReference br = new BlockReference(new Point3d(150, 150, 0), btrId);


После одновременного изменения базовой точки и точки вставки мы получим такую картину:

Блок сдвинулся опять.


Точку вставки блока можно также изменить с помощью свойства Position класса BlockReference:
BlockReference br = new BlockReference(Point3d.Origin, btrId);
br.Position = new Point3d(150, 150, 0);


Эффект будет таким же, как и при задании точки непосредственно в конструкторе класса BlockReference.

Использование блоков внутри блока


Мы тут все любим блоки, так что давайте-ка добавим пару блоков в наш блок, чтобы упростить чертеж, пока мы упрощаем чертеж.
Yo dawg!

Добавление блока в определение блока не представляет никакой сложности. Главная особенность заключается в том, что если уж мы добавляем в блок не геометрический примитив, а вхождение вспомогательного блока, то нам обязательно нужно позаботиться о том, чтобы определение этого вспомогательного блока имелось в таблице блоков документа.
Код команды:
[CommandMethod("HabrCommand")]
public void HabrCommand()
{
    // получаем ссылки на документ и его БД
    Document doc = Application.DocumentManager.MdiActiveDocument;
    Database db = doc.Database;

    // поле документа "Editor" понадобится нам для вывода сообщений в окно консоли AutoCAD
    Editor ed = doc.Editor;

    // имя создаваемого блока
    const string blockName = "ltBlock";
    // имя вспомогательного блока
    const string auxBlockName = "starBlock";

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

        //***
        // ШАГ 0 - создаем новую запись вспомогательного блока в таблице блоков (или получаем ID существующей)
        //***

        // проверяем, нет ли в таблице блока с таким именем
        // если есть - используем его ID
        ObjectId btrIdAux;
        if (bt.Has(auxBlockName))
        {
            btrIdAux = bt[auxBlockName];
        }
        else
        {
            // создаем новое определение блока, задаем ему имя
            BlockTableRecord btr = new BlockTableRecord();
            btr.Name = auxBlockName;

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

            // создаем полилинию
            Polyline poly = new Polyline();
            poly.SetDatabaseDefaults();
            poly.AddVertexAt(0, new Point2d(0, -6), 0, 0, 0);
            poly.AddVertexAt(1, new Point2d(-9, -12), 0, 0, 0);
            poly.AddVertexAt(2, new Point2d(-7, -2), 0, 0, 0);
            poly.AddVertexAt(3, new Point2d(-14, 6), 0, 0, 0);
            poly.AddVertexAt(4, new Point2d(-5, 6), 0, 0, 0);
            poly.AddVertexAt(5, new Point2d(0, 15), 0, 0, 0);
            poly.AddVertexAt(6, new Point2d(5, 6), 0, 0, 0);
            poly.AddVertexAt(7, new Point2d(14, 6), 0, 0, 0);
            poly.AddVertexAt(8, new Point2d(7, -2), 0, 0, 0);
            poly.AddVertexAt(9, new Point2d(9, -12), 0, 0, 0);
            poly.AddVertexAt(10, new Point2d(0, -6), 0, 0, 0);

            // добавляем полилинию в определение блока и в транзакцию
            btr.AppendEntity(poly);
            tr.AddNewlyCreatedDBObject(poly, true);
        }
        //***
        // определение вспомогательного блока создано (или получен ID этого определения)
        //***

        //***
        // ШАГ 1 - создаем новую запись в таблице блоков (или получаем ID существующей)
        //***

        // проверяем, нет ли в таблице блока с таким именем
        // если есть - используем его ID
        ObjectId btrId;
        if (bt.Has(blockName))
        {
            btrId = bt[blockName];
        }
        else
        {
            // создаем новое определение блока, задаем ему имя
            BlockTableRecord btr = new BlockTableRecord();
            btr.Name = blockName;

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

            //***
            // ШАГ 2 - добавляем к созданной записи необходимые геометрические объекты
            //***

            // создаем полилинию
            Polyline polyExt = new Polyline();
            polyExt.SetDatabaseDefaults();
            polyExt.AddVertexAt(0, new Point2d(-50, -125), 0, 0, 0);
            polyExt.AddVertexAt(1, new Point2d(-50, 105), 0, 0, 0);
            polyExt.AddVertexAt(2, new Point2d(-20, 125), 0, 0, 0);
            polyExt.AddVertexAt(3, new Point2d(20, 125), 0, 0, 0);
            polyExt.AddVertexAt(4, new Point2d(50, 105), 0, 0, 0);
            polyExt.AddVertexAt(5, new Point2d(50, -125), 0, 0, 0);
            polyExt.AddVertexAt(6, new Point2d(-50, -125), 0, 0, 0);

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

            // создаем еще одну полилинию
            Polyline polyIn = new Polyline();
            polyIn.SetDatabaseDefaults();
            polyIn.AddVertexAt(0, new Point2d(-5, -125), 0, 0, 0);
            polyIn.AddVertexAt(1, new Point2d(-5, 125), 0, 0, 0);
            polyIn.AddVertexAt(2, new Point2d(5, 125), 0, 0, 0);
            polyIn.AddVertexAt(3, new Point2d(5, -125), 0, 0, 0);

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

            // создаем окружность
            Circle cir = new Circle();
            cir.SetDatabaseDefaults();
            cir.Center = new Point3d(0, 90, 0);
            cir.Radius = 15;

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

            // добавляем звездочки в определение блока и в транзакцию
            BlockReference starLeft = new BlockReference(new Point3d(-27, -75, 0), btrIdAux);
            btr.AppendEntity(starLeft);
            tr.AddNewlyCreatedDBObject(starLeft, true);

            BlockReference starRight = new BlockReference(new Point3d(27, -75, 0), btrIdAux);
            btr.AppendEntity(starRight);
            tr.AddNewlyCreatedDBObject(starRight, true);
        }

        //***
        // ШАГ 3 - добавляем вхождение созданного блока на чертеж
        //***

        // открываем пространство модели на запись
        BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);

        // создаем новое вхождение блока, используя ранее сохраненный ID определения блока
        BlockReference br = new BlockReference(new Point3d(150, 150, 0), btrId);

        // добавляем вхождение блока на пространство модели и в транзакцию
        ms.AppendEntity(br);
        tr.AddNewlyCreatedDBObject(br, true);

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


Результат:

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

В качестве несложного самостоятельного упражнения читателям предлагается выполнить покраску элементов блока и приведение их размеров и расположения в соответствие с Приказом № 1500.

Образец:

Зачем применять блоки?


Основных причин две… Начнем со второй.

Вторая причина


Когда на чертеже содержится большое количество однотипных объектов, использование блоков здорово сокращает размер файла чертежа. Прочувствовать это нам поможет следующий код:
Код плагина:
using System;
using System.IO;
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_SimpleBlock
{
    public class ClassMyAutoCADDLL_SimpleBlock
    {
        public class Commands : IExtensionApplication
        {
            // функция, рисующая набор геометрических фигур в заданной точке чертежа
            public void drawFigure(double x, double y)
            {
                // получаем ссылки на документ и его БД
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

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

                    // открываем пространство модели на запись
                    BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);

                    // создаем полилинию
                    Polyline polyExt = new Polyline();
                    polyExt.SetDatabaseDefaults();
                    polyExt.AddVertexAt(0, new Point2d(x - 50, y - 125), 0, 0, 0);
                    polyExt.AddVertexAt(1, new Point2d(x - 50, y + 105), 0, 0, 0);
                    polyExt.AddVertexAt(2, new Point2d(x - 20, y + 125), 0, 0, 0);
                    polyExt.AddVertexAt(3, new Point2d(x + 20, y + 125), 0, 0, 0);
                    polyExt.AddVertexAt(4, new Point2d(x + 50, y + 105), 0, 0, 0);
                    polyExt.AddVertexAt(5, new Point2d(x + 50, y - 125), 0, 0, 0);
                    polyExt.AddVertexAt(6, new Point2d(x - 50, y - 125), 0, 0, 0);

                    // добавляем полилинию в определение блока и в транзакцию
                    ms.AppendEntity(polyExt);
                    tr.AddNewlyCreatedDBObject(polyExt, true);

                    // создаем еще полилинию
                    Polyline polyIn = new Polyline();
                    polyIn.SetDatabaseDefaults();
                    polyIn.AddVertexAt(0, new Point2d(x - 5, y - 125), 0, 0, 0);
                    polyIn.AddVertexAt(1, new Point2d(x - 5, y + 125), 0, 0, 0);
                    polyIn.AddVertexAt(2, new Point2d(x + 5, y + 125), 0, 0, 0);
                    polyIn.AddVertexAt(3, new Point2d(x + 5, y - 125), 0, 0, 0);

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

                    // создаем окружность
                    Circle cir = new Circle();
                    cir.SetDatabaseDefaults();
                    cir.Center = new Point3d(x, y + 90, 0);
                    cir.Radius = 15;

                    // добавляем окружность в определение блока и в транзакцию
                    ms.AppendEntity(cir);
                    tr.AddNewlyCreatedDBObject(cir, true);

                    // создаем звездочки
                    Polyline starLeft= new Polyline();
                    starLeft.SetDatabaseDefaults();
                    starLeft.AddVertexAt(0, new Point2d(x - 27, y - 75 - 6), 0, 0, 0);
                    starLeft.AddVertexAt(1, new Point2d(x - 27 - 9, y - 75 - 12), 0, 0, 0);
                    starLeft.AddVertexAt(2, new Point2d(x - 27 - 7, y - 75 - 2), 0, 0, 0);
                    starLeft.AddVertexAt(3, new Point2d(x - 27 - 14, y - 75 + 6), 0, 0, 0);
                    starLeft.AddVertexAt(4, new Point2d(x - 27 - 5, y - 75 + 6), 0, 0, 0);
                    starLeft.AddVertexAt(5, new Point2d(x - 27, y - 75 + 15), 0, 0, 0);
                    starLeft.AddVertexAt(6, new Point2d(x - 27 + 5, y - 75 + 6), 0, 0, 0);
                    starLeft.AddVertexAt(7, new Point2d(x - 27 + 14, y - 75 + 6), 0, 0, 0);
                    starLeft.AddVertexAt(8, new Point2d(x - 27 + 7, y - 75 - 2), 0, 0, 0);
                    starLeft.AddVertexAt(9, new Point2d(x - 27 + 9, y - 75 - 12), 0, 0, 0);
                    starLeft.AddVertexAt(10, new Point2d(x - 27, y - 75 - 6), 0, 0, 0);

                    ms.AppendEntity(starLeft);
                    tr.AddNewlyCreatedDBObject(starLeft, true);

                    Polyline starRight = new Polyline();
                    starRight.SetDatabaseDefaults();
                    starRight.AddVertexAt(0, new Point2d(x + 27, y - 75 - 6), 0, 0, 0);
                    starRight.AddVertexAt(1, new Point2d(x + 27 - 9, y - 75 - 12), 0, 0, 0);
                    starRight.AddVertexAt(2, new Point2d(x + 27 - 7, y - 75 - 2), 0, 0, 0);
                    starRight.AddVertexAt(3, new Point2d(x + 27 - 14, y - 75 + 6), 0, 0, 0);
                    starRight.AddVertexAt(4, new Point2d(x + 27 - 5, y - 75 + 6), 0, 0, 0);
                    starRight.AddVertexAt(5, new Point2d(x + 27, y - 75 + 15), 0, 0, 0);
                    starRight.AddVertexAt(6, new Point2d(x + 27 + 5, y - 75 + 6), 0, 0, 0);
                    starRight.AddVertexAt(7, new Point2d(x + 27 + 14, y - 75 + 6), 0, 0, 0);
                    starRight.AddVertexAt(8, new Point2d(x + 27 + 7, y - 75 - 2), 0, 0, 0);
                    starRight.AddVertexAt(9, new Point2d(x + 27 + 9, y - 75 - 12), 0, 0, 0);
                    starRight.AddVertexAt(10, new Point2d(x + 27, y - 75 - 6), 0, 0, 0);

                    ms.AppendEntity(starRight);
                    tr.AddNewlyCreatedDBObject(starRight, true);

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

            // функция, рисующая блок в заданной точке чертежа
            public void drawBlock(double x, double y)
            {
                // получаем ссылки на документ и его БД
                Document doc = Application.DocumentManager.MdiActiveDocument;
                Database db = doc.Database;

                // поле документа "Editor" понадобится нам для вывода сообщений в окно сообщений AutoCAD
                Editor ed = doc.Editor;

                // имя создаваемого блока
                const string blockName = "ltBlock";
                const string auxBlockName = "starBlock";

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

                    //***
                    // ШАГ 0 - создаем новую запись вспомогательного блока в таблице блоков (или получаем ID существующей)
                    //***

                    ObjectId btrIdAux;
                    if (bt.Has(auxBlockName))
                    {
                        btrIdAux = bt[auxBlockName];
                    }
                    else
                    {
                        // создаем новое определение блока, задаем ему имя
                        BlockTableRecord btr = new BlockTableRecord();
                        btr.Name = auxBlockName;

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

                        // создаем полилинию
                        Polyline poly = new Polyline();
                        poly.SetDatabaseDefaults();
                        poly.AddVertexAt(0, new Point2d(0, -6), 0, 0, 0);
                        poly.AddVertexAt(1, new Point2d(-9, -12), 0, 0, 0);
                        poly.AddVertexAt(2, new Point2d(-7, -2), 0, 0, 0);
                        poly.AddVertexAt(3, new Point2d(-14, 6), 0, 0, 0);
                        poly.AddVertexAt(4, new Point2d(-5, 6), 0, 0, 0);
                        poly.AddVertexAt(5, new Point2d(0, 15), 0, 0, 0);
                        poly.AddVertexAt(6, new Point2d(5, 6), 0, 0, 0);
                        poly.AddVertexAt(7, new Point2d(14, 6), 0, 0, 0);
                        poly.AddVertexAt(8, new Point2d(7, -2), 0, 0, 0);
                        poly.AddVertexAt(9, new Point2d(9, -12), 0, 0, 0);
                        poly.AddVertexAt(10, new Point2d(0, -6), 0, 0, 0);

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

                    //***
                    // ШАГ 1 - создаем новую запись в таблице блоков (или получаем ID существующей)
                    //***

                    // проверяем, нет ли в таблице блока с таким именем
                    // если есть - используем его ID
                    ObjectId btrId;
                    if (bt.Has(blockName))
                    {
                        btrId = bt[blockName];
                    }
                    else
                    {
                        // создаем новое определение блока, задаем ему имя
                        BlockTableRecord btr = new BlockTableRecord();
                        btr.Name = blockName;

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

                        //***
                        // ШАГ 2 - добавляем к созданной записи необходимые геометрические примитивы
                        //***

                        // создаем полилинию
                        Polyline polyExt = new Polyline();
                        polyExt.SetDatabaseDefaults();
                        polyExt.AddVertexAt(0, new Point2d(-50, -125), 0, 0, 0);
                        polyExt.AddVertexAt(1, new Point2d(-50, 105), 0, 0, 0);
                        polyExt.AddVertexAt(2, new Point2d(-20, 125), 0, 0, 0);
                        polyExt.AddVertexAt(3, new Point2d(20, 125), 0, 0, 0);
                        polyExt.AddVertexAt(4, new Point2d(50, 105), 0, 0, 0);
                        polyExt.AddVertexAt(5, new Point2d(50, -125), 0, 0, 0);
                        polyExt.AddVertexAt(6, new Point2d(-50, -125), 0, 0, 0);

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

                        // создаем еще полилинию
                        Polyline polyIn = new Polyline();
                        polyIn.SetDatabaseDefaults();
                        polyIn.AddVertexAt(0, new Point2d(-5, -125), 0, 0, 0);
                        polyIn.AddVertexAt(1, new Point2d(-5, 125), 0, 0, 0);
                        polyIn.AddVertexAt(2, new Point2d(5, 125), 0, 0, 0);
                        polyIn.AddVertexAt(3, new Point2d(5, -125), 0, 0, 0);

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

                        // создаем окружность
                        Circle cir = new Circle();
                        cir.SetDatabaseDefaults();
                        cir.Center = new Point3d(0, 90, 0);
                        cir.Radius = 15;

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

                        // добавляем звездочки
                        BlockReference starLeft = new BlockReference(new Point3d(-27, -75, 0), btrIdAux);
                        btr.AppendEntity(starLeft);
                        tr.AddNewlyCreatedDBObject(starLeft, true);
                        BlockReference starRight = new BlockReference(new Point3d(27, -75, 0), btrIdAux);
                        btr.AppendEntity(starRight);
                        tr.AddNewlyCreatedDBObject(starRight, true);
                    }

                    //***
                    // ШАГ 3 - добавляем вхождение созданного блока на чертеж
                    //***

                    // открываем пространство модели на запись
                    BlockTableRecord ms = (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite);

                    // создаем новое вхождение блока, используя сохраненный ранее ID определения блока
                    BlockReference br = new BlockReference(new Point3d(x, y, 0), btrId);

                    // добавляем вхождение блока на пространство модели и в транзакцию
                    ms.AppendEntity(br);
                    tr.AddNewlyCreatedDBObject(br, true);

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

            // эта функция будет вызываться при выполнении в AutoCAD команды "HabrCommand_DrawFigures"
            [CommandMethod("HabrCommand_DrawFigures")]
            public void HabrCommand_DrawFigures()
            {
                for (int x = 0; x < 100; x++)
                {
                    for (int y = 0; y < 100; y++)
                    {
                        drawFigure(125 * x, 300 * y);
                    }
                }
            }

            // эта функция будет вызываться при выполнении в AutoCAD команды "HabrCommand_DrawBlocks"
            [CommandMethod("HabrCommand_DrawBlocks")]
            public void HabrCommand_DrawBlocks()
            {
                for (int x = 0; x < 100; x++)
                {
                    for (int y = 0; y < 100; y++)
                    {
                        drawBlock(125 * x, 300 * y);
                    }
                }
            }

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

            }

            public void Terminate()
            {

            }
        }
    }
}


Код весьма незамысловат: имеется две процедуры — drawFigure(double x, double y) и drawBlock(double x, double y). Первая отрисовывает в заданной точке набор фигур, в точности повторяющий наш блок, а вторая создает в заданной точке вхождение нашего блока.

Далее следуют две команды — HabrCommand_DrawFigures и HabrCommand_DrawBlocks. Первая отрисовывает на чертеже 10 000 наборов фигур, вторая — 10 000 вхождений блока.

Результаты работы
Все целиком:

Чуть ближе:

Еще ближе:

Десять тысяч наборов фигур:

Десять тысяч вхождений блока:



Запустим AutoCAD, выполним первую команду, сохраним чертеж. Затем закроем AutoCAD и повторим аналогичную процедуру для второй команды. В итоге получим два dwg-файла. Сравним их размеры.
Конец немного предсказуем:
Файл с наборами фигур:

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



Первая причина


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

Представим, что в вышеупомянутый Приказ №1500 внесли дополнение, жестко регламентирующее расположение пуговицы на погоне, и нам нужно перенести окружность на всех погонах несколько выше от текущего положения.

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

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

    // поле документа "Editor" понадобится нам для вывода сообщений в окно сообщений AutoCAD
    Editor ed = doc.Editor;

    // имя нашего блока
    const string blockName = "ltBlock";

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

        // проверяем, есть ли в таблице блок с таким именем
        // если нет - выводим сообщение и завершаем выполнение команды
        if (!bt.Has(blockName))
        {
            ed.WriteMessage("Block not found!");
            return;
        }
        else
        {
            // открываем определение блока на запись
            BlockTableRecord btr = tr.GetObject(bt[blockName], OpenMode.ForWrite) as BlockTableRecord;

            // перебираем по очереди ID всех графических объектов блока
            foreach (ObjectId id in btr)
            {
                // для каждого ID получаем сам объект, открываем его на чтение
                DBObject obj = tr.GetObject(id, OpenMode.ForRead);
                // если тип объекта - окружность (Circle), то открываем ее на запись и задаем ей новый центр
                if (obj.GetType() == typeof(Circle))
                {
                    obj.UpgradeOpen();
                    (obj as Circle).Center = new Point3d(0, 100, 0);
                }
            }

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


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

Маленькая деталь: AutoCAD действительно обновит все вхождения блоков, однако чтобы это увидеть, необходимо выполнить команду REGEN:

Результат:
ДО:

ПОСЛЕ:



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

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

NB:
NэBpaЩeHuЯ — одна из причин, по которым мне НЕ нравится программировать. С некоторых пор я работаю конторским пенсионером, чему искренне рад.

Краткий вывод (куда ж без него-то): использование блоков может сэкономить массу сил и времени, но применять их надо обоснованно, при наличии необходимости.

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

Спасибо за внимание!

This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.

Комментариев нет:

Отправить комментарий