Одним из главных недостатков традиционного .NET API в .dwg совместимых САПР является невозможность создания пользовательских примитивов (Custom Entities) на .NET. Пользовательские примитивы создаются на С++, для их использования в .NET необходимо создать управляемые обёртки на C++/CLI.
Технология MultiCAD .NET позволяет создавать пользовательские примитивы, не выходя за рамки управляемого кода. Помимо отсутствия промежуточных объектов на C++, в MultiCAD .NET максимально используются стандартные для .NET механизмы, как следствие нет необходимости во многих привычных для САПР программистов операциях: не нужно вручную описывать сериализацию, свойства в инспектор можно вывести без создания COM объекта и т.п.
В качестве демонстрации MultiCAD .NET мы рассмотрим пример приложения CustomObjects, содержащийся в комплекте поставки SDK. Этот пример создает пользовательский примитив, который представляет собой прямоугольную рамку с находящимся внутри текстом:
Чертежи, содержащие наш тестовый примитив, могут быть открыты в любой .dwg совместимой САПР. Для изменения примитива необходимо загрузить сборку, содержащую код примитива, причём во все поддерживаемые САПР платформы загружается одна и та же сборка без перекомпиляции. Технология является родной для nanoCAD, для загрузки модуля в AutoCAD требуется модуль расширения (Object Enabler). Как это работает смотрите под катом.
Класс пользовательского примитива
Для создания нового типа примитива необходимо написать класс, наследованный от McCustomBase - базового класса для всех пользовательских примитивов. Кроме этого, для объявленного класса необходимо использовать два атрибута:
- атрибут [CustomEntity] с указанием типа класса, его GUID, имени, которое будет использоваться для всех таких объектов в базе данных чертежа и локального имени,
- атрибут [Serializable], для того, чтобы воспользоваться стандартным механизмом сериализации в .NET Framework.
[CustomEntity(typeof(TextInBox), "1C925FA1-842B-49CD-924F-4ABF9717DB62", "TextInBox", "TextInBox Sample Entity")] [Serializable] public class TextInBox : McCustomBase { // First and second vertices of the box private Point3d _pnt1 = new Point3d(50, 50, 0); private Point3d _pnt2 = new Point3d(150, 100, 0); // Text inside the box private String _text = "Text field"; }
Теперь переопределим методы базового класса McCustomBase, которые будут использоваться для отображения геометрии, вставки объекта в чертеж, выбора и трансформации объекта.
Отображение геометрии
Для отображения объекта используется метод OnDraw(). В качестве параметра этого метода выступает объект класса GeometryBuilder, который, собственно, и будет использоваться для отрисовки пользовательского примитива.
public override void OnDraw(GeometryBuilder dc) { dc.Clear(); // Set the color to ByObject value dc.Color = McDbEntity.ByObject; // Draw box with choosen coordinates dc.DrawPolyline(new Point3d[] { _pnt1, new Point3d(_pnt1.X, _pnt2.Y, 0), _pnt2, new Point3d(_pnt2.X, _pnt1.Y, 0), _pnt1}); // Set text height dc.TextHeight = 2.5 * DbEntity.Scale; // Set text color dc.Color = Color.Blue; // Draw text at the box center dc.DrawMText(new Point3d((_pnt2.X + _pnt1.X) / 2.0, (_pnt2.Y + _pnt1.Y) / 2.0, 0), Vector3d.XAxis, Text, HorizTextAlign.Center, VertTextAlign.Center); }
Добавление объекта в чертеж, интерактивный ввод координат
Для добавления пользовательского объекта в чертеж используется метод PlaceObject(), который в нашем случае, кроме собственно операции добавления объекта в базу, будет использоваться и для интерактивного ввода координат объекта. За интерактивный ввод в MultiCAD .NET отвечает класс InputJig, содержащий необходимую нам функциональность:
public InputResult GetPoint(string promt)
- получает точку на чертеже, выбранную пользователем, с возможностью вывода подсказки;
public void ExcludeObject(McObjectId ObjectId)
- исключает указанный объект из привязки при указании точки на чертеже. В нашем случае мы исключим наш объект из привязки, чтобы избежать привязки к самому себе.
public EventHandler MouseMove
- обработчик события движения мышкой. Будем его использовать для интерактивной перерисовки объекта при передвижении мыши.
Реализация метода PlaceObject()
будет выглядеть следующим образом:
public override hresult PlaceObject(PlaceFlags lInsertType) { InputJig jig = new InputJig(); // Get the first box point from the jig InputResult res = jig.GetPoint("Select first point:"); if (res.Result != InputResult.ResultCode.Normal) return hresult.e_Fail; _pnt1 = res.Point; // Add the object to the database DbEntity.AddToCurrentDocument(); // Exclude the object from snap points jig.ExcludeObject(ID); // Monitoring mouse moving and interactive entity redrawing jig.MouseMove = (s, a) => {TryModify(); _pnt2 = a.Point; DbEntity.Update(); }; // Get the second box point from the jig res = jig.GetPoint("Select second point:"); if (res.Result != InputResult.ResultCode.Normal) { DbEntity.Erase(); return hresult.e_Fail; } _pnt2 = res.Point; return hresult.s_Ok; }
Редактирование и трансформация объекта
Добавим возможность модифицирования объекта и редактирования текстовой строки. Для этого потребуется переопределить следующие методы, содержащиеся в базовом классе McCustomBase:
public virtual List OnGetGripPoints()
- получает список ручек для объекта;
public virtual void OnMoveGripPoints(List indexes, Vector3d offset, bool isStretch)
- обработчик перемещения ручек;
public virtual void OnTransform(Matrix3d tfm)
- определяет как должен трансформироваться объект;
public virtual hresult OnEdit(Point3d pnt, EditFlags lInsertType)
- определяет процедуру редактирования объекта;
Ручки представляют собой специальные точки, отмеченные маркером, которые используются для трансформации объекта. Выведем ручки в угловых точках рамки, заданных пользователем:
public override List OnGetGripPoints() { List arr = new List(); arr.Add(_pnt1); arr.Add(_pnt2); return arr; }
Теперь, после выбора объекта на чертеже, в заданных точках будут отображены ручки:
Добавим возможность перемещения определяющих угловых точек с помощью перетаскивания ручек путем определения метода-обработчика OnMoveGripPoints():
public override void OnMoveGripPoints(List indexes, Vector3d offset, bool isStretch) { if (!TryModify()) return; if (indexes.Count == 2) { _pnt1 += offset; _pnt2 += offset; } else if (indexes.Count == 1) { if (indexes[0] == 0) _pnt1 += offset; else _pnt2 += offset; } }
Параметр indexes здесь содержит список номеров ручек, offset - вектор перемещения ручек.
Затем определим метод OnTransform() таким образом, чтобы при трансформации объекта рассчитывались новые координаты для обеих определяющих угловых точек:
public override void OnTransform(Matrix3d tfm) { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; _pnt1 = _pnt1.TransformBy(tfm); _pnt2 = _pnt2.TransformBy(tfm); }
И, наконец, добавим возможность редактирования текстовой строки внутри рамки. Редактирование может осуществляться по двойному щелчку мыши на объекте или путем выбора соответствующего пункта контекстного меню. При вызове команды редактирования будет вызываться форма с текстовым полем, в котором можно вводить новое значение строки:
public override hresult OnEdit(Point3d pnt, EditFlags lInsertType) { TextInBox_Form frm = new TextInBox_Form(); frm.textBox1.Text = Text; frm.ShowDialog(); Text = frm.textBox1.Text; return hresult.s_Ok; }
Добавление свойств объекта в инспектор свойств
MultiCAD .NET API предоставляет возможность добавления свойств пользовательского объекта в инспектор свойств объекта, независимо от платформы, где будет открыт .dwg файл, будь то AutoCAD или nanoCAD. Это делается путем добавления для соответствующего общедоступного свойства объекта следующих атрибутов:
DisplayNameAttribute
- определяет имя свойства, которое будет отображаться в инспекторе;
DescriptionAttribute
- задает описание свойства;
CategoryAttribute
- определяет имя категории, в которой будет отображаться данное свойство.
Воспользуемся этой возможностью и добавим свойство Text в палитру свойств объекта:
[DisplayName("Текстовая метка")] [Description("Описание метки")] [Category("Текстовый объект")] public String Text { get { return _text; } set { //Save Undo state and set the object status to "Changed" if (!TryModify()) return; // Set new text value _text = value; } }
После этого, значение текстовой строки нашего объекта будет отображаться в инспекторе объектов:
Итак, мы создали первую версию примитива, который можно вставить в чертёж формата .dwg и отредактировать несколькими привычными для пользователей САПР способами. Но жизнь на месте не стоит, и функционал примитивов приходится наращивать. В одной из следующих статей мы рассмотрим вторую версию примитива, куда мы добавим новые поля, и расскажем, какие возможности по работе с версиями примитивов предоставляет MultiCAD.NET API.