Первый опыт написания плагинов для Autocad на C#

Источник: habrahabr
Namolem

Я начинающий разработчик, "школьный" уровень знания С++, небольшой (2 года) опыт программирования на С#, нулевой опыт работы в автокаде
Недавно попросили изменить LISP программки автокада, предназначенные для создания/изменения межевых планов и подготовки соответствующих документов MS Word / XML - пофиксить баги и добавить новый функционал.
Поскольку читабельность программ на Лиспе (по крайней мере для меня) оставляет желать лучшего, я решил переписать это на более понятный язык.
Т.к. мне не нужны были миллисекунды прироста скорости, я пропустил С++ и остановился на C#

Пишу эту статью, чтобы:

1. Разложить у себя в голове по полочкам, что я узнал об Autocad.
2. Помочь тем, кто так же как я продирается через очень малое количество документации.
3. Получить в комментариях информацию вида "это ты делаешь неправильно, проще и лучше будет сделать так..."

Начало работы. Создание плагина.

Создаем проект C# по шаблону ClassLibrary
Добавляем ссылки на managed библиотеки API Autocad'a, которые лежат в папке программы.
В моем случае это:
C:\Program Files\AutoCAD 2007\acdbmgd.dll
C:\Program Files\AutoCAD 2007\acmgd.dll

Создаем класс, который что-то делает:

Наследование от IExtensionApplication необязательно, Autocad автоматически подцепит все public классы в библиотеки, но, как мне сказали, так будет быстрее. Плюс можно контролировать Initialize/Terminate плагина.

Компилируем, запускаем автокад, загружаем плагин командой netload (открывается окно выбора managed dll)
Теперь при вводе команды hello мы будем получать ожидаемый ответ.

Структура Autocad приложения:

То, что мы видим на экране, графические объекты, унаследованные от  Entity  
Кроме видимых, есть невидимые информационные объекты -  Слои ,  Типы линий ,  Стили размерности (Dimension styles) ,  Стили таблиц  и т.д.
Все это хранится в  Database Table Records , в хранилищах вида  TYPETable  и классах вида  TYPETableRecord .

Идентификаторы объектов

  • ObjectId, известный также как EName (entity name). Число, создаваемое при открытии рисунка. 
    Использование - идентификация объекта в базе в течении одной сессии, объект запрашивается из базы по его ObjectId
    Может меняться между разными открытиями, лучше не использовать для сохранения ссылок на объекты
  • Handle - число, неизменное между разными открытиями документа, удобно использовать для сохранения связей между объектами при сохранении файла.

Подробнее:  Handles are persistent between AutoCAD sessions, so they are the best way of accessing objects if you need to export drawing information to an external file which might later need to be used to update the drawing. The ObjectId of an object in a database exists only while the database is loaded into memory. Once the database is closed, the Object Ids assigned to an object no longer exist and maybe different the next time the database is opened.

Работа с базой данных

Обычно работа с базой идет с помощью транзакции. Объекты запрашиваются из базы данных, изменяются и commit'ом транзакции сохраняются обратно
Во время транзакции объект запрашивается из БД в одном из 3х режимов ForRead, ForWrite, ForNotify.
Назначение первых двух очевидно, третий как-то используется для механизма событий, с чем я пока не пересекался
В режиме ForWrite автокадом создаются дополнительные объекты, позволяющие отменить изменения в транзации.
Если нужно изменить объект, открытый как "ForRead", вызывается его метод UpgradeOpen(). 
Если вызвать этот метод на объекте, уже открытом в режиме изменения, метод выдаст исключение.

Пример получения объекта Polyline по его ObjectId

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

Иллюстрация

UPDATE: Как мне подсказали в комментариях, предпочтительнее всегда вызывать transaction.Commit(), кроме случаев, когда нужно отменить транзакцию. Если транзакция не коммитится, автоматически вызывается transaction.Abort(), влекущий за собой дополнительные расходы.

Словари

Словари я использовал для сохранения своих данных в DWG, чтобы не создавать лишних файлов
Я столкнулся с двумя видами словарей в рисунке - NamedObjectDictionary и ExtensionDictionary
Данные в словарях хранятся в записях (Record), которые в свою очередь хранят типизированные значения.
Адресуются данные по текстовым ключам.

NamedObjectDictionary - глобальный словарь рисунка. Создается автоматически при создании документа.
Его я использовал для хранения ссылок на главные используемые мной объекты.

ExtensionDictionary - словарь, свой для каждого объекта, его нужно создавать вручную.
Проверить его существование можно сравнив поле entity.ExtensionDictionary c ObjectId.Null

Пример записи и получения строкового значения из ExtensionDictionary

Работа с глобальным словарем почти такая же, только объект DBDictionary получается так:
var dictionary = (DBDictionary) transaction.GetObject(db.NamedObjectsDictionaryId, OpenMode.ForWrite);

С чем я еще столкнулся

1. Автозагрузка плагина

2. Debug

3. Отправка команды в Editor

4. Выбор объектов пользователем

Получение пути к папке документа

И еще полезные отрывки кода:

Изменение координат полилинии (метод расширения)

Получение координат полилинии (метод расширения):

Полезные ссылки

1. AutoCAD .NET Developer's Guide
Английский мини-референс, рассказывается суть устройства Autocad с примерами кода на VB.NET, C#.NET, VBA
2. Through the interface
Блог Kean'a Walmsley, очень много Howto. Встречаются примеры на C#, C#+COM, VB.NET, C++. У меня там искать не получается, но половина моих запросов в гугле "как сделать так.." вела на этот сайт
3. caduser.ru, подфорум ".NET"
Общение с русскоговорящими людьми, хорошо разбирающимися в теме. Много раз помогали разобраться в трудных местах


Страница сайта http://www.interface.ru
Оригинал находится по адресу http://www.interface.ru/home.asp?artId=33876