В производстве все больше используется оборудование с числовым программным управлением (программным обеспечением). И все большую роль приобретают 3D-принтеры и 3D-сканеры (оптические, лазерные). На вход в 3D-принтеры подаются модели формата *.stl – STL модель, на выходе из 3D-сканеров создаётся модель реального объекта в формате *.stl. Модель формата *.stl представляет собой пространственную триангуляционную сетку, поверхность из множества треугольников. Для анализа геометрии деталей по STL и построении из STL твердотельных компьютерных моделей необходимо разбить единую триангуляционную поверхность на отдельные грани. В  программных продуктах для работы с STL, таких как Geomatix Design X, Wrap, NX и др., функционал обязательно включает сегментацию STL  модели на отдельные грани. На рисунке 1 приведён пример сегментации STL  модели секции лопаток соплового аппарата (часть газовой турбины).  В свободно распространяемом ПО, однако, инструменты для сегментации зачастую отсутствуют. В данной статье хочу рассказать о реализованном мной на Python алгоритме разбиения STL на отдельные грани.     Для триангуляционной сетки введём следующие обозначения матриц, характеризующих её: Vg×3 – матрица координат вершин сетки stl-модели, Fm×3 – матрица сочетаний вершин в фасеты (состоит из трех столбцов Fa, Fb, Fc), Nm×3 – матрица координат нормалей фасет. Каждая точка регулярной поверхности в трёхмерном евклидовом пространстве характеризуется двумя экстремальными значениями кривизн kmin и kmax, называемых главными [1]. Вычисление главных кризн основано на вычислении производных, и для поверхности STL, которая является кусочно-линейной, вычисление главных кривизн является нетривиальной задачей. Для корректного вычисления кривизн еще в начале текущего века были разработаны различные подходы [2, 3, 4]. В моей реализации я опирался на работу [3] для вычисления кривизн. В алгоритмах сегментации используются значениями кривизн и направлениями нормалей для разделения граней, реализованный мною алгоритм не является исключением.  Блок-схема алгоритма сегментации stl-моделей на ряд областей приведена на рисунке 2.    На первом этапе разработанного алгоритма, при вычислении главных кривизн в вершинах stl моделей были использованы результаты работы [3]. Тензор главных кривизн представляет собой значения самих главных кривизн (собственные числа тензора) и главные направления изменения этих кривизн (собственные векторы тензора). Приведём кратко последовательность вычислений параметров тензора. Сначала вычисляются все вектора граней фасет (запишем их в виде матриц): Вектора граней нормируются. С использованием компонент матрицы Nm×3 для каждой фасеты вычисляются три угла фасеты, видимых из вершины, как арккосинус скалярного произведения: Нормали каждой фасеты вычисляется как векторное произведение двух векторов граней фасеты.  Компоненты нормалей Nx, Ny, Nz в каждой вершине находятся как сумма произведений компонент нормали фасеты и соответствующего угла. Компоненты нормируются.  На следующем этапе рассчитываются компоненты матриц повората для нормали – Mr3×3 , так чтобы она имела компоненты [-1, 0, 0]. Кроме того, рассчитываются компоненты обратной матрицы  для возврата в исходную систему координат. Для каждой вершины находится вектор порядковых номеров соседних вершин первого и второго уровней – формируется матрица координат этих вершин Vn×3. С вершинами первого уровня она связана ребрами, второй уровень образован соседями вершин первого уровня. Для поиска вершин используется матрица Fm×3.     Вершины в окрестности поворачиваются, то есть производится умножение матриц Vn×3 и Mr3×3. Для повернутых точек подбираются коэффициенты полинома второй степени:    , где – координаты вершин окрестности. Формируется матрица:     . Далее вычисляют собственно собственные числа и (главные кривизны) и собственные векторы    и тензора главных кривизн. , , где . ,  ортогонален . и   нормируются. На втором этапе алгоритма выполнялась кластеризация вершин сетки по значениям главных кривизн  и . Алгоритм кластеризации вершин основан на методе машинного обучении без учителя – методе k-средних. Алгоритм k-средних выполняет поиск заранее заданного количества кластеров в немаркированном многомерном наборе данных [5]. Результаты кластеризации достигаются итерационно, основываясь на двух допущениях: 1) «центр кластера» — арифметическое среднее всех точек, относящихся к этому кластеру 2) каждая точка ближе к центру своего кластера, чем к центрам других кластеров. В рассматриваемом объекте (сочетание четырёх поверхностей) количество кластеров явно следует задать равным четырём. На третьем этапе алгоритма классифицируются сами фасеты. Классификация фасет происходит уже по сочетанию главных кривизн в вершинах фасета. Вначале данной процедуры выявляются все фасеты, у которых вершины принадлежат одному кластеру и не превышают поставленный допуск на кривизну, задаваемый в начале алгоритма. Такие фасеты являются полностью определенными (относящимися к определенному кластеру) [6]. Параметр кривизны каждой полностью определенной фасеты рассчитывается по формуле: где , – кривизны фасеты; ,  – главные кривизны i-й вершины фасеты.  Данные по допуску на кривизну  для каждого конкретного объекта и по количеству кластеров для предыдущего этапа можно получать автоматизировано по результатам классификации детали, или задать вручную.          Кроме рассмотренного ранее случая, существуют еще четыре типа фасет с точки зрения классификации по кривизнам в вершинах. Будем называть вершину «острой», если значение одной из двух главных кривизн превышает заданный допуск на кривизну всех поверхностей. На рисунке 3 приведено пояснение к оставшимся четырем группам с точки зрения наличия «острых вершин». В первой группе фасет (рисунок 3, а) все три вершины фасеты являются «острыми». В данном случае кривизна фасеты определяется так же по выражению (3). Для разделения фасет по классам производится расчет евклидовых расстояний между вектором кривизн фасеты и векторами центров кластеров: где – вектор главных кривизн i-го кластера.   Выбирается кластер, для которого евклидово расстояние (4) минимально. Кривизна фасеты приравнивается к вектору кривизн кластера, к которому она была определена. Во второй группе фасет (рисунок 3, б) две вершины являются «острыми». Кривизна фасеты приравнивается к кривизне не «острой» вершины, фасета отходит в класс этой вершины. В третьей группе фасет (рисунок 3, в) одна вершина «острая». В этой группе также возможно два варианта: оставшиеся две фасеты принадлежат одному классу; оставшиеся две вершины принадлежат разным классам. В обоих случаях кривизна фасеты определяется по выражению (2), только в вычислении учавствуют не «острые» вершины. Определение класса осуществляется аналогично как в первой группе (рисунок 3, а). В четвертой группе фасет  (рисунок 3, г) нет «острых» вершин, но вершины принадлежат разным классам.  Определение принадлежности к тому или иному классу осуществляется аналогично  первой группе (рисунок 3, а).  Четвертым этапом алгоритма является процесс предварительной сегментации фасет. В нем классифицированные на предыдущем этапе фасеты объединяются (разрастаются) до участков фасет. Объединение осуществляется внутри классов, где выбирается случайным образом начальная фасета. Если по абсолютной величине хотя бы одна из кривизн фасеты превышают некую величину , близкую к 0, то в качестве метрики объединения  выступает вектор кривизн. Если обе близки к 0, то видимо имеет место плоский участок (плоская грань), и в качестве метрики для объединения фасет выступает вектор нормали . Допуск на отклонение нормали в угловом выражении так же задается пользователем. После определения метрики выбираются смежные с выбранным треугольники до тех пор, пока разница между их метриками объединения с целевой метрикой не превысит допуска. Производится поиск всех фасет, у которых разница между их метриками объединения с целевой метрикой вектор нормали не превышает величину допуска (для кривизн рассчитывается эвклидово расстояние, аналогично (4); в случае нормалей используется скалярное произведение векторов). Из множества находящихся в заданном допуске фасет производится выбор фасет, связанных с первой и между собой общими вершинами.  Завершающим этапом является алгоритм окончательной сегментации фасет. Целью этапа является: 1) уменьшение чрезмерной сегментации, возникшей на четвертом этапе; 2) сделать алгоритм сегментации независимым от количества кластеров кривизны после классификации вершин на втором этапе. Таким образом, на завершающем этапе происходит объединение смежных однородных областей фасет, полученных в результате выполнения четвертого этапа. Математика для выполнения этого этапа взята из работы [6].  Эффективность алгоритма зависит от используемой схемы данных, была использовано представление данных по уже сегментированным участком в виде графа смежности областей (RAG), используемая при сегментации изображений [7]. Выявляется область с наибольшей площадью. Затем вычисляется общая граница (граф смежности) с соседними областями и вычисляются расстояния между целевой областью и соседями по формуле:     , где  – расстояние по кривизне меду двумя областями, равное сумме расстояний между векторами кривизн участков и кривизны их границы;   – коэффициент, вычисляемый как отношение минимального из периметров границ двух областей к длине их общей границы; – коэффициент ускорения поглощения очень малых областей, основанный на использовании величин площадей поверхности областей ,  и допуска :    Расстояние по кривизне сравнивается с допуском на объединение областей, который равен допуска , умноженному на поправочный коэффициент. Если расстояние меньше допуска, то области объединяются. Алгоритм проходит последовательно по всем областям, от наибольшего к наименьшему, постепенно объединяя однородные участки. Формируется структура сегментированных областей Fc (величина совпадает с матрицей фасет тела Fm×3).    Я реализовывал алгоритм сегментации на Python версии 3.8.8. Кроме того, для выполнения ряда операций и визуализаций решения понадобятся следующие библиотеки: trimesh версии 3.19.4; vedo версии 2023.4.3; numpy версии 1.22.4 для выполнения матричных операций; matplotlib 3.3.0 для визуализации; scipy 1.6.2 для сохранения каждого из пяти этапов в файлы формата *.mat; scikit-learn версии 0.24.2 для выполнения кластеризации. Полный исходный код находится в репозитории https://github.com/vadimpechenin/segmentation_method_of_stl_objects_2020.git. Для хранения всей необходимой информации о сетке STL и параметров, хранящих информацию о сегментации, был создан класс Mesh_class. Для хранения настроек алгоритма был создан класс Main_variables. В классе храняться значения переменный pl_zagr, отвечающая за выполнение или загрузку каждого из этапов; переменной pl, отвечающей за визуализацию результатов каждого из этапов; переменной curvature_tolerance для хранения величины допуска на кривизну и переменной angle_tolerance, для хранения величины допуска на угол(при работе с разбиением плоских граней). Сначала осуществляется импорт матриц, характеризующих STL. Выполняется это в классе Import_stl_data, с помощью единственного его метода import_data. Для импорта используется метод load библиотеки trimesh. В Mesh_class заполняются поля  num_vertices, num_faces, vertices, faces , normal.   Структуры struct_seg, num_segments и color_segmetns на этом этапе заданы в виде единичных матриц из одного элемента. Матрица surface_seg задана в виде двумерного списка, в единственном элементе которого помещается значение структуры faces. В качестве примера работы алгоритма используем сетку, состоящую из 5232 вершин и 10460 граней. Код для визуализации приведен ниже (используется библиотека trimesh).  Результат визуализации приведен на рисунке 4.  Объект состоит из трех сфер разного диаметра, между двумя из них выполнено скругление места сопряжения. С использованием методов библиотеки scipy.io – savemat и loadmat результаты работы каждого из этапов сохраняются в виде словарей в файлы *.mat, удобные для хранения матриц чисел.    После загрузки и сохранения структуры сетки можно приступать собственно к этапам сегментации. Ниже приведён код для вычисления главных кривизн.  На рисунке 5 приведена визуализация значений в вершинах рассматриваемой сетки.    Второй и третий этапы алгоритма объединены в одной функции. Кластеризация вершин по двум значениям кривизн выполняется с использованием класса KMeans библиотеки scikit-learn.  Результат кластеризации приведен на рисунке 6. Этап предварительной сегментации выполняется с помощью единственного метода класса Pre_segmentation_faces.     На рисунке 7 приведены результаты четвертого этапа алгоритма. В результате предварительной сегментации остается много неверно сегментированных фасет на границах основных поверхностей. Уменьшить их количество позволяет этап окончательной сегментации, для которого был создан класс Final_segmentation_faces.    Итог работы алгоритма приведен на рисунке 8. В результате все же остаются неверно сегментированные фасеты на стыках граней, особенно их много при нерезком изменении кривизны. В алгоритме настраиваются начальное количество кластеров, допуски на угол и на отклонение нормали. Все они влияют на получаемый результат.  Для ускорения работы алгоритма на больших «боевых» сетках требуется добавить многопоточность в код, что планирую сделать в будущем.  Роджерс, Д. Математические основы машинной графики / Д. Роджерс, Дж. Адамс. – Москва : Мир, 2001. – 604 c. Cohen-Steiner, D. Restricted Delaunay triangulations and normal cycle / D. Cohen-Steiner, J. Morvan // 19th Annu. ACM Sympos. Comput. Geom. – 2003. – P. 312-321. Discrete Differential-Geometry Operators for Triangulated 2-Manifolds / M. Meyer, M. Desbrun, P. Schröder, H. Barr // International Workshop on Visualization and Mathematics, Berlin, Germany. – 2002. – P.35-37. Taubin, G. Estimating the Tensor of Curvature of a Surface from a Polyhedral Approximation / G. Taubin // Fifth International Conference on Computer Vision. – 1995. – P. 902-907. Вандер Плас Дж. Python для сложных задач. Наука о данных и машинное обучение / Плас Дж. Вандер. – Санкт-Петербург : Питер, 2018. – 516 c. Guillaume, L. A new curvature tensor based segmentation method for optimized triangulated CAD / L. Guillaume, D. Florent, Baskurt Atilla // Comput. Aided Des. – 2005. – Vol. 37. – P. 975-987. Schettini, R. A Segmentation Algorithm For Color Images / R. Schettini // Pattern Recognition Letters. – 1993. – Vol. 14. – P. 499-506.