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

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

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

Перед работой


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

Кратко изложу суть проблемы: поскольку после загрузки плагина «выгрузить» его невозможно, единственный способ запустить плагин, код которого был изменен, — это закрыть AutoCAD, затем вновь запустить его и повторно загрузить плагин. Эта пустяковая операция занимает от силы пару минут, однако уже после пятидесяти-ста повторений она начинает дико раздражать, я бы даже сказал — бесить.

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

Линия


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

Код плагина:
using System;
using System.IO;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.ApplicationServices;
using acad = Autodesk.AutoCAD.ApplicationServices.Application;

namespace HabrPlug_Primitives
{
    public class ClassMyAutoCADDLL
    {
        public class Commands : IExtensionApplication
        {
            // эта функция будет вызываться при выполнении в AutoCAD команды "HabrCommand"
            [CommandMethod("HabrCommand")]
            public void HabrCommand()
            {
                // получаем текущий документ и его БД
                Document acDoc = acad.DocumentManager.MdiActiveDocument;
                Database acCurDb = acDoc.Database;

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

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

                    // создаем линию между точками с указанными координатами
                    Line acLine = new Line(new Point3d(25, 25, 0), new Point3d(33, 33, 0));

                    // устанавливаем параметры созданного объекта равными параметрам по умолчанию
                    acLine.SetDatabaseDefaults();

                    // добавляем созданный объект в пространство модели
                    acBlkTblRec.AppendEntity(acLine);

                    // также добавляем созданный объект в транзакцию
                    acTrans.AddNewlyCreatedDBObject(acLine, true);

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

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

            }

            public void Terminate()
            {

            }
        }
    }
}

При создании проекта, конечно, не забываем про всякие мелочи…)
Во-первых, необходимо указать требуемую версию .NET Framework (в моем случае это .NET Framework 3.5).

Во-вторых, нужно добавить ссылки на библиотеки AcDbMgd.dll и AcMgd.dll и в свойствах этих ссылок запретить копирование библиотек в целевую папку проекта (установить параметр CopyLocal в False).

Сам код крайне прост, подробно расписывать здесь особо нечего.

Во-первых, мы начинаем транзакцию (что это и зачем нужно, рассматривалось в прошлой статье).

Во-вторых, мы открываем пространство модели (Model Space) — это, собственно, и есть наш чертеж. Более четкое определение Model Space можно найти тут и там. Поскольку мы будем добавлять на чертеж новый объект, нужно запросить доступ «Чтение и запись» (OpenMode.ForWrite).

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

В-четвертых, мы должны задать для объекта все свойства. В данном примере мы устанавливаем всем свойствам заданные по умолчанию значения, вызвав для этого функцию SetDatabaseDefaults(). Скупое мужское описание этой функции можно найти в ObjectARX Reference.
NB:
на практике для простых случаев вызовом этой функции можно пренебречь, однако есть мнение, что лучше так не делать.

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

В-шестых, необходимо добавить созданный объект к транзакции, в рамках которой мы работаем. Для этого используется функция AddNewlyCreatedDBObject(), которая уже разбиралась в предыдущей статье.

Наконец, после выполнения всех нужных нам операций мы должны зафиксировать транзакцию, вызвав метод Commit().

После компиляции проекта запускаем AutoCAD, загружаем наш плагин и выполняем команду HabrCommand. Результат прост — но зато, надеюсь, понятен и предсказуем.



Если после запуска команды линия на экране так и не появилась — не спешите расстраиваться. Вместо этого убедитесь, что центр координат — точка (0;0) — находится в пределах видимой рабочей области. Кроме того, есть смысл поменять масштаб, покрутив колесом мыши.

Для начинающих пользователей AutoCAD может стать сюрпризом то, что масштабирование чертежа происходит в определенных пределах. Проще говоря, с ходу перейти от масштаба 1000% к масштабу 1% невозможно — после пары прокруток колеса мыши масштаб «упрется» в невидимый ограничитель. Чтобы продолжить масштабирование чертежа, необходимо выполнить команду REGEN в командной строке AutoCAD.
NB:
из личного опыта: если программист вконец упоролся, и ему уже непонятно, какие объекты создаются и создаются ли вообще, то можно открыть чистый чертеж, выполнить на нем интересующий кусок кода и выделить все объекты на чертеже, нажав комбинацию клавиш «Ctrl+A». После этого найти созданные объекты, как правило, не составляет никакого труда.

Давайте освежимся примером.
Картина ДО:

Ну и где здесь линия?)
Картина ПОСЛЕ:

Притворившуюся пылинкой на мониторе линию с головой выдают синий квадратик и появившееся окно свойств объекта.

Полилиния


Опять же, пример есть в документации (перевод).

Код команды:
[CommandMethod("HabrCommand")]
public void HabrCommand()
{
    // получаем текущий документ и его БД
    Document acDoc = acad.DocumentManager.MdiActiveDocument;
    Database acCurDb = acDoc.Database;

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

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

        // создаем полилинию
        Polyline acPolyline = new Polyline();

        // устанавливаем параметры созданного объекта равными параметрам по умолчанию
        acPolyline.SetDatabaseDefaults();

        // добавляем к полилинии вершины
        acPolyline.AddVertexAt(0, new Point2d(2, 4), 0, 0, 0);
        acPolyline.AddVertexAt(1, new Point2d(4, 8), 0, 0, 0);
        acPolyline.AddVertexAt(2, new Point2d(6, 6), 0, 0, 0);
        acPolyline.AddVertexAt(3, new Point2d(8, 11), 0, 0, 0);

        // добавляем созданный объект в пространство модели
        acBlkTblRec.AppendEntity(acPolyline);

        // также добавляем созданный объект в транзакцию
        acTrans.AddNewlyCreatedDBObject(acPolyline, true);

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

Результат:



Пояснять здесь особенно нечего. При желании с помощью добавления новых точек полилинию можно превратить в троллейбус довольно сложную геометрическую фигуру — например, звездочку…
...или все-таки троллейбус?
[CommandMethod("HabrCommand")]
public void HabrCommand()
{
    // получаем текущий документ и его БД
    Document acDoc = acad.DocumentManager.MdiActiveDocument;
    Database acCurDb = acDoc.Database;

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

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

        // создаем троллейбус
        Polyline acPolyline = new Polyline();

        // устанавливаем параметры созданного троллейбуса равными параметрам по умолчанию
        acPolyline.SetDatabaseDefaults();

        // добавляем к троллейбусу вершины
        acPolyline.AddVertexAt(0, new Point2d(100, 100), 0, 0, 0);
        acPolyline.AddVertexAt(1, new Point2d(100, 650), 0, 0, 0);
        acPolyline.AddVertexAt(2, new Point2d(1050, 650), 0, 0, 0);
        acPolyline.AddVertexAt(3, new Point2d(650, 1150), 0, 0, 0);
        acPolyline.AddVertexAt(4, new Point2d(1050, 650), 0, 0, 0);
        acPolyline.AddVertexAt(5, new Point2d(2050, 650), 0, 0, 0);
        acPolyline.AddVertexAt(6, new Point2d(2050, 100), 0, 0, 0);
        acPolyline.AddVertexAt(7, new Point2d(1950, 100), 0, 0, 0);
        acPolyline.AddVertexAt(8, new Point2d(1950, 400), 0, 0, 0);
        acPolyline.AddVertexAt(9, new Point2d(1800, 400), 0, 0, 0);
        acPolyline.AddVertexAt(10, new Point2d(1800, 100), 0, 0, 0);
        acPolyline.AddVertexAt(11, new Point2d(1950, 100), 0, 0, 0);
        acPolyline.AddVertexAt(12, new Point2d(1700, 100), 0, 0, 0);
        acPolyline.AddVertexAt(13, new Point2d(1660, 170), 0, 0, 0);
        acPolyline.AddVertexAt(14, new Point2d(1600, 225), 0, 0, 0);
        acPolyline.AddVertexAt(15, new Point2d(1500, 225), 0, 0, 0);
        acPolyline.AddVertexAt(16, new Point2d(1440, 170), 0, 0, 0);
        acPolyline.AddVertexAt(17, new Point2d(1400, 100), 0, 0, 0);
        acPolyline.AddVertexAt(18, new Point2d(850, 100), 0, 0, 0);
        acPolyline.AddVertexAt(19, new Point2d(1200, 100), 0, 0, 0);
        acPolyline.AddVertexAt(20, new Point2d(1200, 400), 0, 0, 0);
        acPolyline.AddVertexAt(21, new Point2d(1000, 400), 0, 0, 0);
        acPolyline.AddVertexAt(22, new Point2d(1000, 100), 0, 0, 0);
        acPolyline.AddVertexAt(23, new Point2d(1100, 100), 0, 0, 0);
        acPolyline.AddVertexAt(24, new Point2d(1100, 400), 0, 0, 0);
        acPolyline.AddVertexAt(25, new Point2d(1100, 100), 0, 0, 0);
        acPolyline.AddVertexAt(26, new Point2d(850, 100), 0, 0, 0);
        acPolyline.AddVertexAt(27, new Point2d(810, 170), 0, 0, 0);
        acPolyline.AddVertexAt(28, new Point2d(750, 225), 0, 0, 0);
        acPolyline.AddVertexAt(29, new Point2d(650, 225), 0, 0, 0);
        acPolyline.AddVertexAt(30, new Point2d(590, 170), 0, 0, 0);
        acPolyline.AddVertexAt(31, new Point2d(550, 100), 0, 0, 0);
        acPolyline.AddVertexAt(32, new Point2d(100, 100), 0, 0, 0);
        acPolyline.AddVertexAt(33, new Point2d(450, 100), 0, 0, 0);
        acPolyline.AddVertexAt(34, new Point2d(450, 400), 0, 0, 0);
        acPolyline.AddVertexAt(35, new Point2d(250, 400), 0, 0, 0);
        acPolyline.AddVertexAt(36, new Point2d(250, 100), 0, 0, 0);
        acPolyline.AddVertexAt(37, new Point2d(350, 100), 0, 0, 0);
        acPolyline.AddVertexAt(38, new Point2d(350, 400), 0, 0, 0);

        // добавляем созданный троллейбус в пространство модели
        acBlkTblRec.AppendEntity(acPolyline);

        // также добавляем созданный троллейбус в транзакцию
        acTrans.AddNewlyCreatedDBObject(acPolyline, true);

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



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

Окружность


Рассмотрим пример добавления на чертеж окружности, прилежно перепечатанный из документации (ссылки были выше).
Код команды:
[CommandMethod("HabrCommand")]
public void HabrCommand()
{
    // получаем текущий документ и его БД
    Document acDoc = acad.DocumentManager.MdiActiveDocument;
    Database acCurDb = acDoc.Database;

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

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

        // создаем окружность
        Circle acCircle = new Circle();

        // устанавливаем параметры созданного объекта
        acCircle.SetDatabaseDefaults();
        acCircle.Center = new Point3d(2.5, 3.14, 0);
        acCircle.Radius = 4.25;

        // добавляем созданный объект в пространство модели
        acBlkTblRec.AppendEntity(acCircle);

        // также добавляем созданный объект в транзакцию
        acTrans.AddNewlyCreatedDBObject(acCircle, true);

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

Никаких сложностей с добавлением на чертеж окружности быть не должно: все, что нужно, — это установить центр и радиус.

Результат:



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

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



Для восстановления первозданной формы окружности можно выполнить в командной строке AutoCAD команду REGEN:



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

Эллипс


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

Код команды:
[CommandMethod("HabrCommand")]
public void HabrCommand()
{
    // получаем текущий документ и его БД
    Document acDoc = acad.DocumentManager.MdiActiveDocument;
    Database acCurDb = acDoc.Database;

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

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

        // определяем параметры эллипса
        Point3d center = Point3d.Origin;
        Vector3d normal = Vector3d.ZAxis;
        Vector3d majorAxis = 100 * Vector3d.XAxis;
        double radiusRatio = 0.5;
        double startAng = 0.0;
        double endAng = Math.PI * 2;
        // создаем эллипс, сразу указывая его параметры
        Ellipse acEllipse = new Ellipse(center, normal, majorAxis, radiusRatio, startAng, endAng);

        // добавляем созданный объект в пространство модели
        acBlkTblRec.AppendEntity(acEllipse);

        // также добавляем созданный объект в транзакцию
        acTrans.AddNewlyCreatedDBObject(acEllipse, true);

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

Результат (синяя линия добавлена для пояснения):



При создании эллипса мы задали ему следующие параметры:
  1. Центр (center) эллипса — с этим все понятно.
  2. Нормаль (normal) — вектор, перпендикулярный плоскости эллипса. Поскольку мы добавляем объект на плоскость XY, нормалью будет ось Z.
  3. Большая ось (majorAxis) — в понятиях AutoCAD это вектор с длиной, равной половине «ширины» эллипса (как синий отрезок на рисунке). В обычной математике это называется «большая полуось». В нашем примере мы задали вектор длиной 100, по направлению совпадающий с осью абсцисс.
  4. Радиус (radiusRatio) — определяет, насколько вытянут эллипс. В данном примере максимальная координата эллипса по оси Y составит 100 * 0.5 = 50.
  5. Начальный и конечный углы — определяют начальный и конечный углы отрисовки кривых. Принцип неплохо поясняет картинка ниже, которая взята отсюда.


Чтобы нарисовать всю фигуру, нужно «пробежать» полный круг, то есть 360 градусов, или 2pi (как в полярной системе координат). Если укажем меньше, получим только часть фигуры.

Еще один пример эллипса.
Попробуем задать эллипсу новые параметры:
Point3d center = Point3d.Origin;
Vector3d normal = Vector3d.ZAxis;
Vector3d majorAxis = 100 * Vector3d.XAxis + 100 * Vector3d.YAxis;
double radiusRatio = 0.5;
double startAng = Math.PI * 0.1;
double endAng = Math.PI * 1.3;
Ellipse acEllipse = new Ellipse(center, normal, majorAxis, radiusRatio, startAng, endAng);

Получим следующее (линия проведена для удобства ориентирования):



Поскольку startAng больше нуля, верхняя часть эллипса начинается не от оси, а чуть дальше. Поскольку endAng сильно не дотягивает до 2pi, нижняя часть эллипса заканчивается значительно раньше положенного.

Наклон обеспечивается за счет задания вектора оси (majorAxis = 100 * Vector3d.XAxis + 100 * Vector3d.YAxis). Поскольку X-компонента этого вектора по модулю равна Y-компоненте, получаем наклон 45 градусов.

Длину полуоси можно посчитать по теореме Пифагора: это корень квадратный из суммы квадратов координат X и Y. Вычислив корень квадратный из 1002 + 1002 (итого 20000), получим около 141. Общая длина эллипса, соответственно, составит примерно 282.

Круг


Для создания круга можно использовать штриховку — Hatch (документация, перевод).

Порядок действий будет таким:
  1. Создаем границу штриховки. В качестве границы может выступать любая замкнутая кривая — в нашем случае это будет окружность.
  2. Задаем для границы штриховки необходимые свойства.
  3. Добавляем границу штриховки на чертеж.
  4. Создаем массив объектов, которые будут являться границами штриховки, и добавляем в него нашу границу.
  5. Создаем штриховку.
  6. Добавляем штриховку на чертеж.
  7. Задаем для штриховки необходимые свойства.

Шагов поприбавилось, но они все несложные.

Код команды:
[CommandMethod("HabrCommand")]
public void HabrCommand()
{
    // получаем текущий документ и его БД
    Document acDoc = acad.DocumentManager.MdiActiveDocument;
    Database acCurDb = acDoc.Database;

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

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

        // 1) создаем окружность - границу штриховки
        Circle acCircle = new Circle();

        // 2) устанавливаем параметры границы штриховки
        acCircle.SetDatabaseDefaults();
        acCircle.Center = new Point3d(2.5, 3.14, 0);
        acCircle.Radius = 4.25; 

        // 3) добавляем созданную границу штриховки в пространство модели и в транзакцию
        acBlkTblRec.AppendEntity(acCircle);
        acTrans.AddNewlyCreatedDBObject(acCircle, true);

        // 4) добавляем созданную границу штриховки в массив объектов-границ штриховки
        ObjectIdCollection acObjIdColl = new ObjectIdCollection();
        acObjIdColl.Add(acCircle.ObjectId);

        // 5) создаем штриховку
        Hatch acHatch = new Hatch();

        // 6) добавляем созданную штриховку в пространство модели и в транзакцию
        acBlkTblRec.AppendEntity(acHatch);
        acTrans.AddNewlyCreatedDBObject(acHatch, true);

        // 7) устанавливаем параметры штриховки
        acHatch.SetDatabaseDefaults();
        acHatch.SetHatchPattern(HatchPatternType.PreDefined, "SOLID");
        acHatch.Color = Autodesk.AutoCAD.Colors.Color.FromRgb(0, 200, 0);
        acHatch.Associative = true;
        acHatch.AppendLoop(HatchLoopTypes.Outermost, acObjIdColl);
        acHatch.EvaluateHatch(true);

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

Результат:



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

Вначале мы вызываем функцию setDatabaseDefaults(), которая устанавливает параметры штриховки равными параметрам по умолчанию. Назначение этой функции было рассмотрено в первом разделе.

Затем мы указываем тип штриховки с помощью функции SetHatchPattern(). В данном случае мы хотим нарисовать обычный круг, поэтому выбираем тип SOLID — «сплошная».
NB:
можно задать и другой тип штриховки, помимо сплошной. «Из коробки» AutoCAD предоставляет несколько десятков штриховок — чтобы их узнать, достаточно вызвать свойства объекта Hatch:



Вместо SOLID мы могли бы указать что-то другое. Например, указав HOUND, мы получили бы такой результат:



Помимо стандартных штриховок (HatchPatternType.PreDefined) можно использовать и сторонние, которые предварительно были подключены к AutoCAD. Вот ссылка на пример.

После задания типа штриховки я указал ее цвет (свойство Color) с помощью функции FromRgb(byte ref, byte blue, byte green).

Затем следует свойство Associative — оно определяет, будет ли штриховка менять размер при изменении размера ее границ.
Иллюстрация
Вот что произойдет с нашим примером (Associative = true) при изменении границ штриховки:



Новый радиус круга — 5, штриховка растянулась под новый радиус.

А вот пример с Associative = false:




В разделе «Define the Hatch Boundaries (.NET)» документации (перевод) сказано, что свойство Associative должно быть установлено после добавления шриховки в таблицу блоков (то есть на пространство модели) и до вызова метода AppendLoop.

Метод AppendLoop позволяет указать внешнюю границу штриховки и принимает на вход два параметра: тип границы и массив объектов-границ штриховки. В качестве первого параметра мы указали HatchLoopTypes.Outermost, тем самым обозначив, что хотим задать внешнюю границу штриховки. В качестве второго мы указали ранее созданный массив объектов-границ штриховки.
NB:
при необходимости можно задать несколько внутренних границ в дополнение к внешней и определить поведение штриховки при их пересечении. Подробности есть здесь; я же ограничусь простеньким примером такой фигуры.



Код команды:
[CommandMethod("HabrCommand")]
public void HabrCommand()
{
    // получаем текущий документ и его БД
    Document acDoc = acad.DocumentManager.MdiActiveDocument;
    Database acCurDb = acDoc.Database;

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

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

        // определяем внешнюю границу
        Polyline acPolyline = new Polyline();
        acPolyline.SetDatabaseDefaults();
        acPolyline.AddVertexAt(0, new Point2d(50, 50), 0, 0, 0);
        acPolyline.AddVertexAt(1, new Point2d(150, 285), 0, 0, 0);
        acPolyline.AddVertexAt(2, new Point2d(250, 50), 0, 0, 0);
        acPolyline.AddVertexAt(3, new Point2d(25, 200), 0, 0, 0);
        acPolyline.AddVertexAt(4, new Point2d(275, 200), 0, 0, 0);
        acPolyline.AddVertexAt(5, new Point2d(50, 50), 0, 0, 0);
        acBlkTblRec.AppendEntity(acPolyline);
        acTrans.AddNewlyCreatedDBObject(acPolyline, true);

        // определяем массив элементов-внешних границ
        ObjectIdCollection acObjIdColl_OUTER = new ObjectIdCollection();
        acObjIdColl_OUTER.Add(acPolyline.ObjectId);

        // определяем внутреннюю границу
        Circle acCircleOut = new Circle();
        acCircleOut.SetDatabaseDefaults();
        acCircleOut.Center = new Point3d(150, 165, 0);
        acCircleOut.Radius = 25;
        acBlkTblRec.AppendEntity(acCircleOut);
        acTrans.AddNewlyCreatedDBObject(acCircleOut, true);

        // определяем массив элементов-внутренних границ
        ObjectIdCollection acObjIdColl_INNER = new ObjectIdCollection();
        acObjIdColl_INNER.Add(acCircleOut.ObjectId);

        // определяем штриховку
        Hatch acHatch = new Hatch();
        acBlkTblRec.AppendEntity(acHatch);
        acTrans.AddNewlyCreatedDBObject(acHatch, true);

        // определяем свойства штриховки
        acHatch.SetDatabaseDefaults();
        acHatch.SetHatchPattern(HatchPatternType.PreDefined, "SOLID");
        acHatch.Color = Autodesk.AutoCAD.Colors.Color.FromRgb(200, 0, 0);
        acHatch.Associative = false;
        acHatch.HatchStyle = HatchStyle.Normal;
        // определяем внешние границы штриховки
        acHatch.AppendLoop(HatchLoopTypes.Outermost, acObjIdColl_OUTER);
        // определяем внутренние границы штриховки
        acHatch.AppendLoop(HatchLoopTypes.Default, acObjIdColl_INNER);
        acHatch.EvaluateHatch(true);

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

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

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

Напоследок - еще одно примечание про круг.
Иногда даже программисту приходится работать с чертежом прямо в AutoCAD (в одной из следующих статей я хотел рассказать о динамических блоках — там это бывает особенно актуально). В этом случае для создания круга проще всего воспользоваться командой DONUT в консоли AutoCAD. Ее синтаксис:

DONUT <внутренний радиус> <внешний радиус> <координаты>

Вот две команды DONUT и результат их выполнения:

DONUT 0 25 50,50
DONUT 25 75 200,50



Кстати, какой круг больше — черный или белый?)

P. S.


Статья получилась довольно простой — зато, надеюсь, все понятно и доступно. AutoCAD .NET API позволяет вычерчивать и более сложные фигуры — например, дуги и сплайны — но мне с ними работать не доводилось. Информацию об этом, если придется, можно поискать в документации (перевод).

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

Спасибо за внимание!
Любые отзывы, замечания и пожелания приветствуются в комментариях.)

Комментарии (6)


  1. Kichnap
    02.05.2015 23:25

    Отличная статья.
    Большое спасибо за ссылку на перевод примеров.
    Как опытный пользователь AutoCAD скажу, если дважды щелкнуть на колесико мыши, то масштаб чертежа изменится таким образом, чтобы были видны все элементы чертежа.
    Хотелось бы узнать про то, как искать на чертеже существующие примитивы, например, на определенном слое или цвете.
    Как обычно буду ждать следующей статьи.


    1. lostpassword Автор
      02.05.2015 23:59

      Сайт с переводом основал и поддерживает Андрей Бушман ( Hwd ), я просто привел ссылки.)

      Про поиск элементов я тогда напишу как-нибудь. Насколько я помню, когда я озадачился этой проблемой, мне помог вот этот пост несравненного Kean Walmsley.
      Задача поиска всех объектов на слое решается элементарно. А вот с поиском всех элементов заданного цвета дела обстоят посложнее — не помню, доводилось ли мне такое делать. Поищу в коде.

      Спасибо за отзыв!


    1. Zanoza
      05.05.2015 08:04

      Элементарно Виктор.

          List<TypedValue> allBlocks = new List<TypedValue>();
          allBlocks.Add(new TypedValue((short)DxfCode.Start, "INSERT"));
          allBlocks.Add(new TypedValue((short)DxfCode.LayoutName, "Model"));
      
          allBlocks.Add(new TypedValue(-4, "<AND"));
          allBlocks.Add(new TypedValue((short)DxfCode.ColorName, "какой-нибудь цвет"));
          allBlocks.Add(new TypedValue(-4, "AND>"));
      
          allBlocks.Add(new TypedValue(-4, "<OR"));
          allBlocks.Add(new TypedValue((short)DxfCode.ColorName, "какой-нибудь цвет2"));
          allBlocks.Add(new TypedValue(-4, "OR>"));
      
          SelectionFilter allBlocksFilter = new SelectionFilter(allBlocks.ToArray());
          PromptSelectionResult prResult= Acad.Document.Editor.SelectAll(allBlocksFilter);
          if (prResult.Status == PromptStatus.OK)
          {
          }
      

      Это так навскидку, смотрите SelectionFilter.


  1. Zanoza
    05.05.2015 07:49

    Если вы отрываете объект ForWrite то его нужно закрыть Close. Для наглядности лучше использовать, что-то что возвращает PromptStatus, не забывая про CurrentUserCoordinateSystem, а лучше пример с Jig.


    1. lostpassword Автор
      05.05.2015 11:48

      Спасибо за комментарий!
      Честно говоря, настолько далеко мои знания не распространяются. PromptStatus я использовал нечасто (в основном когда работал с вводом пользователя), а с CurrentUserCoordinateSystem и Jig не работал никогда.)
      По поводу закрытия объектов: дело в том, что я не использую Open() напрямую, а открываю элемент из транзакции с помощью метода GetObject(), и дальше она сама заботится о закрытии объектов. Закрывать открытые в транзакции объекты методом Close() нельзя.


      1. Zanoza
        07.05.2015 07:41

        Действительно, ошибся.