3D-printable hexagonal mirror array capable of reflecting sunlight into arbitrary patterns

Overview

3D printed mirror array

I recently got engaged to the wonderful person holding the hexagonal mirror thingy in the photo below. We're both huge nerds and I wanted to do something unique for a proposal, so I designed and 3D printed a mirror array to ask the question for me. The mirrors are angled so that just before sunset on our 8th anniversary, it reflects the light from the setting sun onto the ground to spell out "MARRY ME?"

Since I had a lot of fun with this project and because I love open-source code, I figured I'd publish all the source code I wrote to generate the 3D printed model and make a writeup of how I designed this. It is straightforward to modify the code to produce a 3D-printable mirror array to project any arbitrary image onto any focal plane.

Supplies

  • Python 3 and Jupyter with the following libraries installed: numpy, matplotlib, numpy-stl, hexy, vpython
    • vpython is optional if you don't care about rendering the ray tracing visualizations
  • A 3D printer
    • I bought a Creality Ender 3 v2 for this project, which I am reasonably happy with, but any FDM printer with a reasonable build volume and ~.1mm accuracy should work fine.
    • Optional: some hair spray or 3D printing adhesive spray (to keep large prints from warping)
  • Any decent PLA filament: [amazon.com/dp/B01EKEMFQS]
  • 1-inch hexagonal mirror tiles: [amazon.com/dp/B08CSCNT86] or [amazon.com/dp/B07DF5W7L6]
  • Cyanoacrylate glue: [amazon.com/dp/B01LYO4R4I]
    • Most "super glue" is this type. You can use any glue that can bind plastic to glass, but it is important that you use glue that doesn't expand when it cures or it will mess up the very sensitive mirror angles! A very thin coating of cyanoacrylate glue is sufficient to hold the mirrors in place - too much glue can also distort the angles.
  • The Sun

Computing the mirror angles

The basic idea is that we have an array of mirrors forming a hexagonal grid. Each mirror is located with its centroid at some point in space, and we want for it to reflect a ray of sunlight to a pixel at some location on the ground (or any other focal plane). Since we know where each mirror is, where each corresponding target is, and where the sun is, we can solve for how each mirror needs to be oriented in 3D space in order to reflect the sunlight onto its corresponding target.

Consider just a single mirror and a single target pixel. The center of the mirror has coordinates located at some vector mirror_pos, and the center of the target pixel is at target_pos. Let's define vectors such that mirror_pos - target_pos is the vector pointing from the target to the mirror center, and is the vector pointing in the direction of the sun (a point source placed at infinity). Here we're assuming the sun is at an inclination of from the horizon and that the azimuthal angle relative to the mirror is .

Since the reflected angles are the same as the incident angles for a mirror, the normal vector of the mirror just needs to bisect these two vectors and , so we have that :

Now that we know how a mirror with a given location and target needs to be angled, let's move on to generating the 3D models for the mirror frame which will hold the mirror at this point in space with this correct orientation.

Generating a 3D model of a hexagonal prism

A 3D model is just a list of vertices and faces. The vertices are the corners of the polygon (a list of 3D coordinates) and the faces are a list of triangular facets, where each face is specified by tuple containing the indices of three vertices. So to generate the 3D model of the hexagonal prism, I just needed to compute the coordinates for the corners of the prism and define the triangular facets that form the outer faces of the prism.

The 3D printed plastic frame of the mirror array is made up of a grid of hexagonal prisms. Each hexagonal prism has its base on the plane and has a position specified by the location of the centroid of the top face (coordinate 7 in the left image), which is normal to the vector . To compute the coordinates for the top corners, I computed the intersection of the plane defined by and the vector pointing vertically from each corner of the base (right image). I then just used a hard-coded list of vertex indices to specify the faces (so, for example, one face is (0,5,6), while another is (5,12,13) when referring to the left image).

Another thing I included in this function was the ability to add a mirror aligner structure to the prism. This mirror aligner is a small protrusion on two sides of the hex prism which fits on a corner of the hexagonal mirrors, which makes it much less tedious to glue hundreds of mirrors to the final structure (right image below). A subtle detail here is that the coordinates of the mirror aligner are not collinear with the coordinates of the top corners of the hex prism: the mirror aligner always needs to make a angle, but if the hex prism has a top surface with a steep slope, the corner angles get distorted. So to compute the coordinates of the mirror aligner, I computed the projection of the x axis onto the plane of the the top of the prism as . Then I rotated this vector about the normal vector by increments of to get the corners of the aligner.

Generating a hexagonal grid of prisms

Once I had the code to generate the hex prim and the mirror aligner, it is straightforward to generate a grid of these structures, since fusing 3D models can be as simple as concatenating the lists of vertices and faces. This approach does result in some unwanted internal geometry, but most slicer software is smart enough to figure this out and ignore internal geometry when printing.

I generated the hexagonal grid coordinates that define the center of each pillar, then I generated the list of hex prisms with the appropriate orientations to reflect onto a list of target coordinates, and finally I fused the list of prisms together into a single .stl file. For aesthetic reasons, I chose to separate the prism by about a 2mm gap and have them joined at the bottom by a slightly wider prism. An example 3D model of a small grid of prisms which all have a common focal point is shown below. The aligner structures are highlighted in red. (Note the difference in angles between the aligner corners and the prism top corners.)

Optimally matching targets to mirrors

One of the more interesting problems to solve in this project was how to optimally match target light spots to corresponding mirrors to maximize the focal distance tolerance. (So that you can hold the structure at a slightly incorrect distance or orientation from the focal plane and it will still reflect a legible message.)

The mirror array will project a set of rays onto the focal plane to form an image, and when the structure is placed at exactly the right angle and distance from the focal plane, the image will (in theory) be exactly what you want it to be. But if you deviate from this height or angle, the image will slightly deform. However, you can mitigate this by making the rays as parallel as possible, so as to minimize any times where rays will cross each other before the focal plane.

Consider the following scenario in the figure below. There are four mirrors projecting light rays onto four targets which form a diamond formation when placed at the proper focal distance. On the left side, two of the rays will cross each other, while on the right side, the rays are closer to parallel and do not cross. If you move the focal plane relative to the mirror array, the left image will distort, becoming vertically squished, while the right image does not significantly distort (the points remain in the same relative configuration).

So we want to match the target points with corresponding mirrors so that the rays are as parallel as possible and minimal crossings occur. How should we do this? An early approach I took was to iterate over the mirrors and greedily assign each target to the mirror which minimizes the angle of incidence (so, minimizing the inner product of ). However, this approach didn't work as well as I had hoped because it only considered the magnitude of this inner product rather than the direction of the reflected light beam. You can see some of the distortion in the early test prints of the heart mirrors in the next section, which used this initial matching algorithm. (Although some of that distortion was also due to poor adhesion to the print bed.)

The algorithm I ended up settling on is inspired by the structure of the hexagonal grid itself. A hexagonal grid with radius of hexagons has a number of mirrors. So there's one mirror at the center (), and after that, 6 mirrors in the innermost ring (), 12 mirrors in the next ring, then 18, and so on. To minimize ray crossings, I computed the center of mass of the target pixel coordinates, then grouped the coordinates into bins based on their distance from the center of mass, where each bin has a size equal to the number of mirrors in that ring. So the centermost target pixel gets assigned to the center mirror, then the 6 other pixels closest to the center gets assigned to the first ring, and so on. Within each bin, I ordered the target coordinates clockwise starting from the bottom coordinate, and I assigned the target to the mirrors iterated in the same order. So the outermost ring of mirrors corresponds to the outermost ring of targets, and within this set of targets, the bottom target gets assigned to the bottom mirror, the leftmost target gets assigned to the leftmost mirror, etc., as shown in the figure below.

This matching algorithm ended up working quite well. (And I do love matching algorithms...) Here is a side by side comparison of the rays which form the "MARRY ME?" message when assigned to mirrors randomly (left) versus when assigned using this matching algorithm (right). You can see that the rays are much more parallel. This means that the mirror array can be held within a much wider tolerance of the correct distance from the focal plane while still projecting the message legibly.

(Also, I discovered the biggest near-failure of this project around the time I was writing the visualization code that I used to generate the images above. At that point, I was about 20 hours into printing the final mirror array frame, and I realized that what I would have printed would actually project the mirror image of what I wanted! "?EM YRRAM" I hadn't caught this issue previously because the test image of the heart was symmetric, so I didn't realize that I was printing the mirror of the correct image. Fortunately I found this mistake in time, since by this point the proposal date was just a week out, so if I had caught this any later I wouldn't have had time to print another frame. The takeaway from this near-miss is that writing visualization code to help debug what is going on is very important... 🙃 )

Printing the mirror array frame

So by this point I had developed the end-to-end process where you can input a set of coordinates that form your desired image, the location of the focal plane, and the location of the sun, and you get out a .stl model of the mirror array frame that you can 3D print. So now we just need to print this out and glue on the mirrors -- simple, right?

I printed four different attempts at a smaller-scale mirror array to test for the big thing. Each small frame had 37 mirrors and would project the design of a heart. (37 mirrors corresponds to a radius hex grid, which was the largest I could print out in a single piece given my Ender3's 220mm × 220mm print area.)

There were two main problems I encountered when printing these test arrays. The first was warping due to the print not fully adhering to the print bed. This would cause the corners at the bottom to slightly peel up due to tension from the cooling layers, which would affect the angles of the hexagonal pillars above the warped base region and distort the projected image. I solved this problem by lowering the bed temperature and applying some hairspray to the print bed (some 3D printing tribal knowledge...), which worked like a charm.

The second problem was how to print out a hex grid which was too big to fit within the print volume. Some slicer programs include built in tools to slice large prints into pieces, but I couldn't use these because the planar cuts would slice through the hexagonal pillars, which would create a ridge on the top face and distort the angles of the mirrors, which need to be accurate to about (which is a few tenths of a degree). So I created a few custom functions to input a set of pillar models and partition them into smaller printable sub-volumes which could be glue together. This way, the only seams would be at the base of the model, which wouldn't affect the normal vectors of the mirrors.

I played around with a couple of different partitioning schemes, but I settled on using this flower-like pattern shown below, both because I liked the way it looked, and because it provides more structural strength since there is no continuous seam across the entire structure along which the assembled object could snap in half.

The entire model took about a week to print (I used adaptive layer heights to speed things up a bit when printing the bottom and middle layers, but the top layers needed to be very fine to allow the mirrors to be mounted at exactly the right angles). I assembled the frame and used cyanoacrylate glue to bond each section of the frame at the base. (I had to slightly sand the edges of the final section to get it to fit.) The fully assembled frame is shown below.

The last step was carefully glueing all 196 mirrors to the completed frame. I found out on some of my earlier test structures that cyanoacrylate glue will produce a lot of vapors when it is curing which can deposit on the mirrors and cause them to look foggy. (They still reflect enough light to work but it looks uglier...) To prevent this, I had a fan running blowing air from right to left while I glued the mirrors, which I attached in a right-to-left, top-to-bottom ordering to minimize vapor exposure.

By the time I finished mounting all of the mirrors, it was the night before I was planning to propose, so in a nail-biting twist of fate, I didn't actually get to test the final structure at sunset in advance. Fortunately, it worked exactly as I wanted it to! (And even more fortunately, the temperamental fog of the SF Bay Area decided to take the day off.)

This was the first project I've done with 3D printing (and in fact I got into 3D printing specifically to do this idea), and it felt like magic having a fabricator sitting in my bathroom that could just bring this idea into existence from nothing but a bit of code and some basic principles of physics.

VIDEO_mirror_array_on_wall.mp4
Owner
Ben Bartlett
PhD candidate in applied physics at Stanford @fancompute. Currently working @Google. I like photonics, Python, and cats.
Ben Bartlett
Small Robot, with LIDAR and DepthCamera. Using ROS for Maping and Navigation

🤖 RoboCop 🤖 Small Robot, with LIDAR and DepthCamera. Using ROS for Maping and Navigation Made by Clemente Donoso, 📍 Chile 🇨🇱 RoboCop Lateral Fron

Clemente Donoso Krauss 2 Jan 04, 2022
Play a song with a 3D printer.

MIDI to GCODE Play a song with a FDM 3D printer. SLA printers don't have motors, so they cannot play music. Warning: Be ready to turn off the 3D print

Patrick 6 Apr 11, 2022
Testing additional addon devices, and their working scripts

ESP32-addon-devices-potpurri Testing additional addon devices, and their micropython working scripts 📑 List of device addons tested so far Ethernet P

f-caro 0 Nov 26, 2022
uOTA - OTA updater for MicroPython

Update your device firmware written in MicroPython over the air. Suitable for private and/or larger projects with many files.

Martin Komon 25 Dec 19, 2022
智能无人机路径规划仿真系统是一个具有操作控制精细、平台整合性强、全方向模型建立与应用自动化特点的软件

Drone智能无人机路径规划仿真系统是一个具有操作控制精细、平台整合性强、全方向模型建立与应用自动化特点的软件。它以A、B两国在C区开展无人机战争为背景,该系统的核心功能是通过仿真平台规划无人机航线,并进行验证输出,数据可导入真实无人机,使其按照规定路线精准抵达战场任一位置,支持多人多设备编队联合行动。

wwy 349 Jan 03, 2023
A module for cross-platform control of the mouse and keyboard in python that is simple to install and use.

PyUserInput PyUserInput is a group project so we've moved the project over to a group organization: https://github.com/PyUserInput/PyUserInput . That

Paul Barton 1k Dec 27, 2022
Mycodo is open source software for the Raspberry Pi that couples inputs and outputs in interesting ways to sense and manipulate the environment.

Mycodo Environmental Regulation System Latest version: 8.12.9 Mycodo is open source software for the Raspberry Pi that couples inputs and outputs in i

Kyle Gabriel 2.3k Dec 29, 2022
A python script to poll RPi GPIO pins and subscribe and publish their state via MQTT

MQTT-GPIO A python script to poll RPi GPIO pins and subscribe and publish their state via MQTT using TLS. This script is short and meant to be edited

23 Oct 12, 2021
Modi2-firmware-updater - MODI+ Firmware Updater With Python

MODI+ Firmware Updater 실행 준비 python3(파이썬3.9 혹은 그 이상의 버전)를 컴퓨터에 설치 python3 -m pip

LUXROBO 1 Feb 04, 2022
Example for Calculating Robot Dynamics Using Pinocchio Library

A Example for Calculating Robot Dynamics Using Pinocchio Library Developed by: Xinyang Tian. Platform: Linux + Pinocchio. In this work, i use Pinocchi

Rot_Tianers 33 Dec 28, 2022
Skykettle ha - Redmond SkyKettle integration for Home Assistant

Redmond SkyKettle integration for Home Assistant This integration allows to cont

Alexey 'Cluster' Avdyukhin 48 Jan 06, 2023
A simple non-official manager interface I'm using for my Raspberry Pis.

My Raspberry Pi Manager Overview I have two Raspberry Pi 4 Model B devices that I hooked up to my two TVs (one in my bedroom and the other in my new g

Christian Deacon 21 Jan 04, 2023
Using a raspberry pi, we listen to the coffee machine and count the number of coffee consumption

A typical datarootsian consumes high-quality fresh coffee in their office environment. The board of dataroots had a very critical decision by the end of 2021-Q2 regarding coffee consumption.

dataroots 51 Nov 21, 2022
Switch predictor for Home Assistant with AppDeamon

Home Assistant AppDeamon - Event predictor WORK IN PROGRESS - CURRENTLY NOT COMPLETE AND NOT WORK This is an idea under development (when I have free

37 Dec 17, 2022
Simples Keylogger para Windows com um autoboot implementado no sistema

MKW Keylogger Keylogger simples para Windos com um autoboot implementado no sistema, o malware irá capturar pressionamentos de tecla e armazená-lo em

3 Jul 03, 2021
Python Wrapper for Homeassistant's REST API

HomeassistantAPI Python Wrapper for Homeassistant's REST API Please ⭐️ the repo if you find this project useful or cool! Here is a quick example. from

Nate 29 Dec 31, 2022
Detic ros - A simple ROS wrapper for Detic instance segmentation using pre-trained dataset

Detic ros - A simple ROS wrapper for Detic instance segmentation using pre-trained dataset

Hirokazu Ishida 12 Nov 19, 2022
An open source two key macro-pad modeled to look like a cartoony melting popsicle

macropopsicle An open source two key macro-pad modeled to look like a cartoony melting popsicle. Build instructions Parts List -1x Top case half (3D p

17 Aug 18, 2022
Count the number of people around you 👨‍👨‍👦 by monitoring wifi signals 📡 .

howmanypeoplearearound Count the number of people around you 👨‍👨‍👦 by monitoring wifi signals 📡 . howmanypeoplearearound calculates the number of

Zack 6.7k Jan 07, 2023
gdsfactory is an EDA (electronics design automation) tool to Layout Integrated Circuits.

gdsfactory 3.5.5 gdsfactory is an EDA (electronics design automation) tool to Layout Integrated Circuits. It is build on top of phidl gdspy and klayou

147 Jan 04, 2023