Делаю светильники из картинок: локальная нейросеть вместо платных 3D-сервисов

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Я давно хотел печатать на 3D-принтере то, что придумал сам. Скачать готовую модель и отправить её в печать — дело пяти минут, но такая же вещь есть у каждого, кто её скачал. Хотелось своего, уникального: такого, что можно только вообразить.

Нейросети, которые превращают картинку в 3D-модель, наконец сделали это реальным. Онлайн-сервисы берут за это деньги: у Tripo тариф Pro стоит $20 в месяц примерно за 200 моделей, у Meshy — около $20 за 1000 кредитов, это до 100 моделей. Бесплатных тарифов хватает на несколько попыток в достаточно низком качестве. Я пошёл другим путём: TRELLIS.2 от Microsoft бесплатно работает на моём ПК с RTX 5060 Ti 16 ГБ, и попыток можно делать сколько угодно.

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

Орочий череп светится изнутри. Пока он стоит на подставке от планеты: своего провода у него ещё нет.
Орочий череп светится изнутри. Пока он стоит на подставке от планеты: своего провода у него ещё нет.
Планета на подставке из корней. Корни тоже пластиковые. Вживую рисунок виден лучше: камера пересвечивает яркие участки.
Планета на подставке из корней. Корни тоже пластиковые. Вживую рисунок виден лучше: камера пересвечивает яркие участки.

Скрипты, пример карты и инструкция — на GitHub, готовые STL — в релизе. Можно взять детали как есть или повторить процесс со своим рисунком.

1. Череп: нейросеть дала форму, обработка чуть не стёрла лицо

Начну с черепа: на нём лучше всего видно, что сырой результат нейросети — ещё не модель для печати. В режиме 1536_cascade TRELLIS.2 не уместился в 16 ГБ видеопамяти на декодировании. В 1024_cascade без текстур прогон занял 181 секунду с пиком 12 ГБ, на выходе — около 24 млн треугольников.

Напечатанный череп без подсветки. Эти глазницы и клыки обработка и должна была сохранить.
Напечатанный череп без подсветки. Эти глазницы и клыки обработка и должна была сохранить.

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

В build_preserved_shell.py вокселизуется только поверхность: вокруг неё наращивается узкая полоса материала, и из неё заново извлекается сетка. Внутренний объём не заливается, поэтому глазницы и височные окна остаются открытыми. Мелкие щели при этом могут закрыться, так что радиус утолщения подбирается под размер деталей.

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

Потом всплыли две ошибки, которых не видно на красивом рендере. После утолщения часть оболочки ушла ниже нуля, и при переносе модели на стол опорное кольцо поднялось примерно на 1,2 мм — принтер начинал бы печать не с него. Помогла обрезка по плоскости стола до финального выравнивания. Вторая ошибка: короткое отверстие под лампу упиралось внутри черепа в плоский потолок. Я продолжил полость выше и закрыл её сужающимся сводом.

Разрез: опорное кольцо стоит на плоскости стола, полость закрывается наклонным сводом. Рендер.
Разрез: опорное кольцо стоит на плоскости стола, полость закрывается наклонным сводом. Рендер.

С поддержками не помогло просто сделать стойки толще: некоторые участки всё равно начинались в воздухе. Тогда скрипт стал проходить модель снизу вверх, искать участки, которым не на что опереться на предыдущем слое, и отмечать длинные мосты. Так нашлось 12 зон, которым добавили опоры.

Скрипт черепа заточен под эту модель: координаты опоры и полости привязаны к нему. В репозитории он лежит как пример метода — для другой модели их придётся пересчитать. Своего провода у черепа пока нет, поэтому светится он на подставке от планеты. О ней дальше.

2. Планета: сначала размеры, потом рисунок

Планету я строил с другого конца — от покупных деталей. Светильник состоит из плафона, подставки и обычного узла с лампой, патроном и проводом. E27 — это резьба лампы, наружный диаметр корпуса патрона из неё не узнать. А колба должна пройти в нижнее отверстие плафона: в 58,4 мм лампа диаметром 60 мм не войдёт.

Деталь или сопряжение

Размер в этой сборке

Зачем его знать

Планета

Диаметр 140 мм

Задаёт свободное место между корнями

Нижнее отверстие цельной планеты

58,4 мм

Через него проходят лампа и центрирующий пояс

Опорное кольцо планеты

Наружный диаметр 67,6 мм, высота 4 мм

Стоит на столе при печати, затем опирается на подставку

Полка и центрирующий пояс подставки

68 и около 57,6 мм

Полка несёт вес, пояс удерживает шар от бокового смещения

Корпус патрона

Не шире 46 мм и не длиннее 55 мм

Под него сделано гнездо 46,4 мм

Лампа

LED с колбой около 45 мм

Проходит через отверстие с запасом

Зазор между поясом и отверстием — (58,4 − 57,6) / 2 = 0,4 мм. Реальная печать его съедает, поэтому сначала лучше напечатать короткую посадочную часть planet_base_test_10mm.stl и примерить её, а потом уже весь шар.

3. Как плоская картинка становится светящимся шаром

Нейросеть здесь не нужна. Планета — литофания: рисунок появляется за счёт толщины пластика, тонкое светится ярче, толстое — темнее. Внешняя поверхность остаётся гладкой сферой, а рисунок меняет внутреннюю. На вход скрипту подаётся плоская развёртка 2:1: горизонталь становится долготой, вертикаль — широтой. Левый и правый края встретятся на шаре, поэтому разрыв между ними превратится в заметный меридиан.

Пример карты из репозитория: белое станет тонким и ярким, чёрное — толстым и тёмным.
Пример карты из репозитория: белое станет тонким и ярким, чёрное — толстым и тёмным.

После перевода карты в серый и нормализации яркости толщина считается одной строкой. У меня минимум — 0,8 мм, максимум — 3 мм:

thickness = 0.8 + (1.0 - brightness) * (3.0 - 0.8)
# brightness: 0 — чёрное, 1 — белое

Для каждой точки сферы скрипт строит наружную и внутреннюю вершины, соединяет соседние точки треугольниками и замыкает нижний край. Возле основания переход устроен отдельно: узкая нижняя кромка должна устойчиво печататься. Всё это видно в make_clean_whole_lithophane.py.

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

Чтобы повторить пример в Windows с Python 3.12, скачайте репозиторий и выполните в его папке:

python -m venv .venv
.venv\Scripts\python.exe -m pip install -r requirements.txt
.venv\Scripts\python.exe tools\rebuild_example.py planet

В build появятся planet.stl, отчёт planet_metrics.json и расчётная карта светопропускания. Повторный запуск собирает тот же STL, что у напечатанной планеты, байт в байт.

Для своего рисунка достаточно передать карту через --lightmap: на входе развёртка 2:1, на выходе сферический плафон. Для первой попытки лучше заменить только картинку, оставив проверенные размеры и толщины. Полная команда со всеми параметрами — в README. Форму генератор не меняет: им делаются шары с разными рисунками, череп так не построить.

После экспорта скрипт проверяет замкнутость сетки, число компонентов и 125 сечений нижних 25 мм. Проверка строгая: на подробной карте она может ругаться на внутренние полости, так что счётчик bad_sections — повод открыть сечения и посмотреть. Последнее слово за слайсером: нижние слои, нависания, траектории стенок.

4. Подставка: нейросеть рисует корни, скрипт освобождает место

Для корней порядок обратный: сначала образ, потом механика. Эскиз сделал встроенный генератор картинок Codex; локальная FLUX.1 Krea dev через ComfyUI тоже давала подходящие эскизы на том же ПК. Для 3D важнее читаемая форма, однотонный материал и чистый фон, чем выбор генератора. На слово «подставка» генераторы упорно рисовали глубокую чашу, в которой планета утонула бы наполовину, поэтому я просил открытое место между корнями. Дальше TRELLIS.2 превратил эскиз в объём.

Но фронтальный рисунок ничего не говорит о глубине. В 3D верхушки корней оказались в 30–38 мм от оси при радиусе шара 70 мм: вычитание сферы срезало бы ветви. Я попробовал поднять шар выше — и вместо планеты в корнях получилась планета на трубе.

Отбракованный вариант: корни целы, но вместо планеты в корнях — планета на трубе. Рендер.
Отбракованный вариант: корни целы, но вместо планеты в корнях — планета на трубе. Рендер.

Решение — сначала плавно развести ветви, а потом вычитать место для шара. Основание остаётся на месте; смещение начинается на высоте 45 мм и достигает 30 мм к высоте 125 мм. Для перехода используется smoothstep: при нормированной высоте u коэффициент равен 3u² − 2u³. Поэтому у корней не появляется резкого излома на границе деформации. Центральная область под патрон из этого смещения исключена.

После разведения ветвей шар опускается между ними. Вес несёт полка, корни остаются декором.
После разведения ветвей шар опускается между ними. Вес несёт полка, корни остаются декором.

Дальше важная операция, которую легко пропустить: освободить не только конечное положение шара, но и весь путь установки сверху. Скрипт строит объём, занимаемый движущимся шаром, и вычитает его из корней. Центральный проход внутри нижнего отверстия оставляет — там должен находиться патрон. Если вычесть сплошную сферу без этого исключения, она срежет и допустимую центральную часть.

Для геометрии — Python, NumPy и trimesh, для булевых операций — manifold3d. make_cradle.py получает уже замкнутую декоративную сетку. Чтобы повторить этап без TRELLIS, я положил в архив промежуточную stand_solid_04.stl: распакуйте его в папку reference и выполните:

.venv\Scripts\python.exe tools\rebuild_example.py stand

Пресет рассчитан на прежний шар с отверстием 59,2 мм. Для печати готовой сборки берите stand_print.stl, а под свои размеры пересчитывайте посадку целиком: параметры скрипта описаны в README.

Полку тоже пришлось переделать: плоское кольцо снизу давало нависание. Вместо поддержек я сделал нижнюю поверхность наклонной — полка 68 мм с фаской высотой 8 мм. Скрипт следит, чтобы высота фаски была не меньше радиального вылета.

Напечатанная подставка без плафона: видны патрон, опора и свободное место между корнями.
Напечатанная подставка без плафона: видны патрон, опора и свободное место между корнями.

Ещё одна ловушка — облегчение модели. Два способа уменьшить число треугольников после булевых операций испортили топологию. Печатную версию пришлось собрать из более грубой промежуточной сетки с шагом 0,4 мм и повторить точные операции.

А если вместо корней — другая подставка? Я прогнал тот же make_cradle.py на каменной композиции: код менять не пришлось, только заготовку и параметры посадки. Обработка заняла 14 секунд, проверка — ещё 27. Сетка замкнута, с плафоном не пересекается, Bambu Studio её нарезал; этот вариант я пока не печатал. Заготовка и пошаговая инструкция — в README. На входе нужна уже замкнутая модель: сырой выход нейросети сначала придётся замкнуть.

5. Что проверить в слайсере, прежде чем тратить катушку

Самая неприятная ошибка литофании — разреженное заполнение внутри стенки. На обычной детали решётку не видно, а здесь она светится вместе с рисунком. Поэтому я смотрю на нарезку: в стенке плафона должны быть только периметры и сплошные линии, без Sparse infill.

Параметр

Цельная планета

Тёмная подставка

Сопло

0,4 мм

0,4 мм

Слой

0,10 мм

0,16–0,20 мм

Стенки

8, генератор Arachne

3–4

Заполнение

100%, проверка отсутствия решётки в стенке

20–25%

Внешняя кайма

Проверить по короткому тесту основания

5 мм

Старый 3MF с 0,20 мм, пятью стенками и 15% заполнения я бы без просмотра траекторий не переносил. Профиль материала — под свою катушку. Поддержек в STL нет: нужны ли они, видно по нарезке.

Порядок печати: сначала planet_base_test_10mm.stl тем же профилем, примерка к подставке, потом полный плафон. В полном STL стоит отдельно пройти первые слои, переход воротника в сферу и нависания, а у подставки — канал провода и обратные стороны ветвей.

Собирать при отключённом питании: патрон, лампа, затем шар вертикально на пояс — он должен садиться без продавливания между корнями. Для сетевой части лучше взять готовый исправный узел с проводом: напечатанный канал не заменяет крепление кабеля. Лампа — только LED.

Что получилось и что можно забрать себе

Для повторения основной пример — планета с древесной подставкой, для каменной композиции есть отдельный пресет. Ниже — напечатанная раньше космическая подставка.

Второй вариант подставки без плафона.
Второй вариант подставки без плафона.
Та же сборка с зелёной подсветкой. Цвет лампы сильно меняет впечатление.
Та же сборка с зелёной подсветкой. Цвет лампы сильно меняет впечатление.

Готовая деталь

Примерная масса

Планета

140 г

Череп

157 г

Древесная подставка

Около 120 г

Космическая подставка

Около 85 г

Массы примерные, без брака и поддержек. Всё остальное — в репозитории со скриптами и инструкцией, архиве эталонных деталей и результатах проверок.

Главное, что я вынес из этих светильников: нейросеть хороша там, где нужна фантазия, — корни, череп. Свет лучше строить через толщину стенки, а посадку и сборку задавать размерами и проверять отдельно. Тогда не приходится просить нейросеть одновременно придумать красивые корни, угадать ваш патрон и предсказать, как шар пройдёт между ветвями.

А вы печатаете своё или скачиваете готовое? Если тоже делаете модели из картинок — расскажите в комментариях, чем доводите сырую сетку до печати.

Источник: https://habr.com/ru/articles/1096428/


Интересные статьи

Интересные статьи

Трафик, генерируемый ботами, впервые в истории интернета превысил объём трафика от реальных пользователей. Об этом сообщает сервис Cloudflare Radar.
Когда клиент откладывает покупку, ожидая скидки, важно не потерять его интерес. Уведомления о снижении цен на избранные товары помогают вовремя сообщать пользователям о выгодных предложениях, возвраща...
Когда я пользуюсь сайтом, я хочу, чтобы поиск был максимально простым и удобным. Мы все уже привыкли к тому, как работают умные системы вроде Google или Яндекса, поэтому от любой другой поисковой стро...
И рассказываем, чтобы сэкономить Ваше времяЧтобы перелезть через этот высоченный забор понимания, как же все-таки работает сейчас КЭДО в Битрикс24 мы...• Просмотрели 384 минуты видеоуроков и рол...
Привет, Хабр! Сегодня расскажем про твой день рождения, создание Telegram-ботов, итоги первой недели розыгрыша подарков и лучшие статьи прошлой недели.