TThis project proposes an alternative method of digital fabrication based on the dissolution of materials using abrasive liquids. While identifying suitable, low-impact, and non-toxic materials is a critical and extensive research topic on its own—and essential for validating the project's long-term feasibility and environmental relevance—this initial stage will focus primarily on developing the mechanical and electronic aspects of the system. The aim is to explore and understand the operational boundaries and potential of a liquid-based subtractive machine, investigating how controlled fluid dynamics can serve as a precise and programmable tool for material removal. This approach shifts the paradigm of fabrication away from traditional mechanical or thermal methods, opening a new experimental path that merges chemistry, engineering, and digital design.
Further development stages will include material testing and environmental evaluation, but for now, the priority is to build a working prototype that allows for iterative testing, learning, and refinement of the core subtractive process.
It is difficult to find projects that are very close to the one I am proposing, or even ones that yield similar results. What I appreciate is that it fits within a current of non-productive fabrication—it feels defiant and resistant. However, as part of the exploration, it is important to build bridges with examples that engage in more iterative and less clearly defined acts of subtraction. Among them, Michael Hansmeyer stands out. His projects speculate on voids reminiscent of caverns and surfaces shaped by time. Likewise, there are technical precedents such as acid etching used for sculpting metallic plates, which operate through gradual, subtractive processes.
TThe first mechanical concept of the project embraces lightness as a core design principle, replacing the traditional Y-axis with a wheel-based system that allows the machine to move endlessly across the chosen material. More than a functional gesture, this setup creates a playful tension between mechanization as an act of precision and a more spontaneous, ludic approach. The wheels support a gantry structure through which the X-axis moves, and mounted on this bridge is the toolhead, responsible for ejecting the abrasive liquid. Initially conceived as a self-contained autonomous machine, it could eventually evolve into a battery-powered mobile system. For mechanical motion, a key reference is the Creality CR-30 3D printer, whose conveyor belt-style print bed enables theoretically infinite movement along one axis. For liquid ejection, the design takes inspiration from micropipettes used in medical laboratories, which deliver precise quantities of liquid to targeted points with high accuracy.
The objective of this week is to establish the basic tools—both in terms of materials and knowledge—needed to successfully carry out the development of the final project. To achieve this, we will set certain guidelines starting from the project's concept up to its current stage of development.
The final project consists of a Cartesian machine capable of dispensing corrosive liquids, such as thinner, that can dissolve materials like polystyrene to create a subtractive fabrication technique with artistic finishes.
To select the best set of components and the necessary knowledge for the project, it is proposed to explain how the device works.
The following diagram demonstrates the knowledge applied at each stage of the project's conception, fabrication, and programming process.
It is a portable Cartesian device designed to eject various liquids with the intention of becoming a subtractive manufacturing method that does not rely on force, but rather uses chemical reaction as the agent for material removal.
The closest precedents of a portable CNC can probably be found in the Shaper Origin, a wood machining tool that, through a camera and screen, can guide the cutting path. However, there is no version that combines these properties of automation or assisted material subtraction with the ejection systems found in bioprinters. This is where the advantage of 'the Machine that Cries' comes into play.
"I will design a lightweight and portable casing for a transmission system based on smooth rods and belts, which enables the movement of a head coordinated by two stepper motors. Using a servo motor in a remote ejection system, it allows the liquid to be dispensed onto the surface.
I will use two Step Motor Drivers, two NEMA 17 stepper motors, an SG-20 servo motor, a Seeed XIAO, a 12V 10A power supply, a voltage regulator and four capacitors, along with a system of belts and smooth rods.
The Seeed XIAO microcontroller and some components, such as the fiberglass PCB base and pin headers, are part of the equipment provided by the university. The rest of the components were purchased privately.
Approximately $75 was spent on electronics, another $40 on mechanical parts, and around $25 on the fabrication of components, resulting in a total investment of $140.
The containers for electronic components, such as the printer case (which will be 3D printed) and the power supply enclosure (CNC machined), will be fabricated. Likewise, the machined plate that organizes the stepper motor drivers will also be manufactured by me.
Digital fabrication methods will be used, such as 3D printing, CNC machining in acrylic, and CNC machining for the fiberglass plate used in the electronics.
Extensive research must be conducted on the material aspect. The interaction between thinner and polystyrene is highly toxic and potentially polluting. While the mechanism is clever and highly expressive, it would be ideal to replace it with organic materials such as formic acid or similar substances applied to naturally sourced materials.
It is necessary to evaluate the coherence between the proposed subtractive manufacturing method and portability. Iterations can be made to improve performance.
The electronics and manufacturing parts are already completed; only the programming and testing stages remain
All the fabrication work (physical, tangible, so to speak) has been completed, but the programming part has only remained in the research phase, looking for a G-code firmware suitable for the memory space of the Seeed XIAO.
It is urgently necessary to determine which firmware is the most suitable. From what has been read so far, Tiny-G will be chosen; however, learning to coordinate servomotors with stepper motors for the project is still pending.
The project can be completed in its first iteration and open the field for experimentation with other automation methods for this subtractive manufacturing system.
his exercise has been very helpful for sharpening my knowledge of 3D modeling oriented toward mechanical systems. Additionally, I have learned a lot about the functioning of microcontrollers, from their design to their electrical operation. Finally, I have been exposed to less typical manufacturing methods which, although not used in this exercise, remain as possibilities for future iterations.
The functional process of the project is explained in the following diagram.
The project was modeled at the component level for fabrication and was entirely designed in Rhinoceros.
The design of the outer casing served the purpose of positioning the internal components and supporting loads without deflecting, as these are plastic prints that, due to their anisotropic characteristics, may undergo changes in shape.
For the transmission of motion from the stepper motors, toothed drive pulleys are installed, which mesh with belts and slide designed and printed parts, enabling guided mobility through linear bearings.
The servomotor is positioned to be converted into a crank-connecting rod mechanism that allows progressively pushing the syringe plunger every 5 degrees.
The head serves the function of channeling the liquid through a cannula, which is positioned between linear bearings that couple with rods to the metal axes for movement in the X and Y directions.
The PCB design was developed in the electronics section of Autodesk Fusion.
The PCB machining process will be carried out using Aspire as the G-code processing software and will be executed on the Roland MDX-540 on a fiberglass board."
To incorporate the PCB into the design, its measurements are taken, and a housing is printed to hold it inside the machine.
Initially, the plan was to install a G-code interpreter like GRBL on the Seeeduino, but it faced compilation issues. Alternatives such as TinyG or FluidNC were explored, but they lacked support for the ESP32C3 model, so it was decided to design a basic G-code interpreter for the project.
Include the Servo.h library to control the servomotor. Define the pins of the XIAO ESP32C3: D2-D5 for STEP and DIR of the X and Y motors, D6 for ENABLE (activates the drivers), D7-D8 for limit switches, and D9 for the servo signal. Set position limits (0 to 600 steps for X and Y) and motor parameters: 200 steps per revolution, microstepping 1, and speed of 60 RPM. Declare global variables: servo (Servo object), stepDelay (delay between steps), currentPositionX and currentPositionY (current position in steps), currentServoAngle (servo angle, initial 0°), servoInitialized (indicates if the servo is activated), inputString (stores serial commands), and stringComplete (indicates complete command).
#include // Definición de pines para el XIAO ESP32C3 #define STEP_PIN_X 2 // D2: STEP eje X #define DIR_PIN_X 3 // D3: DIR eje X #define STEP_PIN_Y 4 // D4: STEP eje Y #define DIR_PIN_Y 5 // D5: DIR eje Y #define ENABLE_PIN 6 // D6: ENABLE común #define LIMIT_MIN_X 7 // D7: Final de carrera mínimo X #define LIMIT_MIN_Y 8 // D8: Final de carrera mínimo Y #define SERVO_PIN 9 // D9: Señal del servo // Límites de posición #define MAX_POSITION_X 600 // Tope máximo X en pasos #define MAX_POSITION_Y 600 // Tope máximo Y en pasos #define MIN_POSITION_X 0 // Tope mínimo X #define MIN_POSITION_Y 0 // Tope mínimo Y // Parámetros de los motores #define STEPS_PER_REVOLUTION 200 // Pasos por revolución #define MICROSTEPPING 1 // Microstepping #define SPEED 60 // Velocidad en RPM // Variables Servo servo; // Objeto para el servomotor int stepDelay; // Retardo entre pasos (µs) long currentPositionX = 0; // Posición actual X long currentPositionY = 0; // Posición actual Y int currentServoAngle = 0; // Ángulo inicial servo bool servoInitialized = false; // Estado del servo String inputString = ""; // Cadena para comando boolean stringComplete = false; // Comando completo
Configure the motor pins (D2-D5 for STEP/DIR, D6 for ENABLE) as outputs and enable the drivers by setting ENABLE to LOW. Set the limit switch pins (D7, D8) as inputs with internal pull-up resistors (reading LOW when activated). Configure the servo pin (D9) as an output and initialize it to LOW. Start serial communication at 9600 baud, print a welcome message with the initial position (X=0, Y=0, Servo=0°), the limits (X/Y: 0-600 steps, Servo: 0-180°), and instructions for sending G-code commands (e.g., G1 X100 Y200 A90). Calculate stepDelay (in µs) based on the motor parameters to control the stepping speed.
void setup() { pinMode(STEP_PIN_X, OUTPUT); pinMode(DIR_PIN_X, OUTPUT); pinMode(STEP_PIN_Y, OUTPUT); pinMode(DIR_PIN_Y, OUTPUT); pinMode(ENABLE_PIN, OUTPUT); digitalWrite(ENABLE_PIN, LOW); // Habilitar motores pinMode(LIMIT_MIN_X, INPUT_PULLUP); pinMode(LIMIT_MIN_Y, INPUT_PULLUP); pinMode(SERVO_PIN, OUTPUT); digitalWrite(SERVO_PIN, LOW); Serial.begin(9600); Serial.println("Control de motores de pasos (X, Y) y servo con fines de carrera"); Serial.println("Posición inicial: X=0, Y=0, Servo=0 grados"); Serial.println("Límites: X[0, 600], Y[0, 600], Servo[0, 180]"); Serial.println("Ingrese comandos G (ejemplo: G1 X100 Y200 A90)"); stepDelay = 60000000 / (STEPS_PER_REVOLUTION * MICROSTEPPING * SPEED); }
The main loop checks if a complete serial command has been received (stringComplete == true). If so, it calls processGCode to process the command, clears the inputString, resets stringComplete to false, and prints a message indicating it is ready for the next command. This simple loop ensures the program responds to serial commands in an orderly manner.
void loop() { if (stringComplete) { processGCode(); inputString = ""; stringComplete = false; Serial.println("Listo para el siguiente comando"); } }
It runs automatically when data is available on the serial port. It reads each received character, adds it to inputString, and detects the end of a command upon receiving a newline (\n), setting stringComplete to true. This allows the loop to process the complete command (e.g., G1 X100 Y200 A90).
void serialEvent() { while (Serial.available()) { char inChar = (char)Serial.read(); inputString += inChar; if (inChar == '\n') { stringComplete = true; } } }
For stepper motors, processGCode checks if the G-code command starts with G1. It extracts X and Y target positions using indexOf and substring, converting them to integers with toInt. Positions are clamped to 0-600 steps (defined by MAX_POSITION_X/Y and MIN_POSITION_X/Y). If X or Y is specified, it calls moveToPosition to move the motors to the target coordinates and prints the updated X/Y positions.
if (inputString.startsWith("G1 ")) { long targetPositionX = currentPositionX; long targetPositionY = currentPositionY; int xIndex = inputString.indexOf('X'); if (xIndex != -1) { int xEnd = inputString.indexOf(' ', xIndex); if (xEnd == -1) xEnd = inputString.length(); String xValueStr = inputString.substring(xIndex + 1, xEnd); targetPositionX = xValueStr.toInt(); if (targetPositionX > MAX_POSITION_X) targetPositionX = MAX_POSITION_X; else if (targetPositionX < MIN_POSITION_X) targetPositionX = MIN_POSITION_X; } int yIndex = inputString.indexOf('Y'); if (yIndex != -1) { int yEnd = inputString.indexOf(' ', yIndex); if (yEnd == -1) yEnd = inputString.length(); String yValueStr = inputString.substring(yIndex + 1, yEnd); targetPositionY = yValueStr.toInt(); if (targetPositionY > MAX_POSITION_Y) targetPositionY = MAX_POSITION_Y; else if (targetPositionY < MIN_POSITION_Y) targetPositionY = MIN_POSITION_Y; } if (xIndex != -1 || yIndex != -1) { moveToPosition(targetPositionX, targetPositionY); } Serial.print("Movido a X: "); Serial.print(currentPositionX); Serial.print(" Y: "); Serial.print(currentPositionY); }
For the servo, processGCode extracts the target angle (A) from the G-code using indexOf and substring, converting it to an integer. If an angle is specified, it initializes the servo with servo.attach (if not already done) and sets the angle using servo.write. The currentServoAngle is updated, and the new angle is printed. Servo control is independent of stepper movement.
if (inputString.startsWith("G1 ")) { int targetServoAngle = currentServoAngle; int aIndex = inputString.indexOf('A'); if (aIndex != -1) { int aEnd = inputString.indexOf(' ', aIndex); if (aEnd == -1) aEnd = inputString.length(); String aValueStr = inputString.substring(aIndex + 1, aEnd); targetServoAngle = aValueStr.toInt(); } if (aIndex != -1 && !servoInitialized) { servo.attach(SERVO_PIN); servoInitialized = true; } if (aIndex != -1 && servoInitialized) { servo.write(targetServoAngle); currentServoAngle = targetServoAngle; } Serial.print(" Servo: "); Serial.println(currentServoAngle); }
Moves steppers to target X/Y positions (in steps). Calculates steps needed, sets direction pins (DIR_X, DIR_Y), and enables motors (ENABLE LOW). In a loop, generates STEP pulses (HIGH then LOW) for each axis, stopping if limit switches (X/Y) are triggered. Updates current positions based on direction and steps moved.
void moveToPosition(long targetPositionX, long targetPositionY) { long stepsToMoveX = abs(targetPositionX - currentPositionX); long stepsToMoveY = abs(targetPositionY - currentPositionY); bool dirX = targetPositionX >= currentPositionX ? HIGH : LOW; bool dirY = targetPositionY >= currentPositionY ? LOW : HIGH; digitalWrite(DIR_PIN_X, dirX); digitalWrite(DIR_PIN_Y, dirY); digitalWrite(ENABLE_PIN, LOW); long maxSteps = max(stepsToMoveX, stepsToMoveY); for (long i = 0; i < maxSteps; i++) { if (dirX == LOW && digitalRead(LIMIT_MIN_X) == LOW && i < stepsToMoveX) { Serial.println("Final de carrera X mínimo alcanzado"); stepsToMoveX = i; } if (dirY == HIGH && digitalRead(LIMIT_MIN_Y) == LOW && i < stepsToMoveY) { Serial.println("Final de carrera Y mínimo alcanzado"); stepsToMoveY = i; } if (i < stepsToMoveX) { digitalWrite(STEP_PIN_X, HIGH); delayMicroseconds(stepDelay / 2); digitalWrite(STEP_PIN_X, LOW); } if (i < stepsToMoveY) { digitalWrite(STEP_PIN_Y, HIGH); delayMicroseconds(stepDelay / 2); digitalWrite(STEP_PIN_Y, LOW); } delayMicroseconds(stepDelay / 2); } currentPositionX += dirX == HIGH ? stepsToMoveX : -stepsToMoveX; currentPositionY += dirY == LOW ? stepsToMoveY : -stepsToMoveY; }
"To consider the project completed, its functionality is tested by positioning the head in different locations and ejecting the liquid onto the foam.
The final explanation of the project was summarized and presented through the following slide and video
A great tool for determining which license best suits my project is the Creative Commons License Chooser , available on the official Creative Commons website. By answering a brief set of questions, the platform helps guide users to the most appropriate license according to their preferences regarding attribution, commercial use, and derivative works. The License Chooser simplifies the process of selecting the right license by asking about key factors, such as: - Whether you want to allow others to remix, adapt, or build upon your work. - If you want to permit commercial use of your work. - Whether derivative works should be shared under the same terms (ShareAlike). Once the questions are answered, the tool generates the most suitable license, along with the appropriate icon, legal code, and a ready-to-use badge or snippet that can be embedded in a website or attached to digital content. This makes the process transparent and accessible, even for users without legal expertise. Using the License Chooser is highly recommended for creators who want to clearly communicate how their work can be used while still retaining certain rights.
PCB Fusion File Rhino Machine Model Arduino Script Printables Pieces in 3mf file