====== Создание пользовательского примитива ====== Программный комплекс Топоматик Робур позволяет разрабатывать пользовательские примитивы. **Примитив** - это элемент модели чертежа. Каждый примитив умеет отобразить себя в чертеже, и при необходимости в окне 3D вида. ==== Создание пользовательского примитива ==== Для создания пользовательского примитива необходимы следующие действия - Создание класса примитива, наследника от **DwgEntity** и связь его с чертежом и контроллером через атрибуты **DesignAliasAttribute** и **EntityControllerAttribute**. - Создание класса контроллера примитива, наследника от **DwgEntityController**. Он служит для графического отображения на плане и 3D-виде - Создание команды добавления примитива на план ==== Подготовка модуля ==== Создайте и настройте новый модуль для подключения к программному комплексу Топоматик Робур. С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки: * Topomatic.Cad.Foundation.dll - базовые математические типы и операции * Topomatic.Cad.View.dll - элемент управления для отображения слоёв моделей * Topomatic.ComponentModel.dll – атрибуты свойств класса * Topomatic.Dwg.dll – элементы области рисования * Topomatic.Dwg.Layer.dll – слои области рисования * Topomatic.Visualization.dll - элементы информационной модели ==== Пользовательский примитив ==== В этом примере мы создадим точечный пользовательский примитив и свяжем его с и элементом ИМ содержащим 3D-модель. Создадим класс примитива. Класс должен наследовать абстрактный тип **DwgEntity**. Для связи с элементом ИМ также добавим свойство **Element** типа **ImElement**. Перекроем необходимые методы и свойства, а также добавим свои для возможности изменять состояние примитива через окно свойств. Класс декорируется атрибутами **DesignAliasAttribute** и **EntityControllerAttribute**. В методе **OnRegen()** необходимо переопределять границы примитива (свойство **Bounds**). Для изменения отображения и положения примитива в пространстве добавим свойства: - **Position** – координаты примитива - **Angle** – угол поворота - **Element** – элемент ИМ Добавим метод **GetPlan()** возвращающий блок (**DwgBlock**) содержащий примитивы для отображения на плане. В нашем случает блок формируется с помощью метода **GetView()** элемента ИМ (класс **ImViewElement**). Добавьте в программу новый класс со следующим содержанием: [EntityController(typeof(CustomDwgEntityController))] [DesignAlias("CUSTOMDWGENTITY")] public class CustomDwgEntity : DwgEntity, IPointObject { public const string PARENT_SMDX = "SmdxElement"; private ImElement m_Element; private Vector3D m_Position; private Vector3D m_Normal = new Vector3D(0, 0, 1); private double m_Angle = 0; private Drawing m_Plan; #region Properties [Browsable(false)] public Matrix Matrix { get { return Matrix.CreateExtrudedInsertion(m_Position, Vector3D.One, m_Normal, m_Angle); } } [DisplayName("Положение")] public Vector3D Position { get { return m_Position; } set { if (Position != value) { m_Position = value; } } } [Angle] [DisplayName("Угол поворота")] public double Angle { get { return m_Angle; } set { if (Angle != value) { m_Angle = value; } } } Vector3D IPointObject.BasePoint { get { return m_Position; } } [DisplayName("3D модель"), ImObjectPropertyProvider(PARENT_SMDX, false)] public ImElement Element { get { return m_Element; } set { BeginUpdate(); try { m_Element = value; m_Plan = null; Invalidate(); } finally { EndUpdate(); } } } [Browsable(false)] public Vector3D Normal { get { return m_Normal; } set { if (Normal != value) { 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(); } } } 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()); } } [Browsable(false)] public BoundingBox3D Bounds3d { get { if (IsInvalid) { Regen(EventArgs.Empty); } var cache = GetCache(); if (cache != null) { var bounds = cache.Bounds; bounds.Min += m_Position; bounds.Max += m_Position; return bounds; } return new BoundingBox3D(); } } #endregion protected override void OnRegen(EventArgs e) { var matrix = Matrix; 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 list) { if (m_Element != null) { var matrix = Matrix; var model = m_Element.GetModel(); if (model != null) { var element = new Static3DElement( m_Element.Name, m_Element.GetObjectType(), m_Element.GetProperties().Clone(), model, new ImDocuments(m_Element)); ExtractEntities(list, matrix, model, element); } if (m_Element.IsAssembly) { foreach (var item in m_Element.GetReferences()) { var m = item.GetMatrix() * matrix; ExtractEntities(list, m, model, item.Element); } } } } protected override void OnAssign(DwgEntity source) { var entity = source as CustomDwgEntity; if (entity != null) { if (entity.m_Element != null) { m_Element = entity.m_Element.Clone(); m_Plan = entity.m_Plan; m_Position = entity.m_Position; m_Normal = entity.m_Normal; m_Angle = entity.m_Angle; } else { m_Element = null; } } } protected override void OnTransform(Matrix matrix) { var scale = Vector3D.One; (Matrix * matrix).GetExtrudedInsertion(out m_Position, out scale, out m_Normal, out m_Angle); } protected override void OnSaveToStg(StgNode node) { m_Position.SaveToStg(node.AddNode("Position")); m_Normal.SaveToStg(node.AddNode("Normal"), Vector3D.UnitZ); 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_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(node.GetInt32("PlanSign")); } } public double? Fire(Ray3D ray) { if (IsInvalid) { Regen(EventArgs.Empty); } var cache = GetCache(); 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; } public GeometryModelsCache GetCache() { var builder = new GeometryModelsCacheBuilder(Position); var cache = builder.Create(Element, Matrix); return cache; } private void ExtractEntities(IList list, Matrix matrix, GeometryModel3D model, ImElement element) { Vector3D position, scale, normal; double angle; matrix.GetExtrudedInsertion(out position, out scale, out normal, out angle); var e = new CustomDwgEntity(); e.CopyProperties(this); e.Position = position; e.Normal = normal; e.Angle = angle; e.Element = element; e.m_Plan = null; list.Add(e); } } ==== Контроллер пользовательского примитива. ==== Контроллер отвечает за отображение примитива на плане, а также на 3D-виде, если это необходимо. Создадим класс контроллера. Класс должен наследовать абстрактный тип **DwgEntityController**. Перекроем необходимые методы и свойства. Если требуется отображение на 3D-виде, то дополнительно нужно перекрыть свойство **SupportPaint3d**, а также методы **OnPaintEntity3d()** для отрисовки и **Fire()** для выделения 3D-модели в указанной точке. Добавьте в программу новый класс со следующим содержанием: 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 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 (args.Pen.DeviceContext.gContains(e.Bounds3d)) { var cache = e.GetCache(); 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-тип унаследован от "SmdxElement". [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(); } } } {{ :developers:tutorial:custom_entity:tut_custom_entity_1.png?nolink |}} Теперь необходимо сформировать наш 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», с подпунктами, которые будут работать в соответствии с описанными выше алгоритмами. [[developers:tutorial:tutorialcode|Исходный код]] примера расположен в проекте **"TutorialCustomDwgEntity"**.