Final project : development

Presentation slide

Presentation video


My project is a 3D scanner based on photogrammetry. There are 2 motors allowing the camera to take pictures from several angles.

The selling point is the low cost of the sensors (only 1 color camera), and the modularity of the scanning resolution:

I started by drawing most of the project in a common Fusion 360 project. Even though not all components are there yet, this helped in getting the geometry right.

The arc allows to capture picture between 0° and 45° of elevation. I decided to use no laser cut part, mostly because our lab’s laser is currently non-functional. I needed to re-think the gear rack on the back of the arc, as explained later.

The LED system is also a bit different: instead of individual power LEDs, I chose a continuous LED strip, offering a more isotropic source of light.

Electronics : actuators

For the electronics, I wanted to re-use the node system I had developed for the Mechanical design and machine design week. However, there were 2 important changes to make:

  • The first node should not connect to a PC through USB, but to the ESP32 through a simple serial line.
  • A new type of node should have an output to control the LEDs instead of a stepper motor.

Here is a schematic of the new system:

I designed the 2 new nodes in EAGLE and manufactured them on our Bantam dekstop CNC.

The complete system was carefully assembled and tested.

Next, I designed a box to hold the electronics in place. I double-check that the mounting holes are correctly placed.

The 3D print of the box took about 4 hours with 0.3mm layer height.

These inserts will allow me to screw the window in place.

I design the window accordingly. It’s important to leave a small gap of 0.3mm to account for the tolerance of the 3D printer.

I decided to cut the window with our CNC machine. I used 4mm PET.

The cutting went OK. Chips are very tough and fly all over the place, but this is manageable.

The box is complete.

After peeling off the protection layer and plugging in all the cables, this part of the project is finished.

Arc motion system

The most ambitious component in this project is the motion system: it is designed to follow an arc so that the camera always point towards the main platform when moving up and down.

The arc is designed to be cut with a CNC machine in MDF wood.

With a 6mm flat end mill, the cut took only about 10 minutes with a 1mm stepdown.

The finished part has tabs so that it gently remains in place.

The other half is cut, with adequate holes for the screw heads.

The groove is designed for 624ZZ bearings, with size 4x13x5mm.

For the gear rack, my solution is to glue a GT2 timing belt. This slightly distorts it, so the gear must be designed carefully.

Triangular supports are added to ensuire the vertical position of the arc.

Standard wood glue is used for the GT2 belt: it was surprisingly effective.

For generating the gear, I start by using this online tool.

With 10 teeth and a 3mm hole, I generated the curve and import it in Fusion 360. Adding the sides is essential for keeping the alignment.

GT2 pulleys (left) are very different from spur gears (right).

Small adjustments are needed because I don’t use the pulley in a regular scenario where the belt would be wrapped around it. Instead, I must make sure the gear can roll onto the belt when laid flat.

After some adjustments in Fusion, I get the following:

The gear is too small to be printed with FDM, so I use the Formlabs Form 2 resin printer from our lab.

Printing took about 2 hours, followed by 10 minutes of cleanup. I print 3 gears to have some spare.

15 more minutes in the UV light are needed for sufficient curing.

I inserted the gear onto the motor shaft during curing to create a bond between the two. It seems sufficient for this use case.

My first design for the rider is quite simple, and only includes the motor in the back and 2 bearings on the front.

Supporting lines help in making the part more rigid.

Assembling is done with M4 screws.

The cable for the motor is pretty short, so I will need to connect it somewhere before running down the arc.

My first design was not successful, as the alignment was not always guaranteed, putting extreme stress on the motor. The motor’s torque is barely enough for this project, so I need to be careful with this.

For my second design, I add a second half for more rigidity, and an additional bearing on the back to share some of the load.

The parts are printed on my home Prusa 3D printer in about 2 hours. Notice that small supports cannot be avoided for the internal holes geometry.

The additional bearing is attached the second half, making it more easy to assemble.

The final part is more lightweight and rigid than my previous iteration.

I carefuly adjust the motor and bearing positions to minimize friction, and add some grease on the gear rack.

For this simple test, only 1 node is needed, as well as an Arduino to stream serial bytes for the motion.

Here is a video of the moving part:

Main platform

After completing the arc motion, I just need to biuld the rotating platform and the base to hole the entire project. This part is less stressful, as there is less constraint on the motor torque/weight. I use a simple NEMA 17 with 0.4A for the motion.

The main platform has a 40cm diameter and is cut in 12mm MDF wood.

As usual, tabs must be carefully cut and sanded.

On the bottom of the platform, I add two disks that form a simple wooden pulley. A GT2 belt will be wrapped around it once I add 3D printed teeth.

Notice the gutter for the LED cable.

The plate must support heavy weight sometimes, so I add 3D printed supports with bearings on the bottom.

The completed plate looks fine. Notice the 5 holes for alignment: they are designed for 4mm wood screws.

For a timing belt, it’s important to design a pulley that matches. I first though of using a reversed belt to emulate a pulley, but the pitch of the teeth is completely different when distorted like that.

I 3D print a thin ring with 178 teeth, generated with the same online tool as mentioned before.

For transmitting current at all plate angles, I use a slip ring with 6 wires.

I 3D print parts that can hold the slip ring in place with screws on the side. This first part must be mounted on the bottom plate of the project.

This second part is mounted on the bottom on the disk platform.

Using side screws allows me to place the platform, then screw it in place by reaching in with a screwdriver.

The cables pass through the pulley and the gutter.

The bottom platform is a rather large component. I mill it in 12mm MDF, as usual. Notice the cable gutter on the bottom, this is important so that the platform’s bearing do not roll onto the cables.

I check that the dimensions are right, and

I mount the platform in place with an electric screwdriver.

Here is a video of the platform moving:

With the arc in place, the motion system is complete!

LED system

The LED system needs to be connected to the slip ring installed under the platform. I decided to place LED strips all around the plaform, held in place by a thin MDF ring. My LED strips are 1cm wide and I wanted to have 2 loops, so I needed 2 layers of 18mm MDF.

The ring is placed onto the platform.

My LED strips can be cut at verious lengths, so I pick the closest cutting point.

The two loops of LED strips are soldered together.

Finally, a female connector lets me connect the strips to the slip ring cables I previously soldered.

Here is a video of the LED getting a constant 12V input:

These LEDs strips are too bright, so I make use of the MOSFET to modulate its strength with PWM. This is a bit of an afterthought, so I’m lucky the ATtiny412 has PWM on that pin.

Electronics : ESP32

The final missing part is the camera itself. I use an ESP32-CAM module, with male pins already soldered. Therefore, I prepare a PCB where I will solder female headers to place/remove the ESP32.

I add male pins on the back for the motor and the main cable running down the arc.

The ESP32 is well centered on the PCB.

The PCB has mounting holes matching the rider. Notice the connector for the motor on the left.

There is a total of 6 cables running down the arc from the ESP32:

  • 5V/GND for power
  • RX/TX for serial
  • D0 and D16 for programming or additional features.

The connector is placed in the back of the arc.

I mount the endstop at the lowest valid position of the rider. It needs to be connected to the stepnode controlling this motor.


The software part is pretty straightforward. It will consist of two parts:

  • A firmware for the ESP32, including a WiFi web server to handle requests.
  • A client Python script that can automate the requests to move/shoot pictures

Finally, after all pictures have been downloaded, they can be used by a photogrammetry software (e.g. Meshroom).


The firmware used on the ESP32 is a modification of the esp32-cam-webserver project. This uses the WiFi capabilities of the ESP32 to turn it into a web server. If you know the IP address of the ESP32 on your network, you can connect to it through a web browser and get a webpage.

I modified the code to handle as many requests as needed in this project. For instance, to launch a homing of the elevation motor, the following request handler was implemented:

static esp_err_t homing_handler(httpd_req_t *req) {
  httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
  if (status != STATUS_IDLE) {
    sprintf(resp, "BUSY");
    return httpd_resp_send(req, resp, strlen(resp));

  status = STATUS_HOMING;
  sprintf(resp, "OK");  
  return httpd_resp_send(req, resp, strlen(resp));

In total, there are 6 requests:

  • /: provide the web page.
  • /homing: performs a homing of the rider and LED
  • /move?az=x&el=y: move the machine to an azimuth/elevation coordinate (x, y)
  • /shoot?led_on=x: shoot and download a picture with/without LED (x=0/1).
  • /set?led_val=x&led_on=y&motor_on=z: set the LED brightness (x=0-255), enable/disable LED (y=0/1), enable/disable motors (z=0/1)
  • /status: provide a status string (IDLE or BUSY)

Here is the web interface provided by the default request /:

Note that the code is based on a Serial communication between the ESP32 and the StepNode system controlling the motors and the LED. To provide a natural interface with the nodes, including acceleration handling, the AccelStepper library was modified. You can find the modified library at the end of the page.

Note that the firmware includes the Over the Air (OTA) service handler, so the ESP32 can be re-programmed through WiFi instead of its serial port. The board must be registered as an OTA compatible ESP32 with PSRAM, so I modified the Nano32 entry of <ARDUINO_ROOT>/packages/esp32/hardware/esp32/1.0.4/boards.txt:

If the ESP32 is connected on the same network, it can be programmed through the Arduino IDE using the OTA port:

Python client

While the machine can be used through a web browser directly, this is not convenient when automating a shooting of several pictures. To make this easier, I provide the following Python class for interfacing the web requests:

class CameraRequest:
    def __init__(self, ip, verbose=False):
        self.ip = ip
        self.fmt_status = "/status"
        self.fmt_move = "/move?az={:.2f}&el={:.2f}"
        self.fmt_shoot = "/shoot?led_on={}"
        self.verbose = verbose

    def get_txt(self, page, verbose=False):
        url = "http://" + self.ip + page
        r = requests.get(url)
        txt = r.text
        if verbose:
            print("GET:", url)
        return txt

    def wait(self):
        txt = "BUSY"
        page = self.fmt_status
        while txt != "IDLE":
            txt = self.get_txt(page)

    def move(self, az, el):
        page = self.fmt_move.format(az, el)
        txt = self.get_txt(page, self.verbose)
        if txt != "OK":
            return False
        return True

    def shoot(self, filename, led_on=True):
        page = self.fmt_shoot.format(int(led_on))
        url = "http://" + self.ip + page
        r = requests.get(url, allow_redirects=True)
        if self.verbose:
            print("GET:", url)
        open(filename, 'wb').write(r.content)

You can find a demo file in the provided software at the end of this page. In that example, the machine is used to shoot a grid around the object, with 10 steps horizontally, and 4 vertically.


To check results, I placed a statue on the platform and picked an adequate amount of LED lighting (7 on the scale 0-255):

I proceeded to shoot pictures all around the statue, with then 10 different azimuth (platform) angles, and 4 elevation angles, for a total of 40 images. Here is a video of the process:

Images are placed in Meshroom 2019.2.0, with the same procedure I tested during the 3D Scanning and printing week. The camera poses are correctly detected, without any additional processing:

Note that some views are discarded. Amazingly, the fact that the background is still causes no issues, thanks to the lack of features to be detected there. So it seems that as long as the background is uniform, it is safely ignored by the Structure From Motion step.

The final model has an impressive accuracy, reaching 3-4mm details:

Note that the OV2640 camera produced heavy jpg quantization noise during this experiment. A future improvement would be to acquire raw data instead of jpg, but this requires internal modifications in the ESP32-cam library.


Bill of Materials


  • 3x MDF 1600x1200x12mm
  • 1x PVC 200x70x5mm
  • 500g spool of 3D printing PLA
  • 26x 12mm wood screw
  • 5x 20mm wood screw
  • 1 meter of GT2 timing belt
  • 1x GT2 400mm loop
  • 3x 608ZZ bearing
  • 3x 624ZZ bearing
  • wood glue
  • 8x M4 10mm machine screw
  • 6x M4 threaded insert
  • 3x M4 15mm machine screw
  • 5x M4 nut
  • 17x M3 10mm machine screw
  • 15x M3 nut
  • 3x M6 screw + nut


The bill of material is provided in each EAGLE project, but here is a complete summary with external components (LED strip, power supply, etc.):

  • 1x ESP32-cam kit
  • 3x Attiny412
  • 2x A4988 Stepper Motor Driver Carrier
  • 2x FR-4 Copper Clad Circuit Board 4 x 2.7 inch
  • 7x 1kOhm SMD resistor
  • 4x 4.7kOhm SMD resistor
  • 4x 10µF SMD capacitor
  • 2x 22µF SMD capacitor format F
  • 2x 22µF SMD capacitor format F8
  • 4x green LED SMD
  • 1x blue LED SMD
  • 1x IRF520N
  • 32x female SMD header (1 row, zig-zag)
  • 31x male SMD header (1 row, zig-zag)
  • 14x male header (1 row, through)
  • 30x male 90° SMD header
  • 20x female 90° SMD header
  • 1x female UART connector 90°
  • 1x uA7805C 5V voltage regulator, 1.5A
  • 1x endstop
  • 2x JST 2-pin female
  • 1x 2000mA 12V power supply
  • 1x female 12V connector
  • 1x NEMA 17 with 0.4A
  • 1x Mercury Motor ST-PM35-15-11C
  • 10x 60cm female-female connector
  • 8x 20cm female-female connector
  • 2x 80cm wire (for the LED)
  • 1x SparkFun Slip Ring - 6 Wire (2A)
  • 3 meters of 1cm wide 12V LED strip


This project is placed under Creative Commons BY-NC-SA 4.0 licensing.