Создание пользовательских примитивов в САПР на MultiCAD .NET API

    Одним из главных недостатков традиционного .NET API в .dwg совместимых САПР является невозможность создания пользовательских примитивов (Custom Entities) на .NET. Пользовательские примитивы создаются на С++, для их использования в .NET необходимо создать управляемые обёртки на C++/CLI.

    Технология MultiCAD .NET позволяет создавать пользовательские примитивы, не выходя за рамки управляемого кода. Помимо отсутствия промежуточных объектов на C++, в MultiCAD .NET максимально используются стандартные для .NET механизмы, как следствие нет необходимости во многих привычных для САПР программистов операциях: не нужно вручную описывать сериализацию, свойства в инспектор можно вывести без создания COM объекта и т.п.

    В качестве демонстрации MultiCAD .NET мы рассмотрим пример приложения CustomObjects, содержащийся в комплекте поставки SDK. Этот пример создает пользовательский примитив, который представляет собой прямоугольную рамку с находящимся внутри текстом:

    Sample TextInBox MultiCAD .NET Entity

    Чертежи, содержащие наш тестовый примитив, могут быть открыты в любой .dwg совместимой САПР. Для изменения примитива необходимо загрузить сборку, содержащую код примитива, причём во все поддерживаемые САПР платформы загружается одна и та же сборка без перекомпиляции. Технология является родной для nanoCAD, для загрузки модуля в AutoCAD требуется модуль расширения (Object Enabler). Как это работает смотрите под катом.

    Класс пользовательского примитива

    Для создания нового типа примитива необходимо написать класс, наследованный от McCustomBase — базового класса для всех пользовательских примитивов. Кроме этого, для объявленного класса необходимо использовать два атрибута:
    1. атрибут [CustomEntity] с указанием типа класса, его GUID, имени, которое будет использоваться для всех таких объектов в базе данных чертежа и локального имени,
    2. атрибут [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;
    }
    

    Теперь, после выбора объекта на чертеже, в заданных точках будут отображены ручки:

    image

    Добавим возможность перемещения определяющих угловых точек с помощью перетаскивания ручек путем определения метода-обработчика 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;
    }
    


    image

    Добавление свойств объекта в инспектор свойств

    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;
      }
    }
    

    После этого, значение текстовой строки нашего объекта будет отображаться в инспекторе объектов:

    image

    Итак, мы создали первую версию примитива, который можно вставить в чертёж формата .dwg и отредактировать несколькими привычными для пользователей САПР способами. Но жизнь на месте не стоит, и функционал примитивов приходится наращивать. В одной из следующих статей мы рассмотрим вторую версию примитива, куда мы добавим новые поля, и расскажем, какие возможности по работе с версиями примитивов предоставляет MultiCAD.NET API.

    Обсуждение статьи доступно также и на нашем форуме: forum.nanocad.ru/index.php?showtopic=6504.

    Перевод статьи на английский: Creating custom entities in CAD with MultiCAD .NET API.
    • +13
    • 9,2k
    • 7

    Нанософт

    48,00

    Компания

    Поделиться публикацией
    Комментарии 7
      +1
      Если можно, несколько вопросов:
      1) Multicad net api — это по сути библиотека классов, являющихся net-обертками для Objectarx? Т.е. «переосмысленный» автокадовский .net API, как я понял? (напрашивается аналогия кстати с EntityJIG <=> InputJIG)
      2) Оно платное? :) Если нет, то где можно посмотреть и пощупать? Или хотя бы посмотреть документацию по классам.
      PS. Спасибо за статью, как раз на днях занялся вопросом CustomObjects и хоп! Ваша статья! Как нельзя кстати. По кастомным объектам в интернете информации кот наплакал.
        +1
        MultiCAD.NET API является обёрткой над С++ MultiCAD API, которое, в свою очередь, является надстройкой над:
        • нативным API nanoCAD-а (в основе которого библиотека Teigha.DWG от Open Design Alliance),
        • ObjectARX в AutoCAD.

        В текущей на сегодняшний момент бесплатной версии nanoCAD 3.7 MultiCAD.NET API ещё не было, но лицензию для разработки на любую версию nanoCAD-а можно получить бесплатно в Клубе разработчиков nanoCAD. В следующей бесплатной версии MultiCAD.NET API будет.

        Документация и примеры являются частью SDK, которое входит в состав дистрибутива nanoCAD 4.0 и выше. По умолчанию не ставится, нужно не забыть включить при установке «Средства разработки (SDK)».

        Object Enabler MultiCAD.NET API для AutoCAD можно скачать в Клубе разработчиков nanoCAD.
        0
        День добрый, спасибо за статью, с ней код примера стал совсем прозрачным и понятным.
        Теперь небольшой вопрос, можно ли созданный в MultiCAD.NET API примитив корректно использовать в приложении которое пишется под .NET API?
          0
          Статья на тему взаимодействия классического .NET API и MultiCAD .NET API у нас уже запланирована, приятно видеть, что с темой мы угадали.

          Если коротко, то полученный примитив можно использовать ровно так же, как любой «чужой» примитив для которого нет классической .NET обёртки, т.е. до уровня класса Entity.
          0
          Я немного подправил код, и перечислил ряд замечаний. Опубликовал здесь.

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

        Самое читаемое