Skip to content

11. Open Source Hardware - From Fibers to Fabric

goals of the week & contents

  • Research and document existing fabrication methods, machines and industries, add references and sketches of the machine and the chosen process.
  • Design and document the files of the machine, machine hack or tool and fabrication - assembly process.
  • Document the schematic and the programming code (if any).
  • List the materials: electronics, materials amount other (references of the components).
  • Design, create and document a final outcome, a sample project of your process.
  • Make a small video of the machine.
  • Create an interface for controling your machine.


Hardware — tangible objects, furniture, textiles, machines, electronic devices.

Open source hardware — knowledge freely accessible (design, components, instructions)

Documentation:

  • License
  • ReadMe/ Overview — what is the machine about/ what is for
  • Assembly instructions — how to build
  • Bill of materials (BOM) — all the parts you need to build the machine, things you have to buy add the links
  • Purchase Parts
  • Unique Parts
  • Source files (original drawing, give to the community so that they can modify it) & Export files
  • Software
  • Review & Rebuild

Licenses:

Permissive licenses — greatest freedom in reusing the project

Copyleft licenses (strong/weak) — ensure that all new versions of the project are releaser under a copyleft license.

Certificates: OHSWA Certificate / DIN SPEC 3105

Upload your project on: Wikifactory, Gitlab, Notion


research

The Cycloid Drawing Machine, designed by LEAFpdx, is a mechanical, hand-cranked device used to create geometric designs. Drawing inspiration from historical mechanical devices that generated cycloid patterns (used since the 1880s), this modern version is more versatile. Unlike its predecessors, this machine is fully customizable, with interchangeable gears and a movable fulcrum that allows for endless design possibilities.

It operates without electricity or motors, just manual cranking.




The Mi.Mu Gloves are a wearable technology developed by musician Imogen Heap and a team of engineers. These gloves allow performers to control music through hand gestures by triggering MIDI events, manipulating sound effects, and adjusting musical parameters in real-time. The gloves utilize motion sensors and can map specific gestures, such as clenched fists or open palms, to control various elements like pitch, effects, and looping​.

The gloves are paired with a highly flexible and customizable Mi.Mu software, which is central to their functionality. This custom-built software allows users to program specific gestures and movements to interact with their music.




Patrick Tresset's Human Study #1 is an installation that explores the role of robots in art. In this piece, robots equipped with drawing arms observe and sketch a human subject in real-time, simulating a life drawing class. The subject remains still while the robots, designed with minimalistic, desk-like bodies, create drawings using Bic pens.




workflow

We started this week with exciting ambitions! Michelle and Asli shared their plans for Open Source Hardware Week, inspired by the Tracks4Crafts project, which is currently being developed at Waag. This initiative, focus on craft knowledge and practice of textile painting, incorporating the lab machinery, like the Axi-draw and the Shopbot. The project explores how digital tools can interact with traditional craft techniques, emphasizing the blend of human and machine collaboration. While their website isn't live yet, it will soon provide more details.

The project showcases the rich history of textile painting craftsmanship in the Netherlands, highlighting not only local practices but also the work of other artists and craftspeople exploring these techniques. It fosters a deeper understanding of the connection between technology, biology, craftsmanship, sustainability, and innovation.

Last year participants developed the Open Source Hardware Week around the ShopBot, at the beginning of the Tracks4Crafts project. Since it was their first time working with the ShopBot for this purpose, the focus was on understanding its workflow, designing custom brushes, developing the ShopBot code, and figuring out how to attach the brushes to the machine. You can check Aslı Aydın Aksan documentation here!


brainstorm

Building on the foundation of this project, we decided to further explore and hack the ShopBot, and develop a functional dye reservoir pumping system. Our focus was on haptics, where we aimed to establish a system of input and output. We envisioned a wireless communication system that would allow us to create a modular setup, offering multiple options for exploring brush movement.

We began by brainstorming potential inputs and outputs for the system, considering various ways to translate the interactions into outputs.


Sensors Inputs Outputs
Audio Sensor Sing Up & Down / Sweep
Touch Slider (felt) Sway Up & Down / Revolve
Squeeze/Stretch (knit) Squeeze/Stretch Up & Down / Sweep / Revolve
LDR/Proximity Shine/Shade Up & Down / Sweep
Heartbeat/Temperature Sweat Sweep


machineinterventions

wirelesscommunication

Illustrations made by Issy on Procreate.



TASK MANAGEMENT AND DISTRIBUTION & BILL OF MATERIALS

taskmanagement

bill of materials


haptic inputs

For our Inputs, we decided to go with three main sensors to have different interactions:

  • Audio Sensor: this was something I was particularly excited about. The circuit was simple to set up, and we used it to control the Up & Down and Sweep outputs. The sensor captures sound levels and translates them into those ouputs.

  • Squeeze/Stretch Sensor: we made a machine-knitted sample with conductive thread and this sensor controlled the Up & Down and Sweep outputs. I experimented with similar sensors during E-Textiles week, but the outputs were sound via a MIDI setup or light, not motion, which made it so much fun!

  • Touch Sensor: we designed a touch slider using a needle-felted sample with conductive fiber. This sensor managed the Up & Down output. The felt sample has a striped design, with conductive fibers in certain stripes. When a single conductive stripe is touched, it generates a specific angle for the arm output. If two adjacent stripes are touched simultaneously, the arm calculates and outputs an intermediate angle between the two corresponding positions.

breadboard

For the code that Michelle Vossen did, she used analogRead() for the audio and squeeze/stretch sensors, and touchRead() for the touch sensor.

Building the Circuit on a Breadboard:

  • Stretch sensor connected to analog pin A9 using alligator clips, connected into a voltage divider to 3.3V and GND.
  • Seven touch pins, T1, T2, T3, T4, T5, T6, and T7, each connected to the respective touch sensors.
  • Audio sensor connected to analog pin A10 via its data pin, connected to 5V for power and GND.


INPUTS (SENDER) CODE:

#include <esp_now.h>
#include <WiFi.h>


int touchAngle;              // variable to store servo angle in for the touch input to revolution angle
int squeezeSensorPin1 = A9;  // Squeeze sensor pin > up/down
int soundSensorPin2 = A10;   // Sound sensor pin > sweep
int buttonPin = D6;
#define NUM_TOUCH_PINS 7

int touchPins[NUM_TOUCH_PINS] = { T1, T2, T3, T4, T5, T6, T7 };
int touchThresholds[NUM_TOUCH_PINS] = { 110000, 110000, 110000, 110000, 110000, 110000, 110000 };
int mappedValues[13];  // Precompute the 13 values between 0 and 180

int sensorData1 = 0;
int sensorData2 = 0;

// Keeps track of the last pins touched
// so we know when buttons are 'released'
uint16_t lasttouched = 0;
uint16_t currtouched = 0;

// REPLACE WITH YOUR RECEIVER MAC Address: 64:e8:33:00:90:1c
uint8_t broadcastAddress[] = { 0x64, 0xe8, 0x33, 0x00, 0x90, 0x1c };

// Structure example to send data
// Must match the receiver structure
typedef struct struct_message {
int id;  // must be unique for each sender board
int revolution;
int pressure;
int sweep;
int ink;
} struct_message;

// Create a struct_message called myData
struct_message myData;

esp_now_peer_info_t peerInfo;

// callback when data is sent
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("\r\nLast Packet Send Status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}

void setup() {
// Init Serial Monitor
Serial.begin(115200);
pinMode(buttonPin, INPUT_PULLUP);
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);

// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
}

// Once ESPNow is successfully Init, we will register for Send CB to
// get the status of Transmitted packet
esp_now_register_send_cb(OnDataSent);

// Register peer
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;

// Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK) {
    Serial.println("Failed to add peer");
    return;
}
}

void loop() {
touchAngle = getTouchAngle();  // Call the function to update touchAngle
if (touchAngle != -1) {
    Serial.println(touchAngle);  // Print the touchAngle to Serial Monitor
}



sensorData1 = analogRead(squeezeSensorPin1);
sensorData2 = analogRead(soundSensorPin2);

// Set values to send
myData.id = 1;           // board number in case there will be more boards sending later
myData.revolution = touchAngle;   // 0-180
myData.pressure = sensorData1;  // 0-4095
myData.sweep = sensorData2;  // 0-4095
myData.ink = digitalRead(buttonPin);  // 0-4095

Serial.println(touchAngle);
Serial.println(sensorData1);
Serial.println(sensorData2);
Serial.println(digitalRead(buttonPin));

// Send message via ESP-NOW
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *)&myData, sizeof(myData));

if (result == ESP_OK) {
    Serial.println("Sent with success");
} else {
    Serial.println("Error sending the data");
}
delay(50);
}


// determine if touch pins are touched and map to servo motor angle 0-180

int getTouchAngle() {
int activePin = -1;
int nextPin = -1;

// Check which pin(s) are touched
for (int i = 0; i < NUM_TOUCH_PINS; i++) {
    int touchValue = touchRead(touchPins[i]);
    if (touchValue > touchThresholds[i]) {
    if (activePin == -1) {
        activePin = i;
    } else {
        nextPin = i;
        break;  // We found two adjacent touches
    }
    }
}

if (activePin != -1) {
    if (nextPin != -1 && nextPin == activePin + 1) {
    // Calculate interpolated value between activePin and nextPin
    int interpolatedValue = map(activePin, 0, NUM_TOUCH_PINS - 1, 0, 180);
    int nextInterpolatedValue = map(nextPin, 0, NUM_TOUCH_PINS - 1, 0, 180);
    return (interpolatedValue + nextInterpolatedValue) / 2;
    } else {
    // Return mapped value for single touch
    return map(activePin, 0, NUM_TOUCH_PINS - 1, 0, 180);
    }
}

return -1;  // No touch detected
}


test2

We created two additional sensors: a Potentiometer Sensor. a potentiometer is a variable resistor that adjusts resistance as you rotate it, allowing precise control over electrical signals. Issy turned it into a small handle wrapped in wool. We connected it to the circuit and used it with the sweep and up&down output. It performed well and was simple to build.

A LDR Sensor. For this, we used a soaker ball and embedded a LDR inside. The LDR reacts to light levels, allowing us to create a light-sensitive sensor. It worked flawlessly with both the sweep and up/down outputs.


stretch & squeeze sensor

For the Stretch/Squeeze Sensor, I began by knitting using conductive thread. To create the pattern seen in the knit, I used punch cards. I love knitting so much, and combining it with eletronics. I really want to continue practicing and maybe use it on my final project!

The goal was to use this touch sensor as an input to control the sweep (angle) output. The idea was that when a single pin is touched, the arm would adjust its angle accordingly. Additionally, the code was designed so that if two pins were touched simultaneously, the arm would calculate and move to an intermediate angle between the two pins corresponding positions.


I used the yarns available in the lab. For the first sensor (left), the yarns were too thick, resulting in a dense knit that didn't have stretch. The second sample performed better as I switched to thinner yarns, improving flexibility. Although the stretch improved, there’s still room for more.

In the video below, you can see the output result from this sensor. We were using the up&down arm, but the motion had a small and irregular range.


knitsample


touch sensor

For the touch sensor, we aimed to create a felted touch slider. Issy began by carding three different colors of wool to create a striped pattern. She used denim fabric as the base and placed it in embroidery hoops before starting the needle felting process.

Once the initial sample was complete, Issy made the fabric denser by adding more layers of carded felt, this time incorporating conductive fibers into specific parts of the striped pattern. To avoid creating a short circuit, she carefully left spaces between the stripes without conductive fibers.

Next, on another piece of denim fabric, Issy mapped out the areas where the conductive fibers were placed. I then used a sewing machine to stitch conductive fabric over those areas, allowing us to later solder wires to connect the sensor to the microcontroller. Finally, we positioned the felted sample on top of the sewn denim, aligning it with the conductive fabric beneath to complete the touch sensor setup.


feltsample


In the video above, you can see the sensor in action. We encountered several challenges with this setup, like reading the serial monitor due to the 7 touch pins. To make it more manageable, we made another code to isolate each pin’s readings and assigned them clear labels, which allowed us to better identify and find the threshold values for each touch pin.

We also faced issues with some of the connections, as the solder wasn’t making proper contact with the conductive fabric. We had to resolder those connections to ensure better contact.

Additionally, the serial monitor kept freezing, likely due to overloading the code with too much data at once. Fixing this issue was hard and we couldn't find a clear solution. We just tried closing the serial monitor or/and the code and reopen them.


audio sensor

For the Audio Sensor, we used a basic analog sound sensor and adjusted the threshold values to ensure the servo motor would respond appropriately.

Michelle calibrated the code by remapping the sensor's output based on the readings from the serial monitor, ensuring a stable resting value while requiring a louder sound to trigger the motor. However, we discovered that the sensor was most responsive when blown on or yelled at.


audio


outputs

test2

For the Outputs, we used 3D-printed prototype arms that Michelle had from the Tracks4Crafts project, which we integrated into the CNC machine. These served as our two main output functions.

  • Sweep: as the name suggests, this output controlled the arm's sweeping motion, with adjustable angles. Allows to maintain a consistent angle throughout the drawing, while adjusting the z-axis to ensure the brush made contact with the material. Also experimenting with various angles while drawing can give you an experimental outcome. The sweep has a fixed-range 180º servo motor.

  • Up&Down: was driven by pressure, by attaching the brush to the arm, we could vary the pressure applied during drawing, which can led to interesting, inconsistent patterns. For this arm, Michelle 3D-printed a linear actuator to convert the fixed-range 180º servo motor into a vertical movement system.


breadboardoutput


OUTPUTS (RECEIVER) CODE:

#include <esp_now.h>
#include <WiFi.h>
#include <ESP32Servo.h>

// Up and down servo control
const int servoPin = 2;  // Define the pin connected to the servo

// Sweeping servo control
const int servoPin2 = 3;  // Define the pin connected to the servo

// Revolving servo control
const int servoPin3 = 4;  // Define the pin connected to the servo

const int waterpumpPin = 5;  // Define the pin connected to the water pump

Servo pressureServo;    // Create a servo object
Servo sweepServo;       // Create a servo object
Servo revolutionServo;  // Create a servo object
int angle;              // Variable to store the servo angle

int sensorData1;
int sensorData2;
int touchAngle;
int sweep;
int ink;

// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
int id;  // must be unique for each sender board
int revolution;
int pressure;
int sweep;
int ink;
} struct_message;

// Create a struct_message called myData
struct_message myData;
struct_message boardsStruct[3];

// callback function that will be executed when data is received
void OnDataRecv(const esp_now_recv_info_t *recvInfo, const uint8_t *incomingData, int len) {
memcpy(&myData, incomingData, sizeof(myData));
// Serial.println(myData.revolution);


// Update the structures with the new incoming data
if (myData.id >= 1 && myData.id <= 3) {
    boardsStruct[myData.id - 1] = myData;

    // Serial.printf("revolution value: %s \n", boardsStruct[myData.id - 1].revolution);
    // Serial.printf("pressure value: %d \n", boardsStruct[myData.id - 1].pressure);
    // Serial.printf("sweep value: %d \n", boardsStruct[myData.id - 1].sweep);
    // Serial.println();
}
touchAngle = myData.revolution;
// Serial.println(myData.revolution);
sensorData1 = myData.pressure;
// Serial.println(myData.pressure);
sensorData2 = myData.sweep;
// Serial.println(myData.sweep);
ink = myData.ink;

if (myData.id == 1) {
    board1action();
} else if (myData.id == 2) {
    board2action();
}
}

void setup() {
// Allow allocation of all timers
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
pressureServo.setPeriodHertz(50);    // standard 50 hz servo
sweepServo.setPeriodHertz(50);       // standard 50 hz servo
revolutionServo.setPeriodHertz(50);  // standard 50 hz servo
pinMode(servoPin, OUTPUT);           // Set servo pin as output
pinMode(servoPin2, OUTPUT);          // Set servo pin as output
pinMode(servoPin3, OUTPUT);          // Set servo pin as output
pinMode(waterpumpPin, OUTPUT);       // Set servo pin as output

pressureServo.attach(servoPin);     // Attach the servo to its pin
sweepServo.attach(servoPin2);       // Attach the servo to its pin
revolutionServo.attach(servoPin3);  // Attach the servo to its pin
Serial.begin(115200);

// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);

// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
}

// Once ESPNow is successfully Init, we will register for recv CB to
// get recv packer info
esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));
}

void loop() {
board1action();
// Serial.println("test");
// // Set the angle of the servo
// Serial.println(" set to 10 ");
// pressureServo.write(10);  // Set the servo angle
// delay(2000);
// Serial.println(" set to 100 ");
// pressureServo.write(100);  // Set the servo angle
// delay(2000);

// Move the servo from 0 to 180 degrees
// for (int pos = 0; pos <= 180; pos += 1) {
//   pressureServo.write(pos);  // Move the servo to 'pos'
//   delay(15);           // Wait for 15ms for smooth movement (adjust if necessary)
// }

// // Move the servo from 180 back to 0 degrees
// for (int pos = 180; pos >= 0; pos -= 1) {
//   pressureServo.write(pos);  // Move the servo to 'pos'
//   delay(15);           // Wait for 15ms for smooth movement (adjust if necessary)
// }
}

void board1action() {
// Squeeze to pressure
if (sensorData1 > 1000) {
    int angle = map(sensorData1, 1000, 4095, 0, 180);  // 90-180 should be clockwise
    pressureServo.write(angle);                        // Set the servo angle
}
if (sensorData1 <= 1000) {
    pressureServo.write(0);  // Set the servo angle, this should stop it
}
Serial.print("Stretch to pressure: ");
Serial.println(sensorData1);
Serial.print("Sound to sweep: ");
Serial.println(sensorData2);
Serial.println(" ");
// Sound to sweep
// if (sensorData2 > 200) {
//   // Calculate sweep offset and speed
//   // int offset = map(sensorData2, 1000, 4095, 0, 90);    // Sweep offset from 0 to ±90
//   int sweepSpeed = map(sensorData2, 200, 4095, 100, 20);  // Faster sweep with higher sensorData2
//   int offset = 30;
//   // Sweep to 90 + offset
//   sweepServo.write(90 + offset);
//   delay(sweepSpeed);

//   // Sweep to 90 - offset
//   sweepServo.write(90 - offset);
//   delay(sweepSpeed);
// } else {
//   sweepServo.write(90);  // Return to center if sensorData2 is low
// }
sweep = map(sensorData2, 0, 4095, 0, 180);  // Faster sweep with higher sensorData2

Serial.println(sweep);
sweepServo.write(sweep);  // Return to center if sensorData2 is low

// Touch to revolution
revolutionServo.write(touchAngle);  // Set the servo angle

if (ink == 0) {
    digitalWrite(waterpumpPin, HIGH);
} else {
    digitalWrite(waterpumpPin, LOW);
}
delay(50);
}

// Not yet in use
void board2action() {
}


brushes

To create our brushes, we started by designing a brush holder to fit the specific diameter of the tube we were using to pump ink into the brush. We designed a cylindrical holder with an 8mm internal diameter, a 12mm external diameter, and a 70mm height.

Using the Boolean split command, we made the cylinder into a tube and added ribbing with 0.5mm width and 2mm height. This ribbing was designed to securely grip the sponge or other materials used for the brush heads, and we also used hot glue for extra stability.

rhino


We chose transparent PLA for 3D printing so we could see the liquid inside the tube. To use different brush types, we printed around 10 brush holders with varying heights, allowing us to test multiple configurations.

Here1 you can download the Rhino8 and PrusaSlicer files!


brushholder

brushes


For the brush heads, we mostly used sponges, cutting them into various shapes with scissors to have different outcomes. The sponge on the left was made from a highly absorbent, dense material, ideal for creating precise and well-defined brush strokes.

On the right, you can see a hand-drawn demonstration of the different brush movement we explored: up-and-down strokes, sweeping, revolving, and straight lines. The ink used throughout these trials was Ecoline.


machine setup

ARM SUPPORT

Issy and I designed on Rhino8, a support for the arms, which we used to attach them to the CNC machine. We weren’t allowed to use the milling bit (z-axis) to attach our setups, so we had to find an alternative way. We removed the skirt from the metal structure with two holes visible in the first image below, and used it to attach our haptic outputs.

Michelle started by making the first design, the on the left, on the top right image. From there, Issy and I refined the design to create the final support that would securely attach the arms.

For the up&down arm (shown in the bottom left image), we simply extended the original support, final measurements 75mmx90mm, and hot-glued the arm to the wood.

support

For the second arm (bottom right image), we started by measuring the hole diameter and the distance between the spacers that were already part of the arm. Of course, we had to make some adjustments, as the first attempt didn’t quite fit!

After recalculating the dimensions, we attached the arm to the wood piece using M3 screws (8mm length), M3 washers, and M3 nuts. For securing the wood piece to the machine, we used M4 screws (20mm length), M4 nuts, and M4 washers, the same we used for the up&down arm.

You can download the file here2!


BED PREPARATION

To protect the CNC machine’s sacrificial layer, we covered the working area with garbage bags from the lab, securing them in place with tape to prevent any liquid from seeping through. After our first test, we added another layer of plastic bags and also placed an acrylic sheet over the covered area.

machinebed


dye reservoir pump system

pump button


big button

Michelle later added a switch button to the input (sender) breadboard and updated the code, enabling us to control the dye reservoir pumping system. When the button is pressed, the aquarium pump draws ink from the squeeze bottle and sends it through the tube to the brush.

Issy and I then created a big button using neoprene fabric and conductive fabric, similar to the foil-based switch we made during E-textiles week. This "sandwich" method involves layering: neoprene fabric, conductive fabric, sponge material with five small holes in the center, followed by another layer of conductive fabric and neoprene.

To avoid short circuits, it's crucial to prevent the conductive fabrics from touching each other. Additionally, leave a trace to connect alligator clips, we used conductive tape for this.


PUMP SYSTEM AND OUTPUT ELETRONIC SETUP PLATE

Asli designed a laser-cut wood support in Rhino, which was secured around the machine's Z-axis using Velcro. This support was designed to accommodate the squeeze bottle and the aquarium pump.

The dye reservoir pumping system operates using a squeeze bottle connected to a water pump via a tube. Another tube runs from the water pump to the brush, allowing the dye to flow from the reservoir to the brush.

The support also includes space for the electronic setup that manages the outputs, ensuring everything is organized and accessible. You can download the file here3!

supportcnc

plugs

Asli also designed two corks for the squeeze bottle. The larger cork allows easy refilling, you can remove it and use a funnel to pour ink without disassembling the setup. The smaller cork serves as an air vent regulating airflow in and out of the bottle for the pressure. In the image on the left, you can see the progression of design attempts leading to the final successful one (shown on the right).

You can download the file here4.


shopbot code

  • Shopbot code and G-code are a similar language used to control CNC machines, 3D printers, and other automated tools. It consists of a series of commands that direct the machine's movements, speeds, and operations. The core of G-code is made up of G-codes (for movement commands like G0 for rapid positioning and G1 for linear movement) and M-codes (for machine functions such as starting the spindle or turning on coolant).

Also includes coordinates (X, Y, Z axes) to specify the machine’s position, as well as feed rates (F) for movement speed and spindle speeds (S). The program begins with a setup command (ex. G21 for millimeters) and ends with a stop command (ex. M30).


gh

Asli created the shopbot code for the project, where we aimed to design a tartan pattern with multiple colors. She used Rhino 8 and Grasshopper to generate the pattern, from the tartan file I had created with her during Computational Couture Week.

Asli explains the steps involved in the code, and we also had a class on how to modify G-codes. From this, we learned that G-code can be broken down into three key parts: the start code (includes instructions for initializing the machine and setting up the required conditions), the body (which defines the machine’s movements and operations), and the finish code (handles the machine's shutdown procedures).

You open the shopbot code in a text editor and save it with a .sbp extension.

You can download the Rhino8 and Grasshoper files here5!

gh


SHOPBOT CODE:

'----------------------------------------------------------------
'SHOPBOT ROUTER FILE IN MM
'GENERATED BY PARTWorks
'Minimum extent in X = 0.000 Minimum extent in Y = 0.000 Minimum extent in Z = -0.500
'Maximum extent in X = 830.000 Maximum extent in Y = 1550.000 Maximum extent in Z = 0.000
'Length of material in X = 830.000
'Length of material in Y = 1550.000
'Depth of material in Z = 0.500
'Home Position Information = Bottom Left Corner, Material Surface 
'Home X = 0.000000 Home Y = 0.000000 Home Z = 20.000000
'Rapid clearance gap or Safe Z = 6.000
'UNITS:MM
'
IF %(25)=0 THEN GOTO UNIT_ERROR 'check to see software is set to standard
SA                              'Set program to absolute coordinate mode
CN, 90
'New Path
'Toolpath Name = Profile 1
'Tool Name   = Fabribrush

&PWSafeZ = 6.000
&PWZorigin = Material Surface
&PWMaterial = 0.500
'&ToolName = "Fabribrush"
&Tool =1                    'Jog Z axis to safe height
C9
TR,18000
C6                   'Return tool to home in x and y
PAUSE 2
'
MS,60,50
JZ,20.000000
J2,0.000000,0.000000
M3,0,0,15
M3,42.5,50,15
M3,42.5,50,-0.01
M3,342.5,50,-0.01
M3,342.5,50,15
M3,42.5,65,15
M3,42.5,65,-0.01
M3,342.5,65,-0.01
M3,342.5,65,15
M3,42.5,80,15
M3,42.5,80,-0.01
M3,342.5,80,-0.01
M3,342.5,80,15
M3,42.5,95,15
M3,42.5,95,-0.01
M3,342.5,95,-0.01
M3,342.5,95,15
M3,42.5,125,15
M3,42.5,125,-0.01
M3,342.5,125,-0.01
M3,342.5,125,15
M3,42.5,140,15
M3,42.5,140,-0.01
M3,342.5,140,-0.01
M3,342.5,140,15
M3,42.5,155,15
M3,42.5,155,-0.01
M3,342.5,155,-0.01
M3,342.5,155,15
M3,42.5,185,15
M3,42.5,185,-0.01
M3,342.5,185,-0.01
M3,342.5,185,15
M3,42.5,200,15
M3,42.5,200,-0.01
M3,342.5,200,-0.01
M3,342.5,200,15
M3,42.5,230,15
M3,42.5,230,-0.01
M3,342.5,230,-0.01
M3,342.5,230,15
M3,42.5,245,15
M3,42.5,245,-0.01
M3,342.5,245,-0.01
M3,342.5,245,15
M3,42.5,260,15
M3,42.5,260,-0.01
M3,342.5,260,-0.01
M3,342.5,260,15
M3,50,42.5,15
M3,50,42.5,-0.01
M3,50,267.5,-0.01
M3,50,267.5,15
M3,65,42.5,15
M3,65,42.5,-0.01
M3,65,267.5,-0.01
M3,65,267.5,15
M3,80,42.5,15
M3,80,42.5,-0.01
M3,80,267.5,-0.01
M3,80,267.5,15
M3,95,42.5,15
M3,95,42.5,-0.01
M3,95,267.5,-0.01
M3,95,267.5,15
M3,125,42.5,15
M3,125,42.5,-0.01
M3,125,267.5,-0.01
M3,125,267.5,15
M3,140,42.5,15
M3,140,42.5,-0.01
M3,140,267.5,-0.01
M3,140,267.5,15
M3,155,42.5,15
M3,155,42.5,-0.01
M3,155,267.5,-0.01
M3,155,267.5,15
M3,185,42.5,15
M3,185,42.5,-0.01
M3,185,267.5,-0.01
M3,185,267.5,15
M3,200,42.5,15
M3,200,42.5,-0.01
M3,200,267.5,-0.01
M3,200,267.5,15
M3,230,42.5,15
M3,230,42.5,-0.01
M3,230,267.5,-0.01
M3,230,267.5,15
M3,245,42.5,15
M3,245,42.5,-0.01
M3,245,267.5,-0.01
M3,245,267.5,15
M3,260,42.5,15
M3,260,42.5,-0.01
M3,260,267.5,-0.01
M3,260,267.5,15
M3,275,42.5,15
M3,275,42.5,-0.01
M3,275,267.5,-0.01
M3,275,267.5,15
M3,305,42.5,15
M3,305,42.5,-0.01
M3,305,267.5,-0.01
M3,305,267.5,15
M3,320,42.5,15
M3,320,42.5,-0.01
M3,320,267.5,-0.01
M3,320,267.5,15
M3,335,42.5,15
M3,335,42.5,-0.01
M3,335,267.5,-0.01
M3,335,267.5,15
JZ,20.000000
J2,0.000000,0.000000
'
'Turning router OFF
C7
END
'
'
UNIT_ERROR:
CN, 91                            'Run file explaining unit error
END


modular box

For our Modular Box, Michelle used MakerCase, a online tool for designing custom laser-cut boxes. The website allows you to input dimensions, choose box styles (like finger joints or flat edges), and generate precise SVG files for laser cutting.

Michelle created a simple box designed to hide all the cables from our input breadboard, improving both the organization and aesthetics of our setup. She engraved labels to make the interface intuitive, guiding users in interacting with the machine and demystifying its functions.

You can download the file here6!


box

She designed a modular system using 4-pin and 10-pin connectors, making it easy to swap and integrate different sensors seamlessly. By incorporating headers, she created four connectors:

  • Angle Connector: Designed for the touch slider to control the sweep servo motor’s angle.
  • Pressure Connector: Compatible with multiple sensors, including the stretch sensor, LDR, and potentiometer, for responsive pressure control.
  • Revolve Connector: Allows sensors to manage the rotational motion of the servo motor.
  • Pump Connector: Used for connecting a digital switch to control the water pump, regulating ink flow into the system.


1st iteration

For our First Test, we were incredibly excited! We began by ensuring everything was set up properly. First, we tested the aquarium pump system before trying it on paper.

Next, we made sure the bed was fully covered and securely taped down the paper so it wouldn't shift during the test. We used the tartan pattern shopbot code and started it without the brush touching the paper, to observe how the machine would behave and determine if the speed settings were appropriate. After that, we calibrated all the machine axes and followed the safety guidelines outlined during Textile Scaffold Week.

Since we weren’t cutting through any hard materials, we didn't need to turn on the spindle or the vacuum. We didn't test the input and output haptic system in our initial trial, as our focus was ensuring that the drawing functionality worked as expected. We also hadn't finalized or perfected the performance of the inputs, so we wanted to prioritize confirming that the drawing process itself was functional.

When we pressed the start button, our hearts were full of excitement, and we were very pleased with the outcome. Although there were a few adjustments needed.


reflections

  • The water leaking from the brush was caused by an issue with the position of the reservoir relative to the discharge point (brushes). When the squeeze bottle was above the brushes, gravity caused the liquid to flow freely, which resulted in a significant amount of leaking.

However, when the squeeze bottle was placed below the brushes, we had the opposite effect. The pressure inside the bottle was reduced, and gravity helped to keep the liquid in place, stopping the leak.

  • We noticed that the ink was too watery and not vibrant. To address this, we added more Ecoline to the water mixture to increase the pigment concentration. Additionally, the paper we were using was not ideal for watercolor techniques, which further hindered the absorption of the ink.

  • It would be ideal to isolate the electronic setup on the wood support, as it's too close to the water pumping system, keeping liquids away from electronics is essential! Additionally, this isolation would improve cable management, making the setup cleaner and more organized.

  • Ensure all sensors are functioning properly, as we encountered several issues with input readings and the corresponding outputs. We need to focus on thoroughly analyzing the code and monitoring the Serial Monitor to better understand the sensor data.

  • We encountered a problem during one of our tests: the tube connecting the water pump to the brush wasn’t securely attached. Due to the pressure, it detached from the pump, causing ink to spray everywhere. We had to act quickly to stop it and clean up as best as we could. Thankfully, the nearby electronic setup wasn’t damaged and continued working. To prevent future incidents, we secured the tube to the water pump using tape and zip ties, ensuring it stays firmly in place.


2nd iteration

In our first test of our second iteration, we needed to modify the pumping system support, as highlighted in our first iteration reflections. The system was leaking ink while the machine moved to create the tartan pattern.

Rather than reconstructing the entire attachment for the pumping system and output electronics, saving time and materials, Asli made a more efficient solution. She laser-cut a small additional piece to securely attach the squeeze bottle beneath the aquarium pump and the discharge point, resolving the leakage issue.

test2


With Michelle’s help, we successfully got all the sensors working after extensive troubleshooting. Here are the key lessons we learned for electronic issues:

  • Measure your connections with a multimeter!! Regularly check your connections throughout the process to ensure everything is properly wired and functioning.
  • Carefully inspect your circuit at each stage, confirming that all connections are secure and correctly aligned. Using consistent wire colors for power, ground, and signal pins can help avoid confusion.
  • Errors can stem from the code. Read it multiple times and ensure it's correctly adapted to the sensors you're using.
  • Search online resources or ask ChatGPT for guidance when you're stuck.

In the end, all the sensors worked beautifully, and we had a fully functional, interactive modular system that was both reliable and fun to use!


test1

With all the sensors working perfectly, it was time to put the machine into action. Watching it operate was incredibly satisfying, and we felt proud of our hard work. For our first test, we didn’t use any sensors, instead, we focused on evaluating the pumping system with the big button. This allowed us to assess how much ink the pump was dispensing, helping us fine-tune the ink quantity and consistency.

This test also gave us a chance to recheck the pattern and ensure everything was functioning as expected. It was our first trial on fabric, using 100% cotton, which performed exceptionally well. To prevent excess ink from soaking through, we placed two sheets of paper beneath the fabric to absorb any overflow. Overall, the test was a success!




For our second test, we used a sensor to control the output for the first time. We paired the sweep arm with an LDR sensor to manage the brush strokes. This time, we switched from a felt pen to a more traditional brush stroke. While the strokes were quite random and the pattern became a bit abstract during the painting process, we absolutely loved the result!

The final outcome was not only beautiful but also deeply meaningful because of the work behind it. Even more rewarding was the fun we had throughout the process.

test2



fabrication files

I highly recommend checking out Issy’s documentation for her perception on this process! <3