Сериализация объектов в MultiCAD.NET. Управление совместимостью чертежей и прокси-объектами


    При создании пользовательских объектов на традиционном C++ API (NRX в nanoCAD, ObjectARX в AutoCAD) для обеспечения сохранения объектов и чтения их из файла чертежа необходимо в явном виде описывать запись (сериализацию) и чтение (десериализацию) каждого поля. В MultiCAD.NET API применён более привычный .NET разработчикам описательный подход, в основе которого лежит стандартная .NET сериализация.

    Применение сериализации, нечувствительной к версии объектов (Version Tolerance Serialization), предоставляет разработчикам более гибкий механизм управления совместимостью объектов разных версий, чем существующий в традиционном C++ API, где предусмотрено чтение предыдущих версий, но чтение файлов «из будущего» невозможно.

    В MultiCAD.NET при описании новых версий объектов можно указать, что вновь добавленные поля необязательны, и тогда чертёж, сохранённый в формате новой версии приложения, прочтётся и в предыдущей версии. Разумеется, без изменений остался и традиционный подход, приводящий к созданию прокси объектов (кешированной графики объектов) при загрузке чертежа в предыдущую версию приложения.

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


    Давайте рассмотрим как MultiCAD.NET использует оба этих подхода (VTS и механизм proxy-объектов) для организации управления совместимостью объектов. В качестве наглядного примера рассмотрим две версии пользовательского объекта «Крестовая метка».



    Создание класса для экспериментов


    Об основах создания пользовательских примитивов в MultiCAD.NET мы рассказывали в одной из прошлых статей, поэтому не будем подробно на этом останавливаться, а сразу перейдём к описанию пользовательского примитива «Крестовая метка»:

    [CustomEntity("1C925FA1-842B-49CD-924F-4ABF9717DB62", 1, "Crossmark", "Crossmark Sample Entity")]
    [Serializable]
    public class CrossMark : McCustomBase
    {
      private Point3d pnt1;
      private Point3d pnt2;
      private Point3d pnt3;
      private Point3d pnt4;
    }
    

    Полный код приложения доступен здесь. После компиляции приложения и запуска его командой «crossmark» по результату пользовательского ввода на чертеж будет добавлен следующий примитив:

    image

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

    Вторая версия класса и обеспечение межверсионной совместимости объектов


    Во второй версии класса мы изменим геометрию нашего примитива, добавив окружность в центр крестовой метки. В качестве дополнительного поля добавим радиус этой окружности, снабдив его атрибутом [OptionalField], чтобы отметить его, как необязательное в случае десериализации более ранней версии объекта. Укажем также и номер новой версии, используюя свойство VersionAdded:

    [OptionalField(VersionAdded = 2)]
    private double radius = -1;
    

    Инициализируем новую переменную в конструкторе и добавим общедоступное свойство для получения доступа к этому полю:

    public CrossMark() 
    {
      ...
      radius = 10;
    }
    
    [Category("Circular component")]
    [DisplayName("Circle radius")]
    [Description("Radius of circular component of the cross mark")]
    public double Radius
    {
      get
      {
        return radius;
      }
      set
      {
        radius = value;
      }
    }
    

    Для того, чтобы проинициировать добавленнное поле после десериализации объекта правильным значением, будет использоваться метод OnDeserialized с атрибутом [OnDeserialized]:

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
      if (radius == -1)
      {
        radius = 10;
      }
    }
    


    Теперь приложения, работающие с новыми версиями типа, могут также принимать объекты более старых версий. Кроме этого, VTS также решает проблему обратной совместимости: приложения, работающие со старыми версиями объектов могут принимать и новые версии: «лишние» поля при этом просто игнорируются и десериализация проходит успешно.

    Таким образом, мы обеспечили полную совместимость версий нашего класса. Осталось только добавить отрисовку окружности в метод OnDraw():

    public override void OnDraw(GeometryBuilder dc)
    {
      ...
      dc.DrawCircle(new Point3d(pnt1.X + 25, pnt1.Y, pnt1.Z), radius);
    }
    

    Полный код второй версии приложения также доступен в архиве. Компилируем проект, запускаем nanoCAD, загружаем построенную сборку и запускаем новую версию приложения все той же командой «crossmark». После указания точки вставки получаем .dwg файл с обновленной версией нашего примитива:

    image

    Так же сохраняем полученный файл.

    Результаты

    А теперь убедимся в полной совместимости обеих версий.

    Запускаем nanoCAD, загружаем вторую версию сборки и открываем первую версию файла. Файл будет успешно открыт и на экране нас ожидает сохраненный в первой версии примитив. Как только будет произведено какое-либо действие, приводящие к перерисовке объекта (например, перемещение) объект будет перерисован в новой версии с учетом работы конструктора сериализации: крестовая метка будет нарисована с окружностью заданного радиуса. Для этого также может быть использована команда REGENOBJ.
    Закрываем файл. Загружаем первую версию приложения и открываем вторую версию файла. Файл также успешно загрузится и первоначально будет отрисован объект второй версии, как он был сохранен в файле. После же перерисовки крестовая метка будет изображена в том виде, который был определен в первой версии.

    Традиционный подход. Совместимость сверху вниз


    Механизм VTS, позволяющий обеспечить совместимость снизу вверх, применим далеко не всегда, поскольку не все данные в новых версиях объектов могут быть сочтены несущественными. Для того, чтобы обеспечить совместимость сверху вниз, при создании новой версии объекта необходимо использовать атрибут следующего вида:

    [CustomEntity(guid, majorVersion, databaseName, localName)]
    

    Здесь свойство majorVersion как раз и задает главную версию объекта, в рамках которой будет обеспечиваться полная совместимость. Если новые объекты созданы с majorVersion, значение которого больше предыдущего, то при попытке открытия таких объектов более старой версией кода будет получен код возврата eMakeMeProxy. В этом случае объекты на чертеже будут представлены как прокси-объекты, что исключает возможность их изменения и редактирования.

    Заключение


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

    Обсуждение статьи доступно также и на нашем форуме: forum.nanocad.ru/index.php?showtopic=6515.
    Перевод статьи на английский: Serializing objects in MultiCAD.NET. Managing drawings compatibility and proxy objects.
    Нанософт
    37.57
    Company
    Share post

    Comments 0

    Only users with full accounts can post comments. Log in, please.