Найти тему
Хроники Георга

Civil 3D & Dynamo. Подходы к оцифровке топографии. Часть 1 - Мультивыноски и выборка данных.

Оглавление

Задачи, связанные с "поднятием" 3D-модели по плоской топооснове типичны для любого Проекта. Как правило, подобные действия выполняются на старте проекта при получении исходных данных по участку проектирования. Если изыскания выполняются по тех-заданию, то к формату итоговых данных можно изначально выдвинуть требования, а вот если топооснова выдается отдельными уполномоченными органами/приобретается у них на коммерческой основе - то, зачастую, формат данных далек от возможности его дальнейшего использования, и требуются значительные трудозатраты на преобразование таких данных.

Оригинал https://t.me/intC3D/22065, взято из genplana.net
Оригинал https://t.me/intC3D/22065, взято из genplana.net

Как правило, подобные данные, в первоисточнике, хранятся в виде некой базы данных/базы векторных слоев по жесткой системе наименования и геометрии. Это может послужить на пользу при поиске отдельных частных случаев при оцифровке подобных данных.

Существенно облегчает задачу предварительная индексация содержимого чертежа. Этот вопрос я специально вынес в отдельную статью и опубликовал как отдельный набор кода - обязательно сперва читайте статью про RTree деревья.
Скриншот из к/ф "Игра в имитацию", где комбинация из трех гарантированно повторяющихся в немецких шифровках слов позволила взломать код Энигмы
Скриншот из к/ф "Игра в имитацию", где комбинация из трех гарантированно повторяющихся в немецких шифровках слов позволила взломать код Энигмы

Мы попробуем коснуться не только вопросов приведения топоосновы в божеский "плоский" вид, но и полуавтоматической оцифровки в 3D (в рамках этой серии статей).

Вопросу оцифровки отдельных слоев данных, к примеру точек поверхности было посвящено видео коллеги из CSD Юрия Сорокина.

Эту часть мы посвятим вопросу оцифровки выносок с текстом, представленных набором отдельных примитивов: двумя отрезками. имитирующими острие стрелки, полку выноски с текстом (Text) и линии-соединитель полки и точки вставки выноски.

Статью разобьем на 2 части - вопросу идентификации геометрии каждой выноски, и генерации Мультивыноски (MLeader) на месте каждого объекта геометрии.

Вот наш типовой "клиент"
Вот наш типовой "клиент"

1. Поиск частных случаев и постановка задачи

Если бы мы говорили про данных из децентрализованной системы (например. отдельные "творения" отдельных лиц и организаций), там как правило превалирует ручная работа и найти что-то одинаковое было бы сложно.

Вообще говоря, общая концепция выглядела бы как набор последовательных процедур:

  • поиск двух пересекающихся отрезков малой длины (острие стрелки) в пределах допуска ~5 мм (поправка на корявость соединения AutoCAD'ом концов линий);
  • поиск третьей линии, входящей в точку пересечения данных отрезков; к слову - эта точка пересечения отрезкой и будет точкой выноски;
  • поиск четвертой линии -- полки выноски, конец которой связан с третьей, а второй конец свободен или имеет связанный со вторым/первым концом Text -- тут ситуация будет требовать скорее всего ручного контроля;
  • индексация всех перечисленных выше объектов в "словарь" с отбором ключевых точек объекта выноски - точка вставки и точка "полки" со значением Text.

В нашем случае ситуация упрощается наличием отдельных частных случаев:

  • острие стрелки - это 2 отрезка фиксированной длины, имеющие одно начало
Первый частный случай - положение острия выноски
Первый частный случай - положение острия выноски
  • расположение текста на полке - строго параллельно ему (угол поворота), также расстояние от полки до точки привязки текста = фиксированному значению (разумеется, значение дробное (скрытое точностью), но одинаковое).
Второй частный случай - позиционирования текста относительно полки выноски
Второй частный случай - позиционирования текста относительно полки выноски

Таким образом, алгоритм будет выглядеть следующим образом:

  1. Оптимизируем содержимое чертежа
  2. Ищем отрезки, имеющие равную (здесь уникальный случай, когда мы можем говорить о равенстве дробных значений, что в обычной практике программирования невозможная вещь) координату в точке начала -- это будет острие выноски. Фиксируем точки острия в отдельный список;
  3. Делаем предварительный выбор окружностью из точки выноски, сортируем прочие объекты кроме данных отрезков с целью установления линии до полки, которая будет лежать между отрезками;
  4. В рамках предыдущего же выбора данных выбираем полку и текст по условию взаимнорасположения текста и полки (читай выше)
  5. Заносим точки геометрии выноски и содержимое текста в словарь
  6. Создаем выноски по всем точкам (операция могла бы быть и в составе п.5; здесь мы лишь делаем "прогиб" в сторону логики Dynamo для препятствования затирки данных).

При этом в качестве методом отбора данных будем пользоваться боевыми сценариями работы с деревьями RTree (ещё раз сошлюсь на свою раннюю статью).

То есть сперва произведем очистку чертежа, а затем - уже будем пользоваться RTree для поиска фрагментов выносок:

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

Такой скрипт "DeleteObjects.dyn" на файле "DemoDrawing.dwg"

https://drive.google.com/open?id=1UiGkELNlnkd6AALQy7LnpSOBVJpDxwsE&authuser=inj5381%40gmail.com&usp=drive_fs

Теперь перейдем к практике в Dynamo (для создания мультивыносок я воспользуюсь методами AutoCAD .NET API, которые подключу через свой пакет Dynamo Civil3D.CustomNodes).

2. О предварительной оптимизации геометрии чертежа и выборки данных

2.1 Теоретическая подготовка

Несмотря на применяемый программный подход, всегда следует максимально упрощать методику решения (формировать алгоритмы) и ограничивать диапазон рабочих данных.

В нашем случае, отрезками представлена практически вся геометрия!

Тип линии представлен отдельными отрезками, а буква в типе линии - также группой отрезков
Тип линии представлен отдельными отрезками, а буква в типе линии - также группой отрезков

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

Заметим, что все "буквы" и прочий мусор имеют фиксированный скелет геометрии (включая и направления сегментов), поэтому наш путь решения задачи -- получить все элементы геометрии (отрезки) внутри и на границе заданного контура (прямоугольник), построенного от вершинах линий.

Логика выборки - выбрать все объекты внутри круга (фактически, квадрата), чьи внешние контуры (они же BoundingBoxes и RTree.Rectangle) полностью попадают или касаются границы.
Логика выборки - выбрать все объекты внутри круга (фактически, квадрата), чьи внешние контуры (они же BoundingBoxes и RTree.Rectangle) полностью попадают или касаются границы.

При этом для отсечения линий коммуникаций (не "буквы"), мы сделаем ограничение на максимальную длину линий.

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

2.2 Логика работы скрипта на удаление мусорных данных

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

  1. Создаются условия для выборки объектов;
  2. Производится выборка объектов с получением их ObjectId;
  3. Производится фильтрация объектов по набору условий
  4. Производится удаление объектов

Процедуры 3 и 4 у меня реализованы в отдельном методе

Предварительная выборка
Предварительная выборка
Результат выборки и создание дерева
Результат выборки и создание дерева
Дополнительная выборка только объектов, удовлетворяющих ограничениям. Заметим что серая группа нодов могла бы быть упрощена флагом true для NeedDeleteObjects
Дополнительная выборка только объектов, удовлетворяющих ограничениям. Заметим что серая группа нодов могла бы быть упрощена флагом true для NeedDeleteObjects
В результате в чертеже все объекты были удалены
В результате в чертеже все объекты были удалены

Теперь, избавившись от мусора перейдем непосредственно к выборе самих структур мультивселенных мультивыносок.

3. Получаем геометрию выносок

3.1 Поиск начала выноски

Тут мне пришлось написать новый метод в классе Selection.GetLinesByLength() возвращающий набор отрезков, удовлетворяющий заданному условию длин.

Здесь мы сузили диапазон объектов отрезков и взяли от них только потенциальные отрезки при вершинах. В качестве проверки найденных значений я построил в точках начал выносок COGO-точки (как элемент отладки)
Здесь мы сузили диапазон объектов отрезков и взяли от них только потенциальные отрезки при вершинах. В качестве проверки найденных значений я построил в точках начал выносок COGO-точки (как элемент отладки)
COGO-точки в местах начала выносок
COGO-точки в местах начала выносок

3.2 Поиск линии-соединителя полки и точки выноски

Теперь необходимо последовательно обработать каждую точку выноски - то есть получить набор отрезков, лежащий рядом с данными стрелками (за исключением, конечно их самих) и проверить каждую из линий на факт прохождения между отрезками -- тогда линия гарантированно будет соединительной между полкой выноски и точкой выноски.

Сперва я немного изменил PythonScript, делающий выборку точек начала, а именно - добавил в него фиксацию для каждой точки вставки выноски коллекцию.

Получилось вот так
Получилось вот так

Теперь применим нод GetObjects_Intersects для поиска отрезков, пересекаемых условной буферной зоной вокруг точки вставки выноски:

Создаем дерево по всем отрезкам чертежа (на данном слое) и получаем из него набор отрезков (ObjectId)
Создаем дерево по всем отрезкам чертежа (на данном слое) и получаем из него набор отрезков (ObjectId)
Сделаем режим отладки - прохождение только по одной такой точке (для ускорения работы скрипта в целом в режиме отладки)
Сделаем режим отладки - прохождение только по одной такой точке (для ускорения работы скрипта в целом в режиме отладки)

Упс-ссс, забыл что это надо делать вот так (я намеренно уходил от прямого использования точек Dynamo):

Отладочная структура для получения ObjectId элементов в границах точки вставки
Отладочная структура для получения ObjectId элементов в границах точки вставки

Далее нам необходимо придумать условие проверки, как будет определяться линия. Я сформулирую его так:

  1. Среди списка значений мы удаляем те, чьи ObjectId = ObjectId от отрезков стрелки.
  2. Для каждой из оставшихся линий находим точку, отстоящую на малое расстояние от начала выноски (порядка 0,01м) и проверяем, входит ли она в треугольник, образованный точкой вставки и крайними точками отрезков-стрелки.
  3. Если проверка выполняется - то цикл останавливается и возвращается ObjectId этой линии и сразу же - конечная координата этой линии, отличная от точки вставки - для запуска следующего метода, ищущего полку и связанный с ней объект Text.

Мне честно сказать было западло писать это через Python Script, и я сделал эту проверку отдельным методом в рамках нового класса Civil3D_CustomNodes.TopoRecognize, а метод называется GetLineIdBetweenLines. Он принимает на вход текущую точку вставки выноски и 2 списка -- с объектами стрелки и прочими линиями, полученными посредством запроса Intersects, а также буфером поиска новой линии - 0.01 (в целом, этого должно хватать всегда).

Вот его результат работы
Вот его результат работы

Далее необходимо найти линию полки и связанный с ней текст.

Полку ищем как ближайшую линию к точке соединения двух линий (при этом используется методика выборки именно Intersects, а не Nearest как вы могли подумать -- чтобы захватить обе линии). Далее для линии считается центр и определяется ближайший/ближайшие объекты типа Text, которые получаются уже запросом типа Nearest из дерева Текста
Полку ищем как ближайшую линию к точке соединения двух линий (при этом используется методика выборки именно Intersects, а не Nearest как вы могли подумать -- чтобы захватить обе линии). Далее для линии считается центр и определяется ближайший/ближайшие объекты типа Text, которые получаются уже запросом типа Nearest из дерева Текста
А вот как я дерево создаю
А вот как я дерево создаю

По окончанию я сохраняю результаты в список:

Сохранение результатов. Нижний список -- это задел для формирования геометрии выносок, а правый - для фильтрации использованных элементов в отдельный слой (для контроля)
Сохранение результатов. Нижний список -- это задел для формирования геометрии выносок, а правый - для фильтрации использованных элементов в отдельный слой (для контроля)

Стоит отметить удобство простановки COGO-точек - по ним (индексам +1) удобно отслеживать неполадки распознавания элементов в коде.

Вот к примеру редкая ситуация (на выборке из 255 объектов это единственный случай), когда между линиями стрелки лежат пару линий. При это пустая вторая непонятно что имеется в виду, то ли это коммуникации, то ли просто палочка ....
Вот к примеру редкая ситуация (на выборке из 255 объектов это единственный случай), когда между линиями стрелки лежат пару линий. При это пустая вторая непонятно что имеется в виду, то ли это коммуникации, то ли просто палочка ....

4. Реализуем класс с выносками

Выноски (они же MLeader) в AutoCAD - это параметрические объекты, имеющие свой отдельный класс в API. В нодпаке Civil3DToolkit есть ряд инструментов по их созданию, но все они опираются на какой-то объект, тогда как у нас есть в наличии только точки.

Методы для работы с MLeader из пакета Civil3DToolkit
Методы для работы с MLeader из пакета Civil3DToolkit

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

Вот такая вот реализация
Вот такая вот реализация
По итогу получается вот такая картинка. Есть косяк с поворотом текста
По итогу получается вот такая картинка. Есть косяк с поворотом текста

Такой скрипт "CreateMLeaders.dyn" на файле "DemoDrawing.dwg"

https://disk.yandex.ru/d/ttWIccmIDvDSqg

Используемые пакеты нодов Civil3D.CustomNodes и DynamoRTree обновил.

5. Небольшое послевкусие послесловие

О чем хочется сказать? Я теперь вплотную уперся в сложность/невозможность как написания, так и отладки скриптов в среде Dynamo с использованием встроенных Python - скриптов -- потому и заменил часть операций выше на отдельные ноды написанные на c#.

Всё-таки когда дело касается обработки неродных объектов для AutoCAD и Dynamo, наступает много сложностей от программной реализации Dynamo (и его конфликтов с классическими функциями языка) до прямой типизации элементов (те же мои метания между DesignScript.Geometry.Point и DatabaseServices.Point3d). Используя динамически-типизированный Python, который гибко связан как с DesignScript, так и с .NET (и COM) библиотеками AutoCAD мы игнорировали части этих моментов, но для статически-типизированного C# так поступать уже не можем.

К чему я веду? К тому, что наверное далее я не буду рассматривать реализацию столь экзотичных действий в среде Dynamo, а буду сразу писать кодом (код, к слову, лежит на GitHub). Борьба с логикой Dynamo порой вообще удручает и заниматься этим дальше желание пропадает напрочь ...

Если в этой части мы рассмотрели ситуацию с мультивыносками, то в следующей соединить отдельные участки линий в единое целое.

Кстати похожие действия как я делал с выносками -- можно сделать для расчлененки блоков - вставить сам блок на месте одного из объектов (с постоянной длиной), а остальные куски удалить вообще.

Не пропускайте публикации, подписывайтесь на Telegram-канал с тизерами статей.

#autocad #dynamo #autodesk dynamo #civil 3d #autocad api #mleader #rtree #топооснова #генплан