====== Создание пользовательского примитива ======
Программный комплекс Топоматик Робур позволяет разрабатывать пользовательские примитивы. **Примитив** - это элемент модели чертежа. Каждый примитив умеет отобразить себя в чертеже, и при необходимости в окне 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"**.