Одним из главных недостатков традиционного .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.
Автор: ISL