Выделение элементов поверхности

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

Программно, поверхность представляет из себя экземпляр класса Surface. Этот класс содержит в себе все элементы поверхности.

Основные элементы класса Surface это:

Surface.Points - массив точек поверхности типа SurfacePointArray

Surface.StructureLines - коллекция структурных линий содержащихся в поверхности типа StructureLines

Surface.Triangles - массив треугольников отписывающих кривизну рельефа типа SurfaceTriangleArray

Surface.Patchs - массив участков типа SurfacePatchArray

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

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

С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки:

Topomatic.Cad.Foundation.dll - базовые математические типы и операции

Topomatic.Cad.View.dll - элемент управления для отображения слоёв моделей

Topomatic.Dwg.dll - примитивы и модель чертежа

Topomatic.Sfc.dll - поверхность

Topomatic.Sfc.Layer.dll - слой поверхности

В качестве основного инструмента получения элементов поверхности рекомендуется пользоваться методами слоя поверхности SurfaceLayer. Для получения текущего SurfaceLayer, необходимо воспользоваться статическим методом SurfaceLayer.GetSurfaceLayer(cadview). В качестве аргумента метод принимает видовой экран CadView, который мы получаем пользуясь свойством CadView нашего модуля.

Множественный выбор

SurfaceLayer предоставляет широкий набор инструментов для выделения элементов поверхности. Ниже описаны методы позволяющие пользователю осуществить выбор:

SurfaceLayer.SelectPoints() - выделение точек поверхности

SurfaceLayer.SelectStructureLines() - выделение структурных линий

SurfaceLayer.SelectPatches() - выделение участков

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

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

var result = surfaceLayer.SelectPoints(i => surfaceLayer.Surface.Points[i].IsSituation, "Выделите ситуационные точки:");

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

Третий представляет из себя массив строк. Аргумент является опциональным и может не использоваться. Он позволяет предоставить пользователю возможность осуществить выбор из предложенных ему вариантов.

Возвращаемое значение GetPointResult указывает на то, какое действие было совершено пользователем. Пользователь может выделить элементы поверхности и подтвердить выбор (Accept), отменить действие (Cancel) или выбрать один из предложенных вариантов контекстного меню (UserCmd).

Значение UserCmd возвращается только в том случае, если в качестве третьего аргумента был передан массив строк и пользователь выбрал одно из предложенных значений. Чтобы понять какой именно пункт был выбран пользователем, следует воспользоваться свойством CadView.LastUserCmd.

Одиночный выбор

Так же SurfaceLayer располагает возможностью выделения одного конкретного объекта:

SurfaceLayer.PickOnePoint() - выделение одной точки поверхности. В качестве аргументов принимаются предикат-фильтр, аналогично описанному ранее, переменная типа int с модификатором out, в которую будет сохранён индекс выбранной точки, сообщение пользователю и массив строк с вариантами пользовательского выбора. Возвращаемое значение GetPointResult, описанное выше.

SurfaceLayer.SelectOneStructureLine() - выделение одной структурной линии. Аргументы предикат-фильтр и сообщение пользователю. Метод возвращает саму структурную линию.

SurfaceLayer.SelectOnePatch() - выбор одного участка. Аргументы предикат-фильтр и сообщение пользователю. Метод возвращает индекс участка из массива участков.

Выделение точек поверхности

В этом примере мы выберем точки поверхности на вкладке «План» и рассчитаем их среднюю отметку. Для этого воспользуемся методом SelectPoints() у слоя текущей поверхности (SurfaceLayer). Так как метод SelectPoints() предоставляет пользователю возможность выбора элементов контекстного меню, то возвращаемое значение будет иметь вид GetPointResult, которое укажет на то, какое именно действие было совершено пользователем.

Для того чтобы получить множество выбранных точек, будет необходимо воспользоваться методом GetSelectedPoints() у того же SurfaceLayer.

В теле программного модуля объявите команду, и декорируйте её атрибутом «cmd». Команда «calculate_average_elevation» предложит пользователю выбрать точки поверхности и посчитает их среднюю отметку.

{ ... [cmd("calculate_average_elevation")] private void CalculateAverageElevation() { //Находим активный SurfaceLayer var cadview = CadView; var actSfcLayer = SurfaceLayer.GetSurfaceLayer(cadview); if (actSfcLayer == null) return; //Выбираем точки поверхности if (actSfcLayer.SelectedPointsCount == 0) { var res = actSfcLayer.SelectPoints(null, "Выберите точки поверхности:"); if (res == GetPointResult.Cancel) return; } if (actSfcLayer.SelectedPointsCount == 0) return; //Обращаемся к выбранным точкам и получаем сумму отметок var sfc = actSfcLayer.Surface; var sum = 0.0; foreach (var selectedPoint in actSfcLayer.GetSelectedPoints()) { var point = sfc.Points[selectedPoint]; sum += point.Vertex.Elevation; } //Вычисляем среднюю отметку и отображаем её в диалоговом окне var average = sum / actSfcLayer.SelectedPointsCount; MessageDlg.Show($"Средняя отметка: {average}"); } ... }

Выделение структурных линий

В данном примере мы рассчитаем максимальный уклон среди всех сегментов структурной линии. Как и в предыдущем примере, для выбора структурной линии нам потребуется слой текущей поверхности (SurfaceLayer). Чтобы выбрать одну структурную линию, воспользуемся методом SelectOneStructureLine(). Здесь возвращаемым значением сразу же будет выбранная структурная линия, поэтому никаких дополнительных действий выполнять не придется.

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

В теле программного модуля объявите команду, и декорируйте её атрибутом «cmd». Команда «define_steepest_grade» предложит пользователю выбрать структурную линию и определит максимальный уклон среди всех её сегментов.

{ ... [cmd("define_steepest_grade")] private void DefineSteepestGrade() { //Находим активный SurfaceLayer var cadview = CadView; var actSfcLayer = SurfaceLayer.GetSurfaceLayer(cadview); if (actSfcLayer == null) return; //Выбираем структурную линию var strLine = actSfcLayer.SelectOneStructureLine(null, "Выберите структурную линию:"); if (strLine == null) return; //Обращаемся к узлам структурной линии, находим соответствующие им точки поверхности //и высчитываем уклоны сегментов по пути определяя максимальный уклон var sfc = actSfcLayer.Surface; var maxIncline = 0.0; for (var i = 0; i < strLine.Count - 1; i++) { var stPoint = sfc.Points[strLine[i].Index]; var endPoint = sfc.Points[strLine[i + 1].Index]; var delta = Math.Abs(stPoint.Vertex.Elevation - endPoint.Vertex.Elevation); var stPos = stPoint.Vertex.Pos; var endPos = endPoint.Vertex.Pos; var length = (endPos - stPos).Length; if (ValueConverter.CompValues(length, 0.0) != 0) { var incline = delta / length; if (incline > maxIncline) maxIncline = incline; } } MessageDlg.Show($"Максимальный уклон: {maxIncline}"); } ... }

Выделение горизонталей

По умолчанию, в программном комплексе Топоматик Робур, выделение горизонталей отключено. Чтобы включить выделение горизонталей нужно изменить значение статического свойства SurfaceLayer.SurfaceSelectionSet.IsHorizontalSelectable на true. Взаимодействовать с объектами горизонталей напрямую нельзя, но горизонтали реализуют интерфейс ILinearObject, поэтому мы можем получить трёхмерную полилинию описывающую положение горизонтали, воспользовавшись методом GetPolyline(). Для получения площади трёхмерной полилинии воспользуемся методом расширения PolylineExtentions.GetArea2D(IPolyline3D)

В теле программного модуля объявите команду, и декорируйте её атрибутом «cmd». Команда «get_horizontal_line_area» предложит пользователю выбрать замкнутую горизонталь и определит её площадь.

... [cmd("get_horizontal_line_area")] private void GetHorizontalLineArea() { //Находим активный SurfaceLayer var cadview = CadView; var actSfcLayer = SurfaceLayer.GetSurfaceLayer(cadview); if (actSfcLayer == null) return; //Включаем возможность выбор горизонталей var initSelectState = SurfaceLayer.SurfaceSelectionSet.IsHorizontalSelectable; SurfaceLayer.SurfaceSelectionSet.IsHorizontalSelectable = true; //Получаем указатель на текущий набор объектов var ss = actSfcLayer.SelectionSet; //Получаем указатель на слой горизонталей var layer = actSfcLayer.Surface.Style.HorizontalsStyle.GetLayer(); //Выбираем горизонталь на плане var linear_object = ss.PickOneObjectAtScreen(obj => { if (obj is SurfaceObjectWrapper layered_object) { return layered_object.LayerID == layer.ObjectID; } return false; }, "Выберите замкнутую горизонталь") as ILinearObject; //Восстанавливаем состояние флага возможности выбора горизонталей SurfaceLayer.SurfaceSelectionSet.IsHorizontalSelectable = initSelectState; if (linear_object == null) return; //Извлекаем полилинию из горизонтали и получаем её площадь var poly = new Polyline3D(); linear_object.GetPolyline(poly); //Проверяем замкнута ли горизонталь var startVec = poly[0]; var endVec = poly[poly.Count - 1]; if (startVec.EqualEps(endVec)) { var area = poly.GetArea2D(); MessageDlg.Show($"Площадь горизонтали: {area}"); } else MessageDlg.Show($"Горизонталь не замкнута."); } ...

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

{ "assemblies": { "TutorialSelectSurfaceElements": { "assembly": "TutorialSelectSurfaceElements.dll, TutorialSelectSurfaceElements.ModulePluginHost" } }, "actions": { "id_calculate_average_elevation": { "cmd": "calculate_average_elevation", "title": "Рассчитать среднюю отметку" }, "id_define_steepest_grade": { "cmd": "define_steepest_grade", "title": "Рассчитать наибольший уклон" }, "id_get_horizontal_line_area": { "cmd": "get_horizontal_line_area", "title": "Получить площадь горизонтали" } }, "menubars": { "rbproj": { "items": [ { "id": "tutorial_menu", "title": "Tutorial", "items": [ "id_calculate_average_elevation", "id_define_steepest_grade", "id_get_horizontal_line_area" ] } ] } } }

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