Недавно я сделал предложение руки и сердца одному прекрасному человеку с помощью шестигранной зеркальной штуковины, изображенной на фото. Мы оба большие нёрды, и мне хотелось сделать что-нибудь особенное, поэтому я спроектировал и распечатал на 3D-принтере зеркальный массив, чтобы задать заветный вопрос. Зеркала расположены под таким углом, что прямо перед закатом в нашу 8-ю годовщину они отражают свет заходящего солнца на землю, образуя слова «MARRY ME?»

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

Что потребовалось

  • Python 3 и Jupyter со следующими библиотеками: numpy, matplotlib, numpy-stl, hexy, vpython

    • vpython опционален, если вам не важна визуализация процесса;

  • 3D-принтер:

    • Для этого проекта я купил Creality Ender 3 v2 и в целом доволен им, но подойдет любой FDM принтер с точностью 1 мм;

    • Опционально: лак для волос или специальный клей-спрей для 3D-печати (для защиты от деформации);

  • Любой хороший PLA пластик;

  • 1-дюймовые шестигранные зеркальные плитки;

  • Цианакрилатный клей:

    • Чаще всего «суперклей» относится к этому типу. Вы можете применить любой клей для соединения пластика со стеклом, но важно использовать клей, который не расширяется при застывании, иначе это негативно повлияет на результат. Тонкого слоя цианакрилатного клея достаточно, чтобы зеркала держались; слишком много клея может исказить углы.

  • Солнце. 

Вычисление углов зеркал

Основная идея состоит в том, что массив зеркал образует гексагональную сетку. Каждое зеркало имеет барицентр в определенной точке, и мы хотим, чтобы луч солнечного света отражался от него в пиксель в некотором месте на плоскости. Нам известно, как расположено каждое зеркало, где находится желаемый пиксель и источник света. Исходя из этого, мы можем определить положение зеркал в пространстве, чтобы они отражали свет в соответствующие им пиксели.

Рассмотрим одно зеркало и один целевой пиксель. Центр зеркала имеет определенные (x, y,z) координаты, лежащие на некотором векторе mirror_pos, а центр пикселя лежит на векторе target_pos. Давайте определим векторы u и v так, что:

  • u = mirror_pos — target_pos. Это вектор от центра зеркала к центру пикселя;

  • v = (sin Φ, cosΦsinθ, cosΦcosθ). Это вектор от центра зеркала к источнику света — солнцу;

Здесь мы предполагаем, что угол между солнцем и горизонтом — это θ, а азимутальный угол между центром зеркала и солнцем — это Φ;

Поскольку углы отражения такие же, как и углы падения, нормаль зеркала n является биссектрисой этих углов, поэтому нормаль n можно выразить через векторы u и v:

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

Создание 3D-модели

3D-модель — это просто набор вершин и граней. Вершины — это углы многоугольника (список координат (x, y,z)), а грани — это список треугольных фасет, определяемых кортежем из трех вершин. Итак, чтобы создать 3D-модель шестиугольной призмы, необходимо вычислить углы призмы и определить фасеты внешних граней призмы. 

Пластиковая рама зеркальной матрицы, напечатанная на 3D-принтере, состоит из сетки шестиугольных призм. Каждая призма имеет основание (x, y) на плоскости и вектор p, определяемый положением барицентра (7 на рисунке), который перпендикулярен вектору n. Чтобы вычислить углы верхней грани, я пересек призму поверхностью, определяемую векторами p, n. и нормалью нижней грани (правое изображение). Затем я просто встроил в свой код вершины для вычисления граней. 

Ещё одна вещь, которую я решил добавить — это боковые грани для выравнивания зеркал. Они представляют собой небольшие выступы на двух гранях призмы, которые намного упрощают процесс приклеивания зеркал. Единственная загвоздка заключается в том, что координаты этих выступов не коллинеарны координатам верхних ребер призмы: два выступа всегда должны образовывать угол в 120 градусов, но при крутом наклоне верхней грани призмы углы могут изменяться. Поэтому для вычисления координат этих выступов, я использовал проекцию оси x на плоскость верхней грани призмы: x'=y * n. Затем я повернул этот вектор относительно нормали с шагом 26 и получил необходимые углы выступов.

Создание гексагональной сетки

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

Я сгенерировал координаты гексагональной сетки, которые определяют центр каждой колонны, затем я сгенерировал список шестигранных призм с соответствующими параметрами для отражения, и, наконец, объединил список призм в один .stl файл. По эстетическим соображениям, я решил разделить призмы зазором в 2мм, сделав основания немного шире. Пример 3D-модели небольшой сетки с общей точкой фокусировки показан ниже. Структуры для выравнивания выделены красным (обратите внимание на разницы углов призмы и выступов для выравнивания).

Оптимальное сопоставление пикселей с зеркалами

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

Массив зеркал будет проецировать солнечные лучи на поверхности и формировать изображение, и, когда структура будет расположена под правильным углом и на правильном фокусном расстоянии, тогда (в теории) вы получите то изображение, которое хотели. Однако если вы отклонитесь от фокусного расстояния или ориентации, то изображение немного деформируется. Этот эффект можно смягчить, сделав лучи как можно более параллельными.

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

Итак, мы хотим сопоставить зеркала и пиксели таким образом, чтобы лучи были практически параллельны друг другу. Как нам это сделать? Изначально я выбрал «жадный» подход, перебирая зеркала и назначая им пиксель, минимизирующий угол падения (т.е., минимизация скалярного произведения u * n). Однако такой подход работал не очень хорошо, так как учитывал только скалярное произведение, а не направление отраженного света. Вы сможете увидеть искажения изображения на ранних текстовых массивах в следующем разделе, при создании которых использовался описанный подход (хотя отчасти это связано с плохой адгезией между печатной платформой и материалом).

Окончательный алгоритм был вдохновлен самой структурой гексагональной сетки. Гексагональная сетка радиуса R состоит из n шестиугольников:

Так, при R=1 имеем 1 зеркало в центре, на следующих кольцах 6, 12, 18 зеркал и так далее. Чтобы минимизировать пересечение лучей, я вычислил центр масс пикселей, а затем сгруппировал их на основе расстояния до центра масс, где количество пикселей в группе равнялось количеству зеркал в соответствующем кольце. Так, центральный пиксель назначался центральному зеркалу, затем 6 других, ближайших к центру, пикселей сопоставлялись 6 зеркалам во втором кольце и так далее. В каждой группе пиксели сопоставлялись с зеркалами по часовой стрелке, начиная с нижних элементов. Так, внешнее кольцо зеркал соответствовало наиболее отдаленным пикселям, при этом нижняя ячейка соответствовала нижнему пикселю, самая левая ячейка — самому левому пикселю.

По итогу, этот алгоритм сопоставления работал довольно хорошо (и мне очень нравятся алгоритмы сопоставления). Вот сравнение лучей, образующих фразу «MARRY ME?»: слева зеркала назначены случайным образом, а справа с использованием алгоритма сопоставления. Отчетливо видно, что во втором случае лучи более параллельны. Это означает, что такая матрица будет «прощать» неточность в размещении, оставляя изображение разборчивым.

Также, на этом этапе я обнаружил чуть ли не роковую ошибку для всего проекта. В этот момент было потрачено около 20 часов на печать окончательного зеркального массива, и я осознал, что готовый образец будет отражать зеркальную фразу «?EM YRRAM». До этого я не замечал эту проблему, так как тестовое изображение сердца было симметричным и я не понимал, что получаю зеркальное изображение. К счастью, я вовремя обнаружил проблему, т.к. до заветной даты осталась одна неделя и я бы не успел распечатать исправленную модель. Вывод из этого следующий: использование ПО для визуализации очень важно при дебаге.

Печать рамы

Итак, к этому моменту я разработал end-to-end процесс, в котором при введении набора координат, формирующих желаемое изображение, проекционной плоскости и положения солнца, на выходе формируется .stl модель рамы массива зеркал, которую можно напечатать на принтере. Теперь нам просто нужно все распечатать и приклеить зеркала, не так ли?

Я напечатал четыре тестовых массива меньших размеров для проверки перед созданием большой рамы. Каждая рама имела 37 зеркал и проецировала изображение сердца (37 зеркал соответствуют R=3 — наибольшему радиусу, который я мог распечатать одним цельным «куском» с помощью моего Ender3 и его печатной платформы 220 на 220 миллиметров).

При печати этих массивов я столкнулся с двумя проблемами. Первой проблемой была деформация массива из-за неполного прилегания к печатной платформе. Это приводило к тому, что из-за натяжения охлаждающихся слоёв уголки слегка приподнимались, что приводило к изменению углов призм и искажению изображения. Я решил эту проблему, снизив температуру стола и нанеся на него немного лака для волос (первобытные знания о 3D-печати…), что сработало как заклинание. 

Второй проблемой стала дилемма о том, как распечатать большую раму, которая не подходила под объем печати моего принтера. Некоторые программы для 3D-печати включают в себя встроенные функции для разрезания больших моделей на части, но я не мог их использовать, поскольку плоские разрезы проходили бы сквозь шестиугольные призмы, что создало бы «гребень» на верхней поверхности, который изменил бы угол:

Около нескольких десятых градуса
Около нескольких десятых градуса

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

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

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

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

Я закончил монтировать все зеркала в ночь перед предложением, так что, по иронии судьбы, мне не удалось протестировать окончательный результат на закате. К счастью, всё сработало именно так, как я и хотел! И, что ещё немаловажно, темпераментный туман в районе залива Сан-Франциско решил взять выходной.

Это был первый проект, который я сделал при помощи 3D-печати (на самом деле я занялся 3D-печатью исключительно ради этой идеи), а также уникальный опыт воплощения идеи с помощью кода и принципов физики.

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


  1. ivanovdev
    18.11.2021 15:21
    +4

    Как говорится - совет да любовь!


    1. trokhymchuk
      19.11.2021 17:32

      +1.

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


  1. Arty_Fact
    18.11.2021 16:03
    +7

    Все-таки испугались и убрали картинку самого предложения?


    1. ksbes
      18.11.2021 16:29

      Don't ask - don't tell. Идеальный принцип.


    1. staticmain
      18.11.2021 18:18
      +5

      В нашей стране всегда может прибежать кто-то, кого это оскорбит.


      1. Mulin
        18.11.2021 19:16
        -4

        Вы из США или Европы?


        1. staticmain
          18.11.2021 19:17
          +3

          Отвечу вопросом на вопрос — вы видели исходную картинку?


          1. Mulin
            18.11.2021 19:18
            -2

            Из Израиля?


            1. staticmain
              18.11.2021 19:20
              +8

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


      1. Arty_Fact
        19.11.2021 07:25
        +1

        Ага, уже даже в комментариях к статье отметились такие...
        Великолепная статья, а людям не плевать, кто кому там предложение делает...


  1. glooow
    18.11.2021 16:11
    +3

    Это максимально впечатляет! Какие невероятные вещи происходят, когда на дереве науки распускаются цветы романтики!


  1. Uris
    18.11.2021 16:34
    +2

    Это сильно на самом деле и не так уж тривиально, как может показаться на первый взгляд, а для диплома студенту оптику прям было бы зачёт


    1. qbertych
      18.11.2021 16:44
      +6

      Да ладно, автор вообще занимается фотонными нейроускорителями и QPGA в Стэнфорде, параллельно работая в гугле. Для него это скорее развлечение после работы.


  1. drWhy
    18.11.2021 17:14

    Если использовать освещаемый на Хабре проект "Умные пайетки", можно добиться динамического отображения информации.


    1. axe_chita
      18.11.2021 21:21

      Кстати, а это идея. И можно использовать как рекламе, Типа бегущей строки или картинки.


      1. JonikCX
        19.11.2021 09:02
        +4

        А недовольных клиентов прижигать, фокусирую все зеркала в одну точку.


        1. axe_chita
          19.11.2021 21:02
          +1

          Для этого надо организовать трекинг клиентов для точной фокусировки, а еще клиентов придется увещевать через матюгальник «Никуда не двигайтесь! Ваше мнение очень важно для нас!»


  1. GospodinKolhoznik
    18.11.2021 18:24
    +14

    прямо перед закатом в нашу 8-ю годовщину они отражают свет заходящего солнца на землю, образуя слова «MARRY ME?»

    Я и сам как мог всеми силами затягивал с вопросом женитьбы и потому восхищён автором! Затянуть с предложением так, что сделать его только на 8ю годовщину свадьбы, это высший пилотаж!


    1. Alecseyyy
      19.11.2021 08:02
      -11

      Да, что только он не готов сделать для своего парня фуууу


    1. SkyWheel
      20.11.2021 04:54
      +2

      Годовщина может быть не только свадьбы, но и знакомства, начала отношений и т.п. Нигде ну указана именно свадьба.


  1. connected201
    18.11.2021 18:44

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


    1. Delsian
      18.11.2021 21:14
      +1

      Может, быстрее будет перейти на оригинал статьи и там написать автору в личку? Вряд ли он читает этот перевод.


    1. glebiuskv
      18.11.2021 21:35
      +1

      Ссылка на оригинал ведёт на github проекта.


  1. dndzph
    18.11.2021 21:28
    +3

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


  1. Alecseyyy
    18.11.2021 23:56
    -7

    Я надеюсь что предложение делали девушке? А то так размыто написано…
    P.S. фу фу фу там два…


    1. Wesha
      19.11.2021 06:03
      +2

      Я надеюсь что предложение делали девушке?

      И не надейтесь.


  1. Stas911
    19.11.2021 03:40
    +4

    Крутая пельменница! :)


  1. alex_dow
    19.11.2021 08:12

    Отличный задел для создания отражателя на солнечные панели.


  1. Jckill
    19.11.2021 08:12

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


  1. Tiamon
    19.11.2021 14:48

    Здорово! Если добавить динамически изменяемую фокусировку то будет совсем круто. Можно сделать подвес на элетромагнитах в каждой секции или лучше механику?


  1. Alecseyyy
    19.11.2021 16:21
    -6

    Мои коменты заминусовали из-за моего фу в отношении двух гомосексуалистов? Ну ок, я просто думал, что не одному мне стало противно от этого.


    1. mdcool
      19.11.2021 17:22
      +3

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


    1. Semy
      19.11.2021 19:01
      +6

      У вас просто детсадовская реакция. Вы бы лучше статью бы оценили


    1. SkyWheel
      20.11.2021 04:57
      +5

      Скажите, а вы тоже ненавидите песни группы Queen из-за ориентации солиста? Или перестанете пользоваться каким-то отличным продуктом только из-за того, что у его создателя вкусы отличные от ваших?


    1. ChezRD
      20.11.2021 14:56

      Индюк тоже думал...


  1. kirillsunday
    20.11.2021 14:56

    Выглядит впечатляющие, понравился сам подход ????


  1. McHummer1
    21.11.2021 23:05
    -2

    Педики


    1. Wesha
      22.11.2021 00:20

      Оригинальный способ хабрасуицида.


      1. Andy_Big
        24.11.2021 14:11
        +1

        Когда это глупость стала считаться оригинальной?