Генерация 3д объекта - как правило, многоэтапный процесс (например в булевых операциях сначала поиск графа пересечений, нахождение геометрии кривых пересечения и построение топологии результирующего тела). Закономерно возникает сложность с его отладкой. Положим при генерации что-то пошло не так и имеем наполовину готовый объект, который не может быть визуализирован разрабатываемой CAD системой. Что делать? Как локализовать место и момент ошибки? Анализировать глазами тысячи xyz координат промежуточных результатов и вспомогательных объектов на момент выдачи исключения? Или хуже, если отклонения желаемого результата от фактического незначительные, тогда и все числа внешне будут корректны. Работая С++ программистом в области 3Д моделирования и построения различных CAD/САПР систем, я регулярно сталкивался с проблемой визуализации вспомогательных/промежуточных сущностей.               

Сформировал себе универсальный инструментарий DumpSTL, позволяющий с минимальными усилиями, в любом C++ проекте дампить в .stl файлы любые внутренние объекты в проекте.

Почему именно .stl? Так уж исторически сложилось. Много использовал чпу фрезера и 3д принтера, где основным и простейшим форматом моделей является .stl.

Суть использования сводится к однократной адаптации инструмента под структуры данных конкретного проекта, затем:

1) подключить один DumpSTL.h;
2) вызвать к необходимым данным метод DUMP::save(...);
3) получить на выходе множество файлов с 3д моделями, которые можно открыть в любом 3д редакторе.

Примеры использования

Пример 1: По ходу генерации 3д модели музыкального инструмента - флейты Пана  

3д модель панфлейты
3д модель панфлейты

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

Визуализация вспомогательных поверхностей
Визуализация вспомогательных поверхностей

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

Использование сплайновых кривых
Использование сплайновых кривых

Получаемые при этом графики функций сплайнов в плоскости XY:

Кривые - сплайны в плоскости XY
Кривые - сплайны в плоскости XY

Пример 3: Обозначение ориентации кривых в пространстве, что бы понять где начало, а где конец.

Отображение ориентации UV поверхности
Отображение ориентации UV поверхности

Адаптация под проект / геометрическое ядро

Покажу на примере библиотеки MeshLib.

1) Указать путь к папке, куда будут дампиться .stl файлы в глобальной переменной.std::string_view folderSTL = "C:\\Repos\\STL\\"

2) Подключить include в DumpSTL.h с необходимыми структурами, в данном примере это положим MR::Mesh (полная сетка 3д тело), MR::FaceBitSet (подмножество треугольников сетки).

3) Написать функции преобразования в DUMP::Model3D для каждого такого пользовательского типа (аргумент может быть не один):
    а) Model3D convert(const MR::Mesh&, const MR::FaceBitSet& );
    б) Model3D convert(const MR::Mesh& );

4) По необходимости написать преобразования пользовательских типов в множество точек const std::vector<Point3f>& points, которые могут быть использованы для дампа с особыми модификациями:
    a) Model3D direction(const std::vector<Point3f>& points) – порождает по упорядоченному множеству точек ориентированную цепочку конусов (пирамидок);
    б) Model3D line(const std::vector<Point3f>& points) – порождает по упорядоченному множеству точек отрезки;
    в) Model3D sphere(const std::vector<Point3f>& points) – порождает по множеству точек низкополигональные сферы.

Пример реализации convert методов

DUMP::Point3f convert( const MR::Vector3f& point )
{
    return { point.x, point.y, point.z };
}

DUMP::Model3D convert( const MR::Mesh& mesh, const MR::FaceBitSet& faces)
{
    DUMP::Model3D res;
    for ( auto f : faces )
    {
        MR::Vector3f a, b, c;
        if ( mesh.topology.hasFace( f ) )
        {
            mesh.getTriPoints( f, a, b, c );
            res.addTriangle( convert(a), convert(b), convert(c));
        }
    }
    return res;
}

DUMP::Model3D convert( const MR::Mesh& mesh )
{
    return convert( mesh, mesh.topology.getValidFaces() );
}

Как пользоваться

Применять методы ниже к стуктурам данным проекта и получать генерируемые 3д модели:

1) void save(std::string_view fileName, Args... args) – сохраняем один файл, где fileName– только название файла (нужная папка в folderSTL), args– передаем напрямую нужные нам объектыsave(“mesh”, my_mesh), save(“mesh_part”, my_mesh, my_faceBitSet)

2) void saveInc(std::string_view fileName, Args... args) – то же самое что и save, с той лишь разницей, что сохраняет всегда в новый файл. Если файл с указанным именем существует, приписывает цифру-счетчик еще не существующего файла. Так например если процесс итерационный, и на 135 итерации все взрывается, то есть возможность увидеть эволюцию 3д объекта во всех 135 итерациях, и увидеть, что было непосредственно перед падением приложения.

3) Использовать модификаторы direction/line/sphere: save(“mesh”, direction(mesh)), save(“mesh_part”, direction(my_mesh, my_faceBitSet))

4) Создовать напрямую объекты Model3D, используя напрямую его API addTriangle/addPoint/addEdge/addCone/addQuad/addSphere и дампить его так же save(“model3D”, my_Model3D)

Hello world

Пример использования и соответственно запускаемый "hello world" можно найти в соседнем проекте в репозитории example. При запуске из коробки должны получаться следующие примитивы:

Кубик, тетраэдр, немного конусов, сферы...
Кубик, тетраэдр, немного конусов, сферы...

И вот такой набор файлов с 3д моделями: dumpStlExample_cube.stl, dumpStlExample_directionChain_0.stl, dumpStlExample_directionChain_1.stl, dumpStlExample_directionChain_2.stl, dumpStlExample_lineChain.stl, dumpStlExample_points.stl, dumpStlExample_spheres.stl, dumpStlExample_tetraedr.stl

Как устроена библиотека

1) Представляет из себя один подключаемый header only DumpSTL.h репозиторий
    а) В нем описаны тривиальные структуры Point3, Triangle, Model3D с сопутствующими методами
    б) Функции для пользователя save, saveInc (incremental)
    в) Модификаторы: direction, sphere, line

2) Проект под студию с примером использования и известным ожидаемым результатом example.sln

3) Скрипт clear_stl.bat для автоматической чистки папки от накопившихся .stl файлов (их могут быть сотни)

4) Скрипт run_blender.bat + blenderScrypt.py для автоматического открытия в 3д редакторе blender всех файлов в папке по маске *.stl и фотографирования каждого с 4ех ракурсов и сохранением фото в эту же папку с припиской _left.jpg, _right.jpg, _top.jpg, _bottom.jpg. Может быть полезно при полуавтоматическом прогоне тестов и относительно быстрой проверки глазами, что ничего не попортилось путем быстрого пролистывания скриншотов.
               

Комментарии (7)


  1. starfair
    17.08.2022 16:46

    Хорошая идея! Жаль нет кармы, плюс вам поставить за материал!
    А скажите, не встречался ли вам по пути, какой нибудь достаточно легкий по объёму инструмент (библиотека или SDK) на С++ который бы поддерживал 2 для меня крайне важных момента:
    1) кривые как сочетание прямых отрезков так и сплайнов;
    2) основные булевые операции над пересечениями таких кривых с генерацией новых или коррекцией исходных

    Всё, что я ни находил, либо жутко громозкое, либо работает только с полилиниями состоящими из прямых отрезков (или с большим набором мелких спрямленных сегментов вместо исходной кривой секции). А хотелось бы что-то , что позволяло бы делать базовые операции в легкой 2D CAD системе без сильного усложнения кода.


    1. ArraLazur Автор
      17.08.2022 17:07

      Спасибо за отзыв, к сожалению мне таких тоже не известно, да и не искал, не сталкивался с такими требованиями


      1. starfair
        18.08.2022 09:27

        Ну, в любом случае - спасибо вам за статью и конечно за библиотеку! Надуюсь, дойдут и у меня руки до создания того, что запланировал.


  1. Xadok
    17.08.2022 20:55
    +1

    Интересно что подобное решение есть для буста, только в ином виде github.com/awulkiew/graphical-debugging


  1. Chuvi
    18.08.2022 08:55
    +1

    Спасибо, как раз искал что-то подобное.

    С вашего позволения, чуть попридираюсь к коду

    1. Использование std::string_view без соответствующего #include. У вас, видимо, string_view заинклудился каким-то из имеюшихся инклудов. На других платформах такого может не случиться.

    2. У пользователя может не быть директории "C:\Repos\STL\", как и диска "C:\". Вдруг у него не windows? Значит пользователю придётся лезть в код библиотеки и править этот путь, что нехорошо. (Может есть смысл сделать эту переменную не constexpr?)

    3. Почему 21? Почему 84? Что это за числа? Подозреваю, что здесь имелось ввиду file.write((char*)dummy, sizeof(dummy));

    4. Там же дальше file.write((char*)&(triangles[0]), static_cast<std::streamsize(triangles.size()) * 50);

      50 это что? sizeof(Triangle)?


    1. ArraLazur Автор
      18.08.2022 11:10
      +1

      2) Пользователю данного инструмента требуется указать свою папку, где он хочет получать дампы + еще и написать набор convert методов. Т.е. предполагается, что в любом случае придется лезть и дописывать код библиотеки под себя и под свои проекты
      3,4) https://en.wikipedia.org/wiki/STL_(file_format) 80+4 байта заголовок, 50 байт каждый треугольник, 21 int просто что бы добрать первые 84 байта. Согласен, числа магические, но по сути определяются форматом и не изменяемые и для пользователя значения не имеют


      1. Chuvi
        18.08.2022 12:19
        +3

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

        В целом, после некоторого колдовства я собрал её под linux. Кстати, file.open не должен принимать string_view в качестве аргумента.

        Ловите пулл-реквест, я там заодно поддержку cmake добавил. https://github.com/KupchishinAB/DumpSTL/pull/3