Group Projects

Week 3. Computer-Controlled Cutting

Tasks

-Do your lab’s safety training.

-Characterize your lasercutter’s focus, power, speed, rate, kerf, joint clearance and types.

-Document your work to the group work page and reflect on your individual page what you learned.

Laser Cutter Certification and Safety Certifications

I earned my Laser Cutter Certifications for the Full Specrum P Series Lasers when I started working at Moonlighter FabLab.

Image Image Image Image

Laser Cutter Characterization

Focus

Image

The Laser Cutters at Moonlighter FabLab do not have the auto focus tool which means we are required to manually focus the focus head to the material using a laser cutter guide placed on the brass holder which would have housed the official focussing component. Focusing a laser is very important for the quality of the cut. A we well focused laser can produce sharper cuts with less unwanted burning.

Speed, Power and Current

Image

Testing the speed and power is an important step in preparing a laser cut as it can save time and prevent the material from burning. I understand that a 1/8" sheet of bass wood can be cut through with 70 Speed, 70 Power, 100 Current and 1-2 passes.These values may changed depending on the calibration of the machine. An example of an unexpected difference would be when a lower power of 40-60 with 1 pass might cut through. This can meanthat the machine was recalibrated, the leveling was done incorrectly, or that the material was thinner or softer than expected.

Image

We normally keep the current at 100 to ensure that our cuts have the best chance of going through the material. It is worth reducing the current for engraving operations.

Kerf

Image Image

Understanding the kerf of the Moonlighter FabLab Laser Cutter was very helpful in this week's project. I knew that I would need to account for the kerf in som way but this was the first time I learned that there are actual values.

Image Image

I designed a simple test for my cardboard using my usual method to getting a rough material thickness with a ruler. I made slots ranging from 3.4mm to 3.6mm to see which one would have the correct dimensions for my material and how the Kerf changes the fit.

Image Image Image

I was able to get the material to fit in all 3 slots. the difference was very subtle differences. My conclusion is that in a difference form factor, the diffences in kerf would be more pronounced.

Joint Clearance and Types

Image Image

I used a Push fit method to assemble these parts. The clearance I used was based on the previous test results. I used the dimension which I thought worked best which was 3.5mm.

Image Image

The assembly went well. I had no issues with excess movement, gaps or difficulty fitting the parts together.

Week 4. Embedded Programming

Tasks

-Demonstrate and compare the toolchains and development workflows for available embedded architectures

-Document your work to the group work page and reflect on your individual page what you learned

Link to ChatGPT

1. Arduino IDE (Beginner–Intermediate)

Best for: quick prototyping, simple projects, education

Toolchain Components:

Workflow:

  1. Install Arduino IDE
  2. Add Seeed RP2040 board support (Boards Manager URL)
  3. Select XIAO RP2040 board
  4. Write code using Arduino-style C/C++ (setup() / loop())
  5. Plug in board and press boot button if needed
  6. Upload via USB

Pros:

Cons:

2. MicroPython (Beginner–Creative Coding)

Best for: interactive projects, teaching, rapid experimentation

Toolchain Components:

Workflow:

  1. Flash MicroPython UF2 file (drag-and-drop after boot mode)
  2. Open Thonny and connect to board
  3. Write Python scripts (main.py)
  4. Run code instantly or save to board

Pros:

Cons:

3. C/C++ SDK (Pico SDK) (Advanced)

Best for: performance, custom firmware, embedded systems

Toolchain Components:

Workflow:

  1. Install ARM GCC toolchain
  2. Clone Pico SDK
  3. Write C/C++ code using hardware libraries
  4. Configure project with CMake
  5. Build to generate .uf2 file
  6. Drag-and-drop onto device in bootloader mode

Pros:

Cons:

4. CircuitPython (Alternative to MicroPython)

Best for: creative coding and hardware libraries

Toolchain Components:

Workflow:

  1. Flash CircuitPython UF2
  2. Board appears as USB drive
  3. Edit code.py directly
  4. Device auto-runs code

Pros:

Cons:

Typical Development Workflow (Generalized)

  1. Write Code
    • Arduino → .ino
    • MicroPython/CircuitPython → .py
    • SDK → .c / .cpp
  2. Build / Interpret
    • Arduino → auto-compile
    • SDK → manual build (CMake + GCC)
    • Python → interpreted
  3. Upload Firmware
    • USB drag-and-drop (.uf2)
    • Direct upload via IDE
  4. Test + Debug
    • Serial Monitor (Arduino / SDK)
    • REPL console (MicroPython)
  5. Iterate

    Modify → upload → test → repeat

Hardware-Specific Considerations

Example Workflow (Servo + Button Project)

  1. Choose Arduino IDE
  2. Import servo library
  3. Write logic: button pressed rotates servo
  4. Upload via USB
  5. Adjust timing and angles

Choosing the Right Toolchain

Week 5. 3D Scanning and 3D Printing

This week I explored 3D Printing using the Bambu Labs A1 printer. Bambu printers have become extremely popular in the past few years for their accessibility and the ease to which experienced professionals and beginner hobbyists are able to produce high quality prints. My node, Moonlighter FabLab, now has a Bambu print farm with X1 Carbon, P1S, H2D and A1 printers. I decided to do my assignment using the A1 printers because the ones at the FabLab would have the same strengths and limitations as my personal 3D printer at home. I often print my projects wherever is most convenient.

3D Printing Rules

Image

The basic model of the Bambu Labs A1 is not enclosed like most other Bambu printers, it also functions a little differently. The standard printer format with enclosures has a toolhead that moves along the X, Y axes while the printbed moves in the Z axis. For the A1 printer, the toolhead moves along the X, Z axes and the printbed moves in the Y axis. This format makes the printer more portable, keeping most of the moving parts closer to the heavier base. In my experience, this reduces the shaking and vibration which would have been managed by the structure of the other models. It is also much safer. I take this printer around kids and students very often. There is a risk that the Z axis printbeds can clamp down on objects or limbs. This is still a present risk for the A1’s toolhead and X-axis linear rail; however, this is much less dangerous of the 2 options.

The other concern about the A1 is that it would produce significantly lower quality prints. From what I have seen, the difference can be minor with the built in calibrations and having specific slicer settings for each printer model. Prints also take slightly longer on the A1 vs other printers. This is also a reason I wanted to work on the A1. If I use the lower end printer to test the base level.

Image

Assembling the parts on the build plate in the Bambu Studio slicer wasn’t difficult. I made sure it was set to Bambu A1, textured PEI plate, 0.4 diameter nozzle, standard flow with PLA Matte which were all the correct settings and conditions of my physical printer and filament. I also set the first attempt to .028mm extra draft quality with supports off. My second attempt was .20mm standard quality with supports on. This difference in settings allowed me to compare a messy print to a much cleaner print.

Image

Sending the print to the printer starts with connecting to the specific device. Under the Device tab, you can see the view from the onboard camera remotely, adjust device settings, load or unload filament, monitor prints and catch failures.

Image Image

The extra draft print was unusable in some areas. I had unsupported bridges and spaghetti failures that fused together. An interesting observation is that the print was able to recover after a while of having hovering filament. The text and non-cantilevering areas printed well in most cases.

Image

The standard quality print with supports turned out much better but needed to be spaced further apart. No failures observed.

Image

Image

Image

Image

Image

Image

Image

Image

Week 6. Electronics Design

Week 7. Computer-Controlled Machining

This week we learned to use the CNC at Moonlighter FabLab. My instructor Augusto went through the training with Alie and me. I also designed, milled and assembled a big fish installation for the ‘Make It Big’.

Group Work and Safety Training

Image

Augusto explained the various parts of the CNC.

Image

There is a dedicated PC for the CNC which uses Rhino software and Rhino CAM to layout and send the files for milling.

Image

We got to help him with carefully placing ¾” plywood which we secured to the bed with screws.

Image

He demonstrated the processes of selecting the 2D profiles and preparing the plot settings.

Image

The tool settings we used for a profile cut were as follows:

  1. ¼ Compression Bit
  2. Holder Diameter: 1.5
  3. Holder Length: 1
  4. Shank Diameter: 0.25
  5. Tool Length: 1.25
  6. Shoulder Length: 1
  7. Flute Length: 1
  8. Tool Diameter: 0.25

The Feeds and Speeds settings were:

  1. Speed: 16000 RPM
  2. Direction: CW
  3. Plunge: 14.666 in/min
  4. Approach: 7.333 in/min
  5. Engage: 5.499 in/min
  6. Cut: 120 in/min
  7. Retract: 5.499 in/min
  8. Departure: 14.666 in/min
  9. Transfer: Use Rapid
  10. Plunge between Levels: 100%
  11. First XY pass: 100%
Image

After preparing the tool path, we sent the plot.

Image

What We Learned

The ShopSabre 4896 is a CNC router with a 48" x 96" work area and 2.5-axis movement. It runs on Shop Sabre Router Controller software using a Windows-based controller. The spindle tops out at 18,000 RPM and is powered by a 9.38 HP motor. We are being tasked with training ourselves on safe operation of the CNC and characterizing its operations and capabilities.

Safety & Use:
- Don't reach for parts while the machine is on. Use the emergency stop if needed.
- Keep the vacuum on during all cuts.
- Wear safety goggles inside the workshop.
- Kerf size depends on bit size and diameter.
- Inside corners will be round due to the bit shape and bit diameter.
- Use a 2D vector file for profile cuts.
- Use a 3D file type for 3D organic surfaces.
- Avoid designs with overhangs.

Material & Bit Pairings:
- Solid Wood – 2-Flute Endmill
- Plywood – Compression Bit
- MDF – 2-Flute Endmill
- Corrugated Plastics/Cardboard – Corrugated Bit
- Foam – Single-Flute Endmill
- Plastics – Single-Flute Endmill
- Thin Metal Composites – Single-Flute Endmill
Pick the right bit for the material you're cutting.


CNC Operations & Guidelines

2D Operations
- Profiling – Cuts along inner/outer edges of shapes
- Engraving – Adds shallow surface detail
- Pocketing – Clears material inside closed shapes
These operations are the most commonly used within our facility and space.

3D Operations
- Horizontal Roughing – Removes bulk in horizontal layers
- Horizontal Finishing – Refines detail using horizontal toolpaths
- Radial Machining – Radiates toolpaths from a center point, good for circular shapes
- Vertical Roughing – Clears material in vertical passes
- Vertical Finishing – Final vertical pass for fine detail

Design Tips
- Max cut area: 48" x 96"
- Leave a margin to screw stock to the table
- Keep at least 1" between parts to reduce vibration
- No vacuum hold-down—secure your stock manually
- Avoid cutting material over 2" thick or carving deeper than 3.5"
- Split complex designs into multiple parts

CNC Workflow
1. Load your material.
2. Turn on your machine.
3. Turn on the shop vacuum (ours is a ShopFox.)
4. Home the CNC
5. Load the correct bit for the job.
6. Tool height your bit.
7. Zero the Z-axis to your material.
8. Open or import your design file.
9. Program toolpaths using 2D or 3D operations.
10. Export toolpaths as a .NC file.
11. Open file in the Shop Sabre Router Controller.
12. Start cuts.
13. Stay by the machine and monitor until job completion.

Feeds and Speeds - Our CNC tops out at 18000 RPM but we max it out at 16000 RPM for longevity purposes. The feeds and speeds we use for a 1/4 inch flat endmill are located on Slide 11. Based on the Vortex Tool app, we can push our feedrate to 324 ins/min if we wanted to. Additionally our chipload at the default 120 in/min we leave it at would be 0.00375 and if set to the 324 in/min mentioned earlier it would be 0.010125.

feed rate (inches per minute) / (RPM x number of flutes)

120 (inches per minute) / (16000[RPM] x 2[number of flutes]) = 0.00375

324 (inches per minute) / (16000[RPM] x 2[number of flutes]) = 0.010125

Cut Depth - This is associated to diameter of the bit, but for our typical purposes and for this test we use a 1/4" bit and it can typically cut all the way through a 3/4" sheet of plywood without increasing the amount of passes. If it were a 1/8" bit, we would make sure that the passes were at least two.

Stepover - (diameter/2) This parameter is determined again by the diameter when divided by two. If pocketing or surfacing this is essentially the distance between each successive pass. The larger the bit the less runtime but less smooth a cut is, the smaller the bit the more runtime but smoother the cut is.

Bit Deflection - This is determined by cutting parameters relating to feeds and speeds and cut depth. In the case of our CNC, we do not cut anywhere near the recommended feedrate of 324 in/min. Since we cut it at 120 in/min. we stay at a safe distance away from any bit deflection issues but are still within a good range that cuts our materials quickly and properly. If we were to cut above 324 in/min. we would risk bending or flexing our bit which leads to inaccurate cuts/ surface imperfections, and if our bit does not break under the pressures being placed upon it we risk it going off center creating runout issues.

Runout - Runout is a measure of how off center a bit is. To determine if our bit was off center we used a right angle tool and flattened it on top of our work surface and lined up the other end against the endmill. Please keep in mind that we had to flip the endmill around temporarily to see how straight this alignment really was, otherwise the flutes would not have given us a proper idea of how centered it was.

Image Image Image

Designing My Test File

Image Image

I designed samples for a push fit installation project I planned for Maker Faire Miami. After making the 2D profiles, I selected them and began setting up my file.

Toolpath Accuracy - Regarding toolpath accuracy, kerf plays a large part in it but so does the programmed toolpath itself. When conducting those tests we used the "Profiling" command which has the option of being set to the center of the line, or inside or outside the line. We choose to cut outside the line and the CNC automatically offsets the toolpath by half the kerf. Additionally, we used a right-angle tool to determine how straight our cuts were and if there was any deviation in its angles. There were none.

Image Image

We were also testing a new tool that could be used on ¾” honeycomb cardboard. The cut went well, unfortunately the finish was very messy with frayed edges and excessive dust. We decided to switch to a combination of laser cutting and finishing by hand.

Week 8. Elelctronics Production

This week I learned to prepare my PCB Gerber files for milling. I did it using KiCad, Bantam, and JLCPCB.

Using JLCPCB

Image Image Image Image

Using the PCB I designed in the previous weeks, I went through the prcosees of setting up an order through JLCPCB. It was very easy once I realized I would need to compress all of my Gerber files into a ZIP folder in order to upload them. I selected the single sided FR-4 PCB, which I would not mill inhouse, in a green color. The minimum order quantity is 5 and the total came up to $29.58 with a 2-4 businessday shipping time. Fulfillment would be done by DHL Express.

Using Bantam

Image

I returned to KiCad to create a proper schematic of the PBC. I still wasn’t fully confident but I followed the logic of how electric currents flow. My logic may have been a little off because I still think I put the resistors in the wrong place.

Image

We use Bantam milling tools at Moonlighter FabLab to produce our PCBs. To use these tools I needed to install the desktop application for my Windows pc.

Image Image

I found these bits for the mill thinking they were the correct type for PCBs.

You can see how sharp and pointed the tips are.

Image Image Image Image

Next I had to prepare the Bantam

I loaded the bit by loosening the grommet and inserting it according to the instruction in the application.

Image Image

Then I set up the interface for the bit type, material and positioning.

Image

After uploading the Gerber file of my PCB from KiCad, I was able to place it on the material for the best layout to be cut.

Image Image

This took some trial and error as I was figuring it out for the first time.

Image

It was finally time to cut my first PBC. The program tracks the mill as it follows the tool path.

Week 9. Input Devices

Testing the PCB and Microcontroller

Image

I used the multimeter to test the resistance on the date cannection to the servo. It confirmed that there were 1k ohms of resistance as specified by my AI instruction sheet.

Image

I also saw good continuity from my microcontroller to the servo trace.

The switches worked consistently throughout the testing process.

Image

The Serial Plotter in Arduino IDE mapped the signal from each push of the switches. Graph is showing that the first switch on D0 is sending the signal for a 180 degree rotation. The second switch on D1 is sending the signal to return to 0. The signals are sent at a uniform rate once the switches are pressed.

Week11. Networking and Communications

This week I repurposed the PCBs I made for the Input Devices and Output Devices weeks to allow my microcontrollers to communicate accross a wifi network. I used 1 Seeed Xiao ESP32C3 microcontroller as the input to send instructions to a webapp which then send a message to a second ESP32C3. The message is then displayed on an 8x8 LED matrix.

Link to Input Devices
Link to Output Devices

Generating the Code

I used Claude.ai to help me in every aspect of the coding for this project. I started by explaining the components I would be using, the existing traces and connections on my PCBs, and the functions I wanted each board and code to perform.

Link to Initial Claude.ai Session

First Prompt

I have 2 xiao esp32c3 microcontrollers. the first is a switch connected to D0 and another switch connected to D1. The other esp32c3 has an 8x8 LED matrix connected to D0. for the first esp32c3, assign the hostname 'esp32c3_1' and the hostname 'esp32c3_2' for the second controller. write an html webapp for the devices to connect to and represent the desired functions. follow the following instructions: When switch D0 on esp32c3_1 is pressed, the LED matrix on esp32c3_2 displays "HI". When switch D1 on esp32c3_1 is pressed, the LED matrix on esp32c3_2 displays "BYE". in the webapp, allow speed, color and text to be customizable. Do not hardcode any messages or animations on esp32c3_2. It should be entirely dependent on input from the esp32c3_1 and the webapp

The Architecture Claude produced


        [esp32c3_1]  ──────────────────────────────────┐
  D0 switch → "switch_press" {switch:0}         │  WebSocket
  D1 switch → "switch_press" {switch:1}         ▼
                                          [server.js]  ←→  [Browser / index.html]
                                                │              ↑ live preview
                                          "display" cmd        ↑ send text, color, speed
                                                ▼              ↑ configure switch mappings
[esp32c3_2]  ──────────────────────────────────┘
  receives → scrolls/shows text on LED matrix
      

The plan for this assignment meant that I would need to create a WebSocket and Hostnames for each ESP32C3 microcontroller. This allows them to connect to the webapp without needing to know the IP address. This is useful because the IP address will change depending on the network or availability. By using hostnames, I can ensure that the webapp can always connect to the devices as long as they are connected to a wifi network.

The hostname 'esp32c3_1' is assigned to the first microcontroller, and 'esp32c3_2' is assigned to the second.

Link to Second Claude.ai Session

I used this second Claide session to trouble shoot issues and errors in the first session. Some of the problems I encountered included, the matrix not responding, Claude including the wrong protocals and libraries in the code which were either incompatible with or unnecessary for Seeed Xiao ESP32C3 microcontrollers.

ESP32C3_1

This is the first ESP32C3 microcontroller, which acts as the input device. It has two switches connected to D0 and D1. The Code below is from the firmware .INO file I ran in Arduino IDE


/*
 * ╔══════════════════════════════════════════════════════════════╗
 * ║  ESP32-C3 #1  —  Switch Input Controller                    ║
 * ║  Hostname : esp32c3_1  (reachable at esp32c3_1.local)       ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Wiring                                                      ║
 * ║    D0  →  Switch 0  (other leg to GND, uses PULLUP)         ║
 * ║    D1  →  Switch 1  (other leg to GND, uses PULLUP)         ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Role                                                        ║
 * ║  • Hosts a WebSocket SERVER on port 80                       ║
 * ║      – Browser connects to receive switch events and         ║
 * ║        push switch-map config                                ║
 * ║  • Acts as WebSocket CLIENT to esp32c3_2:82                  ║
 * ║      – Forwards display commands on switch press             ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Libraries  (Arduino Library Manager)                        ║
 * ║    • WebSockets  by Markus Sattler (Links2004)  ← BOTH      ║
 * ║      search: "WebSockets" — install the one by Markus        ║
 * ║      Sattler, currently v2.4.x                               ║
 * ║    • ArduinoJson  by Benoit Blanchon                         ║
 * ║    NOTE: Do NOT install AsyncTCP or ESPAsyncWebServer —      ║
 * ║    those do not support the ESP32-C3.                        ║
 * ╚══════════════════════════════════════════════════════════════╝
 */

#include 
#include 
#include    // WS server  (Links2004/arduinoWebSockets)
#include    // WS client  (same library)
#include 

// ── Network ──────────────────────────────────────────────────────
const char* WIFI_SSID      = "WIFI_SSID";
const char* WIFI_PASSWORD  = "WIFI_PASSWORD";

const char* HOSTNAME       = "esp32c3_1";
const char* MATRIX_HOST    = "esp32c3_2.local";
const uint16_t MATRIX_PORT = 82;
const uint16_t SERVER_PORT = 80;

// ── Pins ─────────────────────────────────────────────────────────
const int PIN_SW0 = D0;
const int PIN_SW1 = D1;

// ── Debounce ─────────────────────────────────────────────────────
const unsigned long DEBOUNCE_MS = 50;

// ── Switch → display mapping (configurable from webapp) ──────────
struct SwitchMap { char text[64]; int speed; char color[8]; };
SwitchMap switchMap[2] = {
  { "HI",  60, "#00FF41" },
  { "BYE", 40, "#FF4500" },
};

// ── WebSocket server (browser) & client (matrix) ─────────────────
WebSocketsServer wsServer(SERVER_PORT);
WebSocketsClient matrixClient;

bool matrixConnected   = false;
unsigned long lastMCon = 0;

// ── Switch state ─────────────────────────────────────────────────
bool sw0Last = HIGH, sw1Last = HIGH;
unsigned long sw0T = 0, sw1T = 0;
bool sw0Fired = false, sw1Fired = false;

// ── Helpers ──────────────────────────────────────────────────────
String buildDisplayCmd(int idx) {
  StaticJsonDocument<256> doc;
  doc["type"]  = "display";
  doc["text"]  = switchMap[idx].text;
  doc["speed"] = switchMap[idx].speed;
  doc["color"] = switchMap[idx].color;
  String s; serializeJson(doc, s); return s;
}

void broadcastToBrowser(int idx) {
  StaticJsonDocument<256> doc;
  doc["type"]   = "switch_press";
  doc["switch"] = idx;
  doc["text"]   = switchMap[idx].text;
  doc["speed"]  = switchMap[idx].speed;
  doc["color"]  = switchMap[idx].color;
  String s; serializeJson(doc, s);
  wsServer.broadcastTXT(s);
}

void sendState(uint8_t clientId) {
  StaticJsonDocument<512> doc;
  doc["type"] = "state";
  JsonArray arr = doc.createNestedArray("switchMap");
  for (int i = 0; i < 2; i++) {
    JsonObject m = arr.createNestedObject();
    m["index"] = i;
    m["text"]  = switchMap[i].text;
    m["speed"] = switchMap[i].speed;
    m["color"] = switchMap[i].color;
  }
  String s; serializeJson(doc, s);
  wsServer.sendTXT(clientId, s);
}

void handleSwitchPress(int idx) {
  Serial.printf("[Switch] D%d → \"%s\"\n", idx, switchMap[idx].text);
  broadcastToBrowser(idx);
  if (matrixConnected) {
    String cmd = buildDisplayCmd(idx);
    matrixClient.sendTXT(cmd);
    Serial.printf("[WSC→matrix] %s\n", cmd.c_str());
  } else {
    Serial.println("[WSC] matrix not connected");
  }
}

// ── WebSocket SERVER callback (browser) ──────────────────────────
void onWsServerEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
  if (type == WStype_CONNECTED) {
    Serial.printf("[WSS] browser #%u connected\n", num);
    sendState(num);
    return;
  }
  if (type == WStype_DISCONNECTED) {
    Serial.printf("[WSS] browser #%u disconnected\n", num);
    return;
  }
  if (type == WStype_TEXT) {
    StaticJsonDocument<256> doc;
    if (deserializeJson(doc, payload, length) != DeserializationError::Ok) return;
    const char* t = doc["type"] | "";
    if (strcmp(t, "set_switch_map") == 0) {
      int idx = doc["switch"] | -1;
      if (idx < 0 || idx > 1) return;
      strlcpy(switchMap[idx].text,  doc["text"]  | "", 64);
      switchMap[idx].speed = doc["speed"] | 50;
      strlcpy(switchMap[idx].color, doc["color"] | "#00FF41", 8);
      Serial.printf("[Config] sw%d → \"%s\" spd=%d col=%s\n",
        idx, switchMap[idx].text, switchMap[idx].speed, switchMap[idx].color);
      // Ack
      StaticJsonDocument<64> ack;
      ack["type"]   = "map_saved";
      ack["switch"] = idx;
      String s; serializeJson(ack, s);
      wsServer.broadcastTXT(s);
    }
  }
}

// ── WebSocket CLIENT callback (matrix) ───────────────────────────
void onMatrixClientEvent(WStype_t type, uint8_t* payload, size_t length) {
  if (type == WStype_CONNECTED) {
    matrixConnected = true;
    Serial.println("[WSC] connected to esp32c3_2");
  }
  if (type == WStype_DISCONNECTED) {
    matrixConnected = false;
    Serial.println("[WSC] disconnected from esp32c3_2");
  }
}

// ── WiFi + mDNS ──────────────────────────────────────────────────
void connectWiFi() {
  WiFi.setHostname(HOSTNAME);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("[WiFi] connecting");
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print('.'); }
  Serial.printf("\n[WiFi] IP: %s\n", WiFi.localIP().toString().c_str());
  if (MDNS.begin(HOSTNAME))
    Serial.printf("[mDNS] %s.local\n", HOSTNAME);
}

// ── Setup ─────────────────────────────────────────────────────────
void setup() {
  Serial.begin(115200);
  delay(400);

  pinMode(PIN_SW0, INPUT_PULLUP);
  pinMode(PIN_SW1, INPUT_PULLUP);

  connectWiFi();

  // Start WS server for browser
  wsServer.begin();
  wsServer.onEvent(onWsServerEvent);
  Serial.printf("[WSS] server on port %d\n", SERVER_PORT);

  // Start WS client toward matrix
  matrixClient.begin(MATRIX_HOST, MATRIX_PORT, "/ws");
  matrixClient.onEvent(onMatrixClientEvent);
  matrixClient.setReconnectInterval(5000);
  matrixClient.enableHeartbeat(15000, 3000, 2);
}

// ── Loop ──────────────────────────────────────────────────────────
void loop() {
  wsServer.loop();
  matrixClient.loop();

  unsigned long now = millis();

  // Switch 0
  bool s0 = digitalRead(PIN_SW0);
  if (s0 != sw0Last) { sw0T = now; sw0Last = s0; sw0Fired = false; }
  if (!sw0Fired && sw0Last == LOW && (now - sw0T) > DEBOUNCE_MS) {
    sw0Fired = true; handleSwitchPress(0);
  }

  // Switch 1
  bool s1 = digitalRead(PIN_SW1);
  if (s1 != sw1Last) { sw1T = now; sw1Last = s1; sw1Fired = false; }
  if (!sw1Fired && sw1Last == LOW && (now - sw1T) > DEBOUNCE_MS) {
    sw1Fired = true; handleSwitchPress(1);
  }
}
  

ESP32C3_2

This is the second ESP32C3 microcontroller, which acts as the output device. It has an 8x8 LED matrix connected to D0. The Code below is from the firmware .INO file I ran in Arduino IDE


 /*
 * ╔══════════════════════════════════════════════════════════════╗
 * ║  ESP32-C3 #2  —  LED Matrix Display Controller              ║
 * ║  Hostname : esp32c3_2  (reachable at esp32c3_2.local)       ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Wiring  (WS2812B 8×8 NeoPixel matrix)                      ║
 * ║    D0  →  DIN   (single data wire)                          ║
 * ║    5V  →  VCC   (use 5V rail, NOT 3V3)                      ║
 * ║    GND →  GND                                               ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Role                                                        ║
 * ║  • Hosts a WebSocket SERVER on port 82                       ║
 * ║      – esp32c3_1 connects here to forward switch presses    ║
 * ║      – Browser also connects to send direct commands        ║
 * ║  • All display content is driven by incoming messages.      ║
 * ║    Nothing is hardcoded.                                     ║
 * ╠══════════════════════════════════════════════════════════════╣
 * ║  Libraries  (Arduino Library Manager)                        ║
 * ║    • WebSockets  by Markus Sattler (Links2004)               ║
 * ║    • FastLED     by Daniel Garcia                            ║
 * ║    • ArduinoJson by Benoit Blanchon                          ║
 * ║    NOTE: Do NOT install AsyncTCP or ESPAsyncWebServer —      ║
 * ║    those do not support the ESP32-C3.                        ║
 * ╚══════════════════════════════════════════════════════════════╝
 */

#include 
#include 
#include 
#include 
#include 

// ── Network ──────────────────────────────────────────────────────
const char* WIFI_SSID     = "WIFI_SSID";
const char* WIFI_PASSWORD = "WIFI_PASSWORD";

const char* HOSTNAME      = "esp32c3_2";
const uint16_t WS_PORT    = 82;

// ── Matrix hardware ──────────────────────────────────────────────
#define DATA_PIN     D0
#define MATRIX_W     8
#define MATRIX_H     8
#define NUM_LEDS     (MATRIX_W * MATRIX_H)
#define BRIGHTNESS   40    // 0-255; keep low to avoid power issues over USB

CRGB leds[NUM_LEDS];

// ── Display state ────────────────────────────────────────────────
char    currentText[128] = "";
CRGB    currentColor     = CRGB(0, 255, 65);   // default: green
int     scrollSpeed      = 50;                  // 1-100

// Scroll state
int     scrollX          = MATRIX_W;
unsigned long lastStep   = 0;
bool    scrolling        = false;

// ── WebSocket server ─────────────────────────────────────────────
WebSocketsServer wsServer(WS_PORT);

// ── 5×7 font (column-major, bit 0 = top row) ────────────────────
static const uint8_t FONT5x7[][5] PROGMEM = {
  {0x7E,0x09,0x09,0x09,0x7E}, // A  [0]
  {0x7F,0x49,0x49,0x49,0x36}, // B
  {0x3E,0x41,0x41,0x41,0x22}, // C
  {0x7F,0x41,0x41,0x41,0x3E}, // D
  {0x7F,0x49,0x49,0x49,0x41}, // E
  {0x7F,0x09,0x09,0x09,0x01}, // F
  {0x3E,0x41,0x41,0x51,0x72}, // G
  {0x7F,0x08,0x08,0x08,0x7F}, // H
  {0x41,0x7F,0x41,0x00,0x00}, // I
  {0x20,0x40,0x41,0x3F,0x01}, // J
  {0x7F,0x08,0x14,0x22,0x41}, // K
  {0x7F,0x40,0x40,0x40,0x40}, // L
  {0x7F,0x02,0x04,0x02,0x7F}, // M
  {0x7F,0x04,0x08,0x10,0x7F}, // N
  {0x3E,0x41,0x41,0x41,0x3E}, // O
  {0x7F,0x09,0x09,0x09,0x06}, // P
  {0x3E,0x41,0x51,0x21,0x5E}, // Q
  {0x7F,0x09,0x19,0x29,0x46}, // R
  {0x26,0x49,0x49,0x49,0x32}, // S
  {0x01,0x01,0x7F,0x01,0x01}, // T
  {0x3F,0x40,0x40,0x40,0x3F}, // U
  {0x1F,0x20,0x40,0x20,0x1F}, // V
  {0x7F,0x20,0x18,0x20,0x7F}, // W
  {0x63,0x14,0x08,0x14,0x63}, // X
  {0x03,0x04,0x78,0x04,0x03}, // Y
  {0x61,0x51,0x49,0x45,0x43}, // Z  [25]
  {0x3E,0x51,0x49,0x45,0x3E}, // 0  [26]
  {0x00,0x42,0x7F,0x40,0x00}, // 1
  {0x42,0x61,0x51,0x49,0x46}, // 2
  {0x21,0x41,0x49,0x4D,0x33}, // 3
  {0x18,0x14,0x12,0x7F,0x10}, // 4
  {0x27,0x45,0x45,0x45,0x39}, // 5
  {0x3C,0x4A,0x49,0x49,0x30}, // 6
  {0x01,0x71,0x09,0x05,0x03}, // 7
  {0x36,0x49,0x49,0x49,0x36}, // 8
  {0x06,0x49,0x49,0x29,0x1E}, // 9  [35]
  {0x00,0x00,0x00,0x00,0x00}, // ' '[36]
  {0x00,0x00,0x5F,0x00,0x00}, // !  [37]
  {0x08,0x08,0x08,0x08,0x08}, // -  [38]
  {0x00,0x60,0x60,0x00,0x00}, // .  [39]
  {0x00,0x36,0x49,0x55,0x22}, // ?  [40]
  {0x08,0x08,0x3E,0x08,0x08}, // +  [41]
};

const uint8_t* getGlyph(char c) {
  if (c >= 'A' && c <= 'Z') return FONT5x7[c - 'A'];
  if (c >= 'a' && c <= 'z') return FONT5x7[c - 'a'];
  if (c >= '0' && c <= '9') return FONT5x7[26 + (c - '0')];
  if (c == ' ')  return FONT5x7[36];
  if (c == '!')  return FONT5x7[37];
  if (c == '-')  return FONT5x7[38];
  if (c == '.')  return FONT5x7[39];
  if (c == '?')  return FONT5x7[40];
  if (c == '+')  return FONT5x7[41];
  return FONT5x7[36]; // unknown → space
}

// ── WS2812B pixel mapping (serpentine) ───────────────────────────
// Even rows left→right, odd rows right→left.
// If text appears mirrored or upside-down, adjust here.
int xyToLed(int x, int y) {
  if (y < 0 || y >= MATRIX_H || x < 0 || x >= MATRIX_W) return -1;
  int fy = (MATRIX_H - 1 - y);
  if (fy % 2 == 0) return fy * MATRIX_W + x;
  else              return fy * MATRIX_W + (MATRIX_W - 1 - x);
}

// ── Rendering ────────────────────────────────────────────────────
void clearLeds() {
  fill_solid(leds, NUM_LEDS, CRGB::Black);
}

int textPixelWidth() {
  int len = strlen(currentText);
  if (len == 0) return 0;
  return len * 6 - 1;  // 5px glyph + 1px gap, no trailing gap
}

void drawTextColumn(int screenX, int textCol, CRGB color) {
  if (screenX < 0 || screenX >= MATRIX_W) return;
  int charIdx   = textCol / 6;
  int colInChar = textCol % 6;
  if (charIdx >= (int)strlen(currentText)) return;
  if (colInChar == 5) return;  // gap column

  const uint8_t* glyph = getGlyph(currentText[charIdx]);
  uint8_t colBits = pgm_read_byte(&glyph[colInChar]);

  for (int row = 0; row < MATRIX_H; row++) {
    int bit = (row < 7) ? ((colBits >> row) & 1) : 0;
    int idx = xyToLed(screenX, row);
    if (idx >= 0) leds[idx] = bit ? color : CRGB::Black;
  }
}

void renderFrame() {
  clearLeds();
  int tpw = textPixelWidth();
  if (tpw == 0) { FastLED.show(); return; }

  if (tpw <= MATRIX_W) {
    // Short text: centre statically
    int startX = (MATRIX_W - tpw) / 2;
    for (int tc = 0; tc < tpw; tc++)
      drawTextColumn(startX + tc, tc, currentColor);
  } else {
    // Scrolling
    for (int sc = 0; sc < MATRIX_W; sc++) {
      int tc = sc - scrollX;
      if (tc < 0 || tc >= tpw) continue;
      drawTextColumn(sc, tc, currentColor);
    }
  }
  FastLED.show();
}

// ── Helpers ───────────────────────────────────────────────────────
int speedToStepMs(int speed) {
  return map(constrain(speed, 1, 100), 1, 100, 200, 20);
}

CRGB parseColor(const char* hex) {
  if (!hex || hex[0] != '#' || strlen(hex) < 7) return CRGB(0, 255, 65);
  long v = strtol(hex + 1, nullptr, 16);
  return CRGB((v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF);
}

void startDisplay(const char* text, int speed, const char* colorHex) {
  strlcpy(currentText, text ? text : "", sizeof(currentText));
  if (speed > 0)                      scrollSpeed  = constrain(speed, 1, 100);
  if (colorHex && colorHex[0] == '#') currentColor = parseColor(colorHex);

  scrollX  = MATRIX_W;   // start off right edge
  lastStep = millis();
  scrolling = (textPixelWidth() > MATRIX_W);

  Serial.printf("[Display] \"%s\" speed=%d\n", currentText, scrollSpeed);
  renderFrame();
}

// ── WebSocket SERVER callback ─────────────────────────────────────
void onWsEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {

  if (type == WStype_CONNECTED) {
    Serial.printf("[WSS] client #%u connected\n", num);
    StaticJsonDocument<256> doc;
    doc["type"] = "state";
    doc["text"] = currentText;
    String s; serializeJson(doc, s);
    wsServer.sendTXT(num, s);
    return;
  }

  if (type == WStype_DISCONNECTED) {
    Serial.printf("[WSS] client #%u disconnected\n", num);
    return;
  }

  if (type == WStype_TEXT) {
    StaticJsonDocument<256> doc;
    if (deserializeJson(doc, payload, length) != DeserializationError::Ok) return;

    const char* t = doc["type"] | "";

    if (strcmp(t, "display") == 0) {
      startDisplay(doc["text"] | "", doc["speed"] | 50, doc["color"] | "#00FF41");
      StaticJsonDocument<256> ack;
      ack["type"] = "display_ack";
      ack["text"] = currentText;
      String s; serializeJson(ack, s);
      wsServer.broadcastTXT(s);
    }

    if (strcmp(t, "clear") == 0) {
      startDisplay("", 50, nullptr);
      wsServer.broadcastTXT("{\"type\":\"display_ack\",\"text\":\"\"}");
    }
  }
}

// ── WiFi + mDNS ──────────────────────────────────────────────────
void connectWiFi() {
  WiFi.setHostname(HOSTNAME);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("[WiFi] connecting");
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print('.'); }
  Serial.printf("\n[WiFi] IP: %s\n", WiFi.localIP().toString().c_str());
  if (MDNS.begin(HOSTNAME))
    Serial.printf("[mDNS] %s.local\n", HOSTNAME);
}

// ── Setup ─────────────────────────────────────────────────────────
void setup() {
  Serial.begin(115200);
  delay(400);

  FastLED.addLeds(leds, NUM_LEDS);
  FastLED.setBrightness(BRIGHTNESS);
  clearLeds();
  FastLED.show();

  // Brief green flash to confirm hardware is alive
  leds[xyToLed(0, 0)] = CRGB::Green;
  FastLED.show();
  delay(500);
  clearLeds();
  FastLED.show();

  connectWiFi();

  wsServer.begin();
  wsServer.onEvent(onWsEvent);
  Serial.printf("[WSS] server on port %d\n", WS_PORT);
}

// ── Loop ──────────────────────────────────────────────────────────
void loop() {
  wsServer.loop();

  if (!scrolling || textPixelWidth() <= MATRIX_W) return;

  unsigned long now = millis();
  if (now - lastStep >= (unsigned long)speedToStepMs(scrollSpeed)) {
    lastStep = now;
    scrollX--;
    if (scrollX < -(textPixelWidth())) scrollX = MATRIX_W;  // wrap
    renderFrame();
  }
}
  
Image

Web Interface

I also created a web interface to allow the user to customize the text, color and speed of the LED matrix. The web interface also allows the user to see the current state of the switches and the LED matrix. The web interface is built using HTML and CSS. It automatically tries to connect to the WebSocket servers on each of the microcontrollers and displays their hostnames to indicate the connection status.

Link to HTML Web Interface
Image

Hardware Assembly

Image

This is the failed attempt at sending the messages to the LED Matrix. The reason this did not work is because the protocols and libraries were not properly configured in the firmware for ESP32C3_2.

Image

The Input Board with the switch 0 on D0 and switch 1 on D1. The antenna on the ESP32C3 allows it to connect to the WiFi network.

Image

The Output Board with the LED Matrix connected to the ESP32C3. This also has an antenna for the same reason as the Input Board.

This video shows the full series of operations working. when a switch is pressed, the interface registers the input and displays the corresponding message with the options to edit the text, color and speed. The message is then sent to the LED Matrix as the output.

12. Mechanical Design and Machine Design

The focus of this week is to design a machine which shows an understanding of mechanisms, actuation, automation, and application. Working in a group with Emil, a Fab Learning student who also works at Moonlighter FabLab, our project became a chance for us to expand upon our previous projects.

Tasks and Responsibilities

Part 1: Mechanical Design

To start, Emil designed the gears for a time tracking mechanism. This was a part of one of his lesson plans for elementary school students. He calculated the gear ratios and the relationships to track seconds and minutes. He then used Tinkercad to model the prototype for his design.

Image Image Image

This is the 3D printed prototype he produced.

Gear Redesign

Image
Link to Gear Generator

I decided to explore gear assemblies further to see if I could design a more integrated system for his concept. This brought me to a gear generator website that simulates gear assemblis and allows you to modify them. Unfortunately, this site was not free, so I used the simulation as a reference as I sought out other resources.

Image Image Image Image Image Image
Link to Gear Generator

Next I found a free Gear Calculator that allowed me to design each of the gears I saw in on the first website. After generating the gears, I downloaded the .DXF files and imported them into Rhino. This did not work out because the gears were not properly aligning and I very quickly realized that the system would not work.

Image
Link to Grasshopper Tutorial

While in Rhino, it occured to me that I might be able to use Grasshopper to create parametric gears. Turning to a Youtube for guidance, I found a tutorial on how to use the Planthopper plugin. It does over adding gears, modifying its parameters and running a simulation. The final product can be baked as polysurfaces and modeled as regular 3D models.

I followed the tutorial until I got a sufficient grasp on how the tools worked. I then designed my parent and child spur gears, and a planetary gear set.

Image Image Image

The 3D modeling went well. It took a while to account for the various design considerations which include pin and shaft placement and dimensions, gear thickness, and the enclosure. The gears are meant to be exposed because this is a tool for learning and further development.I designed it in a way would allow us to continue tinkering.

Image

Emil and I reviewed the design changes. We decided to print it and test the physical prototype.

Image Image Image

I printed the parts on a Bambu Lab A1 printer.

The assembly worked but I had to break the crankshaft to fit the components together. This made manually turning the gears rather difficult

Part 2: Machine Design

Image Image
Link to Input Devices

For my Input Devices assignment, I designed a PCB that used switches to control a servo motor. Using the PCB and code from that project, I was able to automate the gear assembly.

The connection was not fully resolved, but Emil and I were able to control the gear rotation much better.

Conclusion and Areas to Improve

The project was a great implementation of a PCB I already designed. It was a valuable opportunity to explore gear design and to use parametric tools to generate them. Going forward, I would like to refine the design and improve the system integration. I would also like to add more design elements for the project to function as a working time device.

Presentation Video and Slide

Image

Week 14. Moulding and Casting

This week we will be exploring the techniques of moulding and casting, which I used to create little Makey Robots.

Nicpro 2-Part Silicone Mold Making Kit — Safe Use Guide

Platinum-cure RTV-2 silicone  |  Non-toxic, odorless, food & skin safe  |  Not a hazardous substance (OSHA 29 CFR 1910.1200)

Technical Specifications

Property Details
Mix Ratio1A : 1B by weight
Pot Time~30 minutes
Cure Time3–5 hours at 73°F (23°C)
Hardness15A (translucent) or 20A (jade green)
Mold Life500–1,000 uses
UV Resin CompatibleNo — will NOT cure with UV resin

Safety & PPE

Step-by-Step Instructions

Troubleshooting

Problem Fix
Tacky or uncured moldWrong ratio or incomplete mixing
Bubbles on surfacePour slower from greater height; rest mixture longer
Silicone sticks to masterApply petroleum jelly before pouring
Slow or incomplete cureRoom too cold — warm with a heat lamp
Want a colored moldAdd mica powder or silicone pigment before pouring

Sakrete Crack Resistant Concrete Mix (50 lb) — Safe Use Guide

Professional-grade blend of cement, sand, gravel & alkali-resistant fibers | Exceeds ASTM C 387 | 4,000 psi at 28 days

⚠ Hazard Warning (OSHA 29 CFR 1910.1200): Contains Portland cement / crystalline silica. Causes skin irritation, serious eye damage, allergic skin reaction, and lung damage with prolonged exposure. May cause cancer. Keep out of reach of children. Emergency: CHEMTREC 800-424-9300

Technical Specifications

PropertyDetails
Compressive Strength2,500 psi at 7 days | 4,000 psi at 28 days
Water per 50 lb bag~2.5 quarts (2.4 L) — adjust for stiff, forming consistency
Working Temperature40°F–90°F (4°C–32°C)
Foot Traffic24 hours
Vehicular Traffic72 hours
Min. Application Depth2 inches (50 mm)
Control Joint SpacingEvery 3–4 ft; expansion joints every 8 ft x 12 ft

Safety & PPE

Step-by-Step Instructions

Troubleshooting

ProblemFix
Mix too stiffAdd small amounts of water until forming consistency is reached.
Mix too soupyAdd more dry mix. Do not use as-is — weak, crack-prone result.
Surface crackingCut control joints; keep surface moist during curing; shield from wind and direct sun.
Set too slowTemperature too cold. Use warm water; insulate if below 40°F.
Set too fastTemperature too hot. Use cold water; work early morning; mist surface.
Weak / dusty surfaceDo not add water to the surface during finishing.

Storage & Disposal

Based on Sakrete Crack Resistant Concrete Mix TDS and SDS (rev. March 2022). Technical support: 866-725-7383. Emergencies: CHEMTREC 800-424-9300.

Casting Concrete

I can not share the 3D model for this project so I decided to use it for my group work experimentation.

Image

I 3D printed the molds from PLA and designed places for threaded rods and nuts to be embedded in the concrete when cast. This component was designed to connect to other parts of a larger structure and I needed the rods to be precisely placed.

Image

To add reinforcement, I added chicken wire mesh and thick rigid wires.

Image

I leaned the molds against a stable concrete object while my dad and I mixed the concrete in a bucket. We aimed for an oatmeal consistency when adding the water and used a drill with a mixer attachment to combine the materials uniformly. We also sprayed the inside of the molds with a mixture of castor oil in isopropyl alcohol that would act as mold release.

Note that We are outside in a well-ventilated area. We are also wearing appropriate PPE because working with concrete can be hazardous.

Image

We tied the molds to the support object with ropes before pouring the concrete. When it was full, we added 2 pieces of rebar kept in place with a flat piece of wood. The concrete used had fiber for additional reinforcement.

Image

After 3 days, the concrete had cured sufficiently for the molds to be removed. I began breaking the seams of the 3D printed molds. I used multiple tools to break the PLA but it was not an easy process. I even used a soldering gun to melt and cut away sections of the mold.

Image

When the mold pieces were removed, the seam and joint lines were very pronounced. This was an interesting consequence of this process that I actually liked. The rods also bonded to the concrete very well.

I learned that there are a lot of variables to consider when casting concrete, especially when using 3D printed molds. The molds need to be strong enough to manage the weight of the concrete. The seams are the likely points of failure during the initial pour; however, this is only a concern in the first 3o minutes to hour. Once the concrete starts to set, everything stabilizes. The seams and joints also need to be designed in a way that avoids grooves or ridges in the casting.

Casting Silicone

Image

I designed the 2 part molds in Rhino 8 considering the holes to pour the silicone and stumps to help with alignment of the parts. I also added vent holes and clamping pieces to hold the mold together. There are also plugs to push the silicone firther into the mold. I exported the model as an STL and opened it in Bambu Studio. The Print was set to an A1 printer with a texture plate and a 0.4 nozzel. The Bambu PLA was Matte and the print quality was standard. If I try again, I would set it to a higher quality. The infill was 10 percent grid and I adjusted the variable layer height to make teh curves smoother.

3D printed mold halves and registration pins on table next to printer

I sent the print with all of the parts on one plate.

Bambu Lab 3D printer with completed mold parts on the print bed

The print went well.

I assembled the mold parts and added tape around the seams to seal them and hold teh part together more securely. I then poured the silicone into the mold. The silicone is a 2 part mixture at a 1:1 ratio. poured each part into a separate disposable cup and mixed them together until well incorporated. I waited a few minutes after mixing to allow to bubble to float up and reduce the amount of trapped air in the cast. After the wait, I was able to pour the silicone into the mold making sure the silicone got into all areas of the mold.

Green 3D printed Makey Robot front view

The silicone was allowed to cure for 3-6 hours. I released the mold and took the silcone out. it came out easily thanks to the petroleum jelly I added to the mold before assembly.

Green 3D printed Makey Robot back view

The silicone needed to be cleaned up after demolding to remove excess material.

I learned that the process benefits from careful attention to detail in mold design and assembly. Mold release agents are crucial for easy demolding.

Silicone Mold Making

Bambu Lab printer printing two robot positives with grid infill Close-up inside printer showing early layers of the mold base

Using the same setting as before, I printed 2 halves of the makey robot as the positives of the mold I wanted to make. I printed these in 2 sizes and on Bambu P1S printers.

Two completed 3D printed square mold box frames on a workbench Flat mold base tray on the Bambu Lab print bed

I designed a base and walls to contain the silicone when I would do the pours. I printed them in the P1S printers as well.

Two assembled mold boxes with robot positives inside, large and small sizes

The robot positives are placed face-up inside their respective mold boxes, one large and one small.

Workspace with mold boxes, isopropyl alcohol, Q-tips, petroleum jelly, silicone rubber, and hot glue gun

The full workspace laid out: two assembled mold boxes, isopropyl alcohol, Q-tips, petroleum jelly (mold release), a mixing cup, NicPro silicone rubber Parts A and B, and a hot glue gun to seal any gaps.

Mold boxes, silicone rubber Part B, petroleum jelly, and hot glue gun on cutting mat

A closer view confirming all materials are present: NicPro Silicone Rubber Part B, petroleum jelly for release, a calibrated mixing cup, and the hot glue gun for sealing the mold box joints.

Blue gloved hands wiping down the mold box frame with isopropyl alcohol

Wearing nitrile gloves, the mold box frame is wiped down with isopropyl alcohol on a paper towel. Removing dust, oils, and any 3D print residue ensures the molds are able to be properly sealed.

Applying double-sided tape to the robot positive inside the mold box

Double-sided Scotch tape is applied to the underside of the robot positive to anchor it firmly to the mold box floor. This prevents the positive from floating or shifting when the silicone is poured.

Brushing petroleum jelly onto the robot positive as a release agent

Petroleum jelly is brushed over every surface of the robot positive using a fine paintbrush. This release agent prevents the cured silicone from bonding to the 3D print, allowing clean demolding.

Brushing petroleum jelly onto the robot positive inside the mold box

The petroleum jelly coat is continued inside the mold box, covering all interior walls and the floor around the positive. Full coverage ensures the cured silicone releases from every surface.

Utility knife trimming blue painter's tape sealing the mold box base

Blue painter's tape is applied around the joint between the box frame and base tray. A utility knife trims the tape flush so there are no gaps for the liquid silicone to seep through.

Two prepared mold boxes sealed with blue tape, ready for pouring

Both mold boxes are prepped and sealed with blue painter's tape around their bases. The robot positives are coated in release agent and taped down — ready to receive the silicone rubber.

Two sealed mold boxes with robot positives, positioned on cutting mat

A top-down view of both mold boxes positioned on the cutting mat. The positives are centered, the tape seals look tight, and the workspace is clear for a controlled pour.

Mold box filled with teal mixed silicone rubber, curing

The mixed NicPro silicone rubber (Parts A and B combined) has been poured into the large mold box. The teal liquid completely covers the robot positive. The mold is left undisturbed to cure.

Close-up of teal silicone in the mixing cup being poured

The combined Parts A and B in the mixing cup show the vibrant teal color of the NicPro silicone rubber after mixing. The cup is tilted over the mold box to begin the slow, steady pour.

Full measuring cup of mixed teal silicone rubber showing the 6 oz level

The full mixing cup holds approximately 6 oz of combined silicone. Getting the volume right before pouring ensures the positive is fully submerged with enough silicone thickness above it for mold durability.

Pouring teal silicone from the mixing cup into the robot mold box

The mixed teal silicone is poured directly into the mold box over the robot positive. The pour is slow and from a height to help break up air bubbles as the material flows into the details of the robot shape.

Two completed teal silicone molds showing the robot cavity, large and small

After curing, the silicone is removed from the 3D printed boxes. The two finished molds reveal clean, flexible robot cavities with sharp detail in the head, chest logo, arms, and legs.

Purple-dyed soap poured over silicone molds on a red tray Broken soap cast pieces next to the silicone mold after first attempt Both silicone molds with broken soap cast pieces from both attempts Close-up of a broken cast piece showing white and purple layered colors Spooning purple liquid soap into the mold cavities for second attempt Purple wax/soap cast of the robot shape in the silicone mold

I melted soy wax and added candle dye and fragrance oil to see if my molds would work. After multiple attempts, I learned that wax does not like to be poured in layers on cold joints. I also found that my mold was causing the wax to break very easily. A few attempts in, I understood that the problem was that I had too many diferences in depth in my design. The legs and arms would need to be deeper with less dramatic joint when meeting the torso. The cast was smooth otherwise.

Final Comparison of Materials and Methods

I believe that each material is suitable for different applications, and the choice depends on the specific requirements of the project. That said, casting silicone was the easiest process, most forgiving and balanced quality and speed.

The downside to silicone is that it does not have a clean option for post processing. Concrete however, can be filled, sanded and finished after casting. This makes it better suited for a final, long lasting product. It is also a strong, durable composite material that can be used in structural and decorative applications. Concrete also has the most safety concerns which is why I worked outside in well ventilated areas and used PPE gear when working.

the soy wax had the fastest setting time and had a professional finish when cast in the silicone mold. the issue was that it was very brittle and had no resistance to cracking. This makes its applications limited.

Downloadable Files
Group Projects