Инструменты пользователя

Инструменты сайта


Боковая панель

developers:tutorial:customentity

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


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

Программный комплекс Топоматик Робур позволяет разрабатывать пользовательские примитивы. Примитив - это элемент модели чертежа. Каждый примитив умеет отобразить себя в чертеже, и при необходимости в окне 3D вида.

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

Для создания пользовательского примитива необходимы следующие действия

  1. Создание класса примитива
  2. Создание класса обёртки примитива для работы с инспектором свойств
  3. Создание класса контроллера примитива для определения его графического отображения
  4. Создание команды добавления примитива на план

Для связывания примитива с 3D-моделью рассмотрим процесс создания пользовательской библиотеки 3D-объектов. Во время создания библиотеки, мы также создадим пользовательские элементы ИМ и свяжем их с 3D-моделями. Этот процесс не является обязательным.

Подготовка модуля

Создайте и настройте новый модуль для подключения к программному комплексу Топоматик Робур. С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки:

  • Topomatic.Cad.Foundation.dll - базовые математические типы и операции
  • Topomatic.Cad.View.dll - элемент управления для отображения слоёв моделей
  • Topomatic.ComponentModel.dll – атрибуты свойств класса
  • Topomatic.Dwg.dll –классы элементов области рисования
  • Topomatic.Dwg.Layer.dll – слои области рисования
  • Topomatic.Visualization.dll - классы элементов информационной модели

Пользовательский примитив

В этом примере мы создадим точечный пользовательский примитив и свяжем его с пользовательской 3D-моделью и элементом ИМ. Класс примитива содержит много кода, поэтому для удобства сделаем его разделяемым применив модификатор partial.

Создадим класс примитива. Класс должен наследовать абстрактный тип DwgEntity. Для связи с элементом ИМ также необходимо реализовать интерфейс IConstructionModelHolder. Перекроем необходимые методы и свойства, а также добавим свои для возможности изменять состояние примитива через окно свойств. Класс декорируется атрибутами DesignAliasAttribute и EntityControllerAttribute.

В методе OnRegen() необходимо переопределять границы примитива (свойство Bounds).

Для изменения отображения и положения примитива в пространстве добавим свойства:

  1. Position – координаты примитива
  2. Angle – угол поворота
  3. Scale – масштаб
  4. Element – элемент ИМ

Добавим метод GetPlan() возвращающий блок (DwgBlock) содержащий примитивы для отображения на плане. В нашем случает блок формируется с помощью метода GetView() элемента ИМ (класс ImViewElement). Добавьте в программу новый класс со следующим содержанием:

CustomDwgEntity.cs
[EntityController(typeof(CustomDwgEntityController))]
[DesignAlias("CUSTOMDWGENTITY")]
public partial class CustomDwgEntity : DwgEntity, IConstructionModelHolder, IPointObject
{
    public const string PARENT_SMDX = "SmdxCustomVolumeElement";
    private ImElement m_Element;
    private GeometryModelsCache m_Cache;
    private Vector3D m_Position;
    private Vector3D m_Normal = new Vector3D(0, 0, 1);
    private Vector3D m_Scale = new Vector3D(1, 1, 1);
    private double m_Angle = 0;
    private Drawing m_Plan;
    private int m_Changes;
 
    #region Properties
    [Browsable(false)]
    public Matrix Matrix
        {
            get
            {
                return Matrix.CreateExtrudedInsertion(m_Position, m_Scale, m_Normal, m_Angle);
            }
        }
 
    [DisplayName("Положение")]
    public Vector3D Position
        {
            get
            {
                return m_Position;
            }
            set
            {
                if (Position != value)
                {
                    BeginChange();
                    try
                    {
                        m_Position = value;
                    }
                    finally
                    {
                        EndChange();
                    }
                }
            }
        }
 
    [Browsable(false)]
    public double Rotation
        {
            get
            {
                return Angle;
            }
            set
            {
                Angle = value;
            }
        }
 
    [Angle]
    [DisplayName("Угол поворота")]
    public double Angle
        {
            get
            {
                return m_Angle;
            }
            set
            {
                if (Angle != value)
                {
                    BeginChange();
                    try
                    {
                        m_Angle = value;
                    }
                    finally
                    {
                        EndChange();
                    }
                }
            }
        }
 
    Vector3D IPointObject.BasePoint
        {
            get { return m_Position; }
        }
 
    [GlobalVector, DisplayName("Масштаб")]
    public Vector3D Scale
        {
            get
            {
                return m_Scale;
            }
            set
            {
                if (Scale != value)
                {
                    BeginChange();
                    try
                    {
                        m_Scale = value;
                    }
                    finally
                    {
                        EndChange();
                    }
                }
            }
        }
 
    [DisplayName("3D модель"), ImObjectPropertyProvider(PARENT_SMDX, false)]
    public ImElement Element
        {
            get
            {
                return m_Element;
            }
            set
            {
                BeginUpdate();
                try
                {
                    m_Element = value;
                    m_Plan = null;
                    m_Cache = null;
                    Invalidate();
                }
                finally
                {
                    EndUpdate();
                }
            }
        }
 
    [Browsable(false)]
    public Vector3D Normal
        {
            get
            {
                return m_Normal;
            }
            set
            {
                if (Normal != value)
                {
                    BeginChange();
                    try
                    {
                        Vector3D position, scale;
                        double angle;
                        var view1 = Matrix.GetElementView(out position, out scale, out angle);
                        m_Normal = value;
                        var view2 = Matrix.GetElementView(out position, out scale, out angle);
                        if (view1 != view2)
                        {
                            m_Plan = null;
                        }
                        Invalidate();
                    }
                    finally
                    {
                        EndChange();
                    }
                }
            }
        }
 
    [Browsable(false)]
    public GeometryModelsCache Cache
        {
            get
            {
                if (m_Cache == null)
                {
                    if (m_Element != null)
                    {
                        var builder = new GeometryModelsCacheBuilder(Position);
                        m_Cache = builder.Create(m_Element, Matrix);
                    }
                }
                return m_Cache;
            }
        }
 
    public override bool IsBreakable
        {
            get
            {
                return (m_Element != null) && (m_Element.IsAssembly);
            }
        }
 
    public override bool IsPurged
        {
            get
            {
                return m_Element == null;
            }
        }
 
    public override bool IsProxyGraphics
        {
            get
            {
                return false;
            }
        }
 
    public override string EntityName
        {
            get { return TypeExplorer.GetSerializableString(GetType()); }
        }
 
    [Category("Информационная модель"), TypedObjectProvider(null)]
    public TypedObject TypedObject
        {
            get
            {
                return new CustomElementWrapper(this);
            }
        }
 
    [Browsable(false)]
    public BoundingBox3D Bounds3d
        {
            get
            {
                if (IsInvalid)
                {
                    Regen(EventArgs.Empty);
                }
                var cache = Cache;
                if (cache != null)
                {
                    var bounds = cache.Bounds;
                    bounds.Min += m_Position;
                    bounds.Max += m_Position;
                    return bounds;
                }
                else
                {
                    return new BoundingBox3D();
                }
            }
        }
 
    [Browsable(false)]
    public bool HasCache3D
        {
            get
            {
                return m_Cache != null;
            }
        }
    #endregion
 
    protected override void OnRegen(EventArgs e)
        {
            var matrix = Matrix;
            if (m_Cache != null)
            {
                m_Cache.Dispose();
            }
            m_Cache = null;
            var plan = GetPlan(1.0);
            if ((plan != null) && (plan.Count > 0))
            {
                Vector3D position, scale;
                double angle;
                matrix.GetElementView(out position, out scale, out angle);
                Bounds = plan.Bounds * Matrix.CreateInsertion(position, scale, angle);
            }
            else
            {
                Invalidate();
            }
        }
 
    protected override void OnBreak(IList<DwgEntity> list)
        {
            if (m_Element != null)
            {
                var matrix = Matrix;
                var model = m_Element.GetModel();
                if (model != null)
                {
                    var m = matrix;
                    Vector3D position, scale, normal;
                    double angle;
                    m.GetExtrudedInsertion(out position, out scale, out normal, out angle);
                    var e = new CustomDwgEntity();
                    e.CopyProperties(this);
                    e.Position = position;
                    e.Scale = scale;
                    e.Normal = normal;
                    e.Angle = angle;
                    e.Element = new Static3DElement(m_Element.Name, m_Element.GetObjectType(), m_Element.GetProperties().Clone(), model, new ImDocuments(m_Element));
                    e.m_Plan = null;
                    e.m_Cache = null;
                    list.Add(e);
                }
                if (m_Element.IsAssembly)
                {
                    foreach (var item in m_Element.GetReferences())
                    {
                        var m = item.GetMatrix() * matrix;
                        Vector3D position, scale, normal;
                        double angle;
                        m.GetExtrudedInsertion(out position, out scale, out normal, out angle);
                        var e = new CustomDwgEntity();
                        e.CopyProperties(this);
                        e.Position = position;
                        e.Scale = scale;
                        e.Normal = normal;
                        e.Angle = angle;
                        e.Element = item.Element;
                        e.m_Plan = null;
                        e.m_Cache = null;
                        list.Add(e);
                    }
                }
            }
        }
 
    protected override void OnAssign(DwgEntity source)
        {
            var model = source as CustomDwgEntity;
            if (model != null)
            {
                if (model.m_Element != null)
                {
                    m_Element = model.m_Element.Clone();
                    m_Cache = model.m_Cache;
                    m_Plan = model.m_Plan;
                    m_Position = model.m_Position;
                    m_Normal = model.m_Normal;
                    m_Scale = model.m_Scale;
                    m_Angle = model.m_Angle;
                }
                else
                {
                    m_Element = null;
                }
            }
        }
 
    protected override void OnTransform(Matrix matrix)
        {
            BeginChange();
            try
            {
                (Matrix * matrix).GetExtrudedInsertion(out m_Position, out m_Scale, out m_Normal, out m_Angle);
            }
            finally
            {
                EndChange();
            }
        }
 
    protected override void OnSaveToStg(StgNode node)
        {
            m_Position.SaveToStg(node.AddNode("Position"));
            m_Normal.SaveToStg(node.AddNode("Normal"), Vector3D.UnitZ);
            m_Scale.SaveToStg(node.AddNode("Scale"), Vector3D.One);
            node.AddDouble("Angle", m_Angle, 0.0);
        }
 
    protected override void OnSaveToStg(StgNode node, ISerializationContext context)
        {
            base.OnSaveToStg(node, context);
            if (m_Element != null)
            {
                m_Element.SaveToStg(node.AddNode("Model"), context);
            }
            if (m_Plan != null)
            {
                node.AddInt32("PlanSign", context.AddSerializable(m_Plan));
            }
        }
 
    protected override void OnLoadFromStg(StgNode node)
        {
            if (node.IsExists("Element"))
            {
                var context = new SerializationContext(this);
                context.LoadFromStg(node.GetNode("Context"));
                m_Element = ImElement.LoadFromStg(node.GetNode("Element"), context);
            }
            m_Position = Vector3D.LoadFromStg(node.GetNode("Position"));
            m_Normal = Vector3D.LoadFromStg(node.GetNode("Normal"), Vector3D.UnitZ);
            m_Scale = Vector3D.LoadFromStg(node.GetNode("Scale"), Vector3D.One);
            m_Angle = node.GetDouble("Angle", 0);
        }
 
    protected override void OnLoadFromStg(StgNode node, ISerializationContext context)
        {
            base.OnLoadFromStg(node, context);
            if (node.IsExists("Model"))
            {
                m_Element = ImElement.LoadFromStg(node.GetNode("Model"), context);
            }
            if (node.IsExists("PlanSign"))
            {
                m_Plan = context.GetSerializable<Drawing>(node.GetInt32("PlanSign"));
            }
        }
 
 
    public void BeginChange()
        {
            m_Changes++;
            BeginUpdate();
        }
 
    public void EndChange()
        {
            m_Changes--;
            if (m_Changes == 0)
            {
                m_Plan = null;
                m_Cache = null;
                Invalidate();
                EndUpdate();
            }
        }
 
    public double? Fire(Ray3D ray)
        {
            if (IsInvalid)
            {
                Regen(EventArgs.Empty);
            }
            var cache = Cache;
            if (cache != null)
            {
                ray.Position -= Position;
                return cache.Fire(ray);
            }
            return null;
        }
 
    [Browsable(false)]
    public DwgBlock GetPlan(double mapscale)
        {
            var sscale = mapscale.ToString(CultureInfo.InvariantCulture);
            if (m_Plan == null)
            {
                if (m_Element != null)
                {
                    Vector3D position, scale;
                    double angle;
                    var view = Matrix.GetElementView(out position, out scale, out angle);
                    m_Plan = new Drawing();
                    var vel = m_Element as ImViewElement;
                    if (vel != null)
                    {
                        try
                        {
                            vel.GetView(view, m_Plan.Blocks.Add(sscale), Matrix.Identity, mapscale);
                        }
                        catch
                        { }
                    }
                }
            }
            if (m_Plan == null)
            {
                return null;
            }
            var block = m_Plan.Blocks[sscale];
            if (block == null)
            {
                if (m_Element != null)
                {
                    Vector3D position, scale;
                    double angle;
                    var view = Matrix.GetElementView(out position, out scale, out angle);
                    m_Plan = new Drawing();
                    var vel = m_Element as ImViewElement;
                    if (vel != null)
                    {
                        try
                        {
                            block = m_Plan.Blocks.Add(sscale);
                            vel.GetView(view, block, Matrix.Identity, mapscale);
                        }
                        catch
                        { }
                    }
                }
            }
            return block;
        }
}

Обёртка примитива

Обёртка примитива (Wrapper) – это объект-посредник между примитивом и инспектором свойств. С его помощью обеспечивается изменение состояния примитива. Класс обёртки описывается внутри класса примитива. Создадим класс обёртки. Для удобства работы с кодом, поместим описание класса-обёртки в отдельный файл. Класс должен наследовать абстрактный тип TypedObjectWrapper. Перекроем необходимые методы. Добавьте в программу новый класс со следующим содержанием:

CustomDwgEntity.Wrapper.cs
public partial class CustomDwgEntity
{
    class CustomElementWrapper : TypedObjectWrapper
    {
        private CustomDwgEntity m_Owner;
 
        public CustomElementWrapper(CustomDwgEntity owner)
            {
                m_Owner = owner;
            }
 
        protected override TypedObject GetTypedObject()
            {
                return m_Owner.m_Element;
            }
 
        protected override void OnTypeChanged(ImTypeDescriptor dsc)
            {
                throw new NotSupportedException();
            }
 
        protected override void OnAddProperty(TypedObject tobj, ImProperty p)
            {
                m_Owner.BeginUpdate();
                try
                {
                    var properties = tobj.GetProperties();
                    properties.Add(p);
                    tobj.ApplayOverridedProperties(properties);
 
                    m_Owner.m_Cache = null;
                    m_Owner.m_Plan = null;
                    m_Owner.Invalidate();
                }
                finally
                {
                    m_Owner.EndUpdate();
                }
            }
 
        protected override void OnModifiedProperty(TypedObject tobj, ImProperty p, object value)
            {
                m_Owner.BeginUpdate();
                try
                {
                    if (tobj.TryGetProperty(p, out var property))
                    {
                        property.Value = value;
                        tobj.ApplayOverridedProperties(new[] { property });
                    }
 
                    m_Owner.m_Cache = null;
                    m_Owner.m_Plan = null;
                    m_Owner.Invalidate();
                }
                finally
                {
                    m_Owner.EndUpdate();
                }
            }
 
        protected override void OnRemoveProperty(TypedObject tobj, ImProperty property)
            {
                m_Owner.BeginUpdate();
                try
                {
                    var properties = tobj.GetProperties();
                    properties.Remove(property);
                    tobj.ApplayOverridedProperties(properties);
 
                    m_Owner.m_Cache = null;
                    m_Owner.m_Plan = null;
                    m_Owner.Invalidate();
                }
                finally
                {
                    m_Owner.EndUpdate();
                }
            }
    }
}

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

Контроллер отвечает за отображение примитива на плане, а также на 3D-виде, если это необходимо. Создадим класс контроллера. Класс должен наследовать абстрактный тип DwgEntityController. Перекроем необходимые методы и свойства. Если требуется отображение на 3D-виде, то дополнительно нужно перекрыть свойство SupportPaint3d, а также методы OnPaintEntity3d() для отрисовки и Fire() для выделения 3D-модели в указанной точке. Добавьте в программу новый класс со следующим содержанием:

CustomDwgEntityController.cs
public class CustomDwgEntityController : DwgEntityController
{
    public override bool SupportPaint3d
        {
            get
            {
                return true;
            }
        }
 
    protected override void OnPaintEntity(DwgEntity entity, PaintEntityEventArgs args)
    {
        var e = (CustomDwgEntity)entity;
        var block = e.GetPlan(args.AnnotationScale);
        if (block != null)
        {
            Vector3D position, scale;
            double angle;
            e.Matrix.GetElementView(out position, out scale, out angle);
            PaintEntityEventArgs.PaintEntities(e, block, position, scale, angle, args);
        }
    }
 
    public override IEnumerable GetGrips(DwgEntity entity, object cadview)
    {
        var e = (CustomDwgEntity)entity;
        var has_grips = false;
        var drawing = e.Drawing;
        var obj = e.TypedObject;
        var cadView = (CadView)cadview;
        foreach (var p in obj.GetAllProperties())
        {
            var grips = TypedPropertyManagerCollection.Current.GetGrips(cadView, drawing, obj, p, e.Matrix);
            if (grips != null)
            {
                foreach (var tg in grips)
                {
                    if (tg.Location.EqualsEps(e.Position))
                    {
                        has_grips = true;
                    }
                    yield return tg;
                }
            }
        }
        if (!has_grips)
        {
            var grip = new EntityGrip(((CadView)cadview), entity);
            grip.Location = e.Position;
            var z = e.Position.Z;
            grip.Move += delegate (Vector3D vertex)
            {
                e.Position = new Vector3D(vertex.Pos, z);
            };
            yield return grip;
        }
    }
 
    protected override void OnPaintEntity3d(DwgEntity entity, PaintEntityEventArgs args)
        {
            var e = (CustomDwgEntity)entity;
            var dc = args.Pen.DeviceContext;
            if (!e.HasCache3D)
            {
                if (dc.Simplify)
                {
                    dc.RequestRedraw();
                    return;
                }
            }
            if (args.Pen.DeviceContext.gContains(e.Bounds3d))
            {
                var cache = e.Cache;
                if (cache != null)
                {
                    var pivot = dc.Pivot;
                    dc.Pivot = e.Position;
                    try
                    {
                        cache.Paint(dc);
                    }
                    finally
                    {
                        dc.Pivot = pivot;
                    }
                }
            }
        }
 
    public override double? Fire(DwgEntity entity, Ray3D ray)
        {
            var e = (CustomDwgEntity)entity;
            return e.Fire(ray);
        }
}

Команда вставки примитива на план

В теле программного модуля объявите команду, и декорируйте её атрибутом «cmd». Команда «insert_custom_entity» предложит пользователю указать точку на плане, выбрать 3D-модель из библиотеки и добавить пользовательский примитив в чертёж активной модели. 3D-модели будут отфильтрованы в соответствии с константой PARENT_SMDX класса CustomDwgEntity. В списке будут доступны только те модели, чей smdx-тип унаследован от «SmdxCustomVolumeElement».

Module.cs
[cmd("insert_custom_entity")]
private void InsertCustomEntity()
{
    var cadView = this.CadView;
    if (DrawingLayer.GetDrawingLayer(cadView) is DrawingLayer drawingLayer)
    {
        var drawing = drawingLayer.Drawing;
        Vector3D point;
        if (!CadCursors.GetPoint(cadView, out point, "Укажите точку вставки примитива")) return;
        if (ImObjectPropertyProvider.SelectObject(CustomDwgEntity.PARENT_SMDX) is ImElement imElement)
        {
            var block = drawing.ActiveSpace;
            var entity = new CustomDwgEntity{Element = imElement, Position = point};
            block.Add(entity);
 
            cadView.Unlock();
            cadView.Invalidate();
        }
    }
}

Теперь необходимо сформировать наш plugin-файл. Заполните его следующим образом.

TutorialCustomDwgEntity.plugin
{
  "assemblies": {
    "TutorialCustomDwgEntity": {
      "assembly": "TutorialCustomDwgEntity.dll, TutorialCustomDwgEntity.ModulePluginHost"
    }
  },
 
  "actions": {
    "id_insert_custom_entity": {
      "cmd": "insert_custom_entity",
      "title": "Пользовательский примитив",
      "description": "Вставка пользовательского примитива"
    }
  },
 
  "menubars": {
    "rbproj": {
      "items": [
        {
          "id": "tutorial_menu",
          "title": "Tutorial",
          "items": [
            "id_insert_custom_entity"
          ]
        }
      ]
    }
  }
}

Результатом запуска проекта будет появление в главном меню пункта «Tutorial», с подпунктами, которые будут работать в соответствии с описанными выше алгоритмами.

Исходный код примера расположен в проекте «TutorialCustomDwgEntity».
developers/tutorial/customentity.1670574752.txt.gz · Последние изменения: 2022/12/09 08:32 — proxor