Modular things drawing plotter
Written by Þórarinn
On my way to mín framtíð conference I meet up with Andri in Fab Lab Reykjavík. He was working on a drawing potter using modular things that was not working.
Andri had been working on the machine using these links:
Isuess
The machine is inconsistent we tyred drawing the letter B but it sometimes seams to work fine but then it does not complete the letter or goes of track.
The code has a UI where you can put in letter and the machine is suppose to draw what ever you put into it.
Code
Here is the code we are using:
const el = document.createElement("div");
el.style = `
padding: 10px;
`;
el.innerHTML = `
<button id="Home"> Home</button>
<p>
<button id="Draw"> DrawSquare</button>
<p>
<button id="DrawStar"> DrawStar</button>
<p>
<button id="PenUp"> Pen Up</button>
<p>
<button id="PenNeutral"> Pen Neutral</button>
<p>
<button id="PenDown"> Pen Down</button>
<p>
<input type="text" id="TextInput" placeholder="Enter text to write" />
<button id="WriteText"> Write Text</button>
<p>
<label for="xCoordinate">X:</label>
<input type="number" id="xCoordinate" style="width: 50px;" />
<label for="yCoordinate">Y:</label>
<input type="number" id="yCoordinate" style="width: 50px;" />
<button id="goToCoord">Move to Coordinate</button>
<p>
<button id="MoveUp"> UP </button>
<button id="MoveDown"> DOWN </button>
<button id="MoveLeft"> LEFT </button>
<button id="MoveRight"> RIGHT </button>
`;
el
.querySelector("#Home")
.addEventListener("click", () => {
goToHome();
});
el
.querySelector("#Draw")
.addEventListener("click", () => {
delay(100);
draw();
});
el
.querySelector("#DrawStar")
.addEventListener("click", () => {
delay(100);
drawStar();
});
el
.querySelector("#PenUp")
.addEventListener("click", () => {
delay(100);
penUp();
});
el
.querySelector("#PenNeutral")
.addEventListener("click", () => {
delay(100);
penNeutral();
});
el
.querySelector("#PenDown")
.addEventListener("click", () => {
delay(100);
penDown();
});
el
.querySelector("#WriteText")
.addEventListener("click", async () => {
const input = el.querySelector("#TextInput").value;
if (input.trim()) {
await writeText(input.trim());
} else {
console.log("Please enter text.");
}
});
el
.querySelector("#goToCoord")
.addEventListener("click", () => {
const x = parseInt(document.querySelector("#xCoordinate").value, 10);
const y = parseInt(document.querySelector("#yCoordinate").value, 10);
if (!isNaN(x) && !isNaN(y)) {
goToCoordinate(x, y);
} else {
console.log("Invalid coordinates. Please enter valid numbers.");
}
});
render(el);
// Creating the Servo
let servo = servo1;
console.log("setting calibration for Servo");
servo.setCalibration([700, 2300], [-10, 180]);
// Servo has been set up
motorA.setCurrent(1);
motorA.setStepsPerUnit(9);
motorB.setCurrent(1);
motorB.setStepsPerUnit(9);
const machine = createSynchronizer([motorA, motorB]);
machine.setPosition([0, 0]);
// Current position tracker
let currentX = 0;
let currentY = 0;
// Step size for movements
const stepSize = 4;
// Movement functions
async function moveUp() {
currentY += stepSize;
await goTo(currentX, currentY);
}
async function moveDown() {
currentY -= stepSize;
await goTo(currentX, currentY);
}
async function moveLeft() {
currentX -= stepSize;
await goTo(currentX, currentY);
}
async function moveRight() {
currentX += stepSize;
await goTo(currentX, currentY);
}
// Attach event listeners to the directional buttons
el.querySelector("#MoveUp").addEventListener("click", async () => {
console.log("Moving UP");
await moveUp();
});
el.querySelector("#MoveDown").addEventListener("click", async () => {
console.log("Moving DOWN");
await moveDown();
});
el.querySelector("#MoveLeft").addEventListener("click", async () => {
console.log("Moving LEFT");
await moveLeft();
});
el.querySelector("#MoveRight").addEventListener("click", async () => {
console.log("Moving RIGHT");
await moveRight();
});
// Write text func
const charMap = {
A: [
[0, 0], [0, 4], // Vertical left
[0, 4], [4, 8], // Left diagonal up
[4, 8], [8, 4], // Right diagonal down
[8, 4], [8, 0], // Vertical right
[0, 4], [8, 4], // Horizontal middle
],
B: [
[0, 0], [0, 8], // Vertical left
[0, 8], [4, 8], // Top horizontal
[4, 8], [6, 6], // Upper curve
[6, 6], [4, 4], // Middle curve
[4, 4], [6, 2], // Lower curve
[6, 2], [4, 0], // Bottom curve
[4, 0], [0, 0], // Vertical left bottom
[0, 4], [4, 4], // Horizontal middle
],
H: [
[0, 0], [0, 8], // Vertical left
[0, 4], [8, 4], // Horizontal middle
[8, 0], [8, 8], // Vertical right
],
E: [
[0, 0], [0, 8], // Vertical left
[0, 8], [8, 8], // Top horizontal
[0, 4], [8, 4], // Middle horizontal
[0, 0], [8, 0], // Bottom horizontal
],
L: [
[0, 0], [0, 8], // Vertical
[0, 0], [8, 0], // Bottom horizontal
],
O: [
[0, 0], [0, 8], // Left vertical
[0, 8], [8, 8], // Top horizontal
[8, 8], [8, 0], // Right vertical
[8, 0], [0, 0], // Bottom horizontal
],
W: [
[0, 8], [2, 0], // First leg
[2, 0], [4, 6], // Peak
[4, 6], [6, 0], // Trough
[6, 0], [8, 8], // Last leg
],
R: [
[0, 0], [0, 8], // Vertical
[0, 8], [6, 8], // Top horizontal
[6, 8], [6, 4], // Top diagonal
[6, 4], [0, 4], // Mid horizontal
[0, 4], [6, 0], // Bottom diagonal
],
D: [
[0, 0], [0, 8], // Vertical
[0, 8], [6, 8], // Top curve
[6, 8], [8, 6], // Top-right curve
[8, 6], [8, 2], // Bottom-right curve
[8, 2], [6, 0], // Bottom curve
[6, 0], [0, 0], // Bottom horizontal
],
};
async function writeText(text) {
console.log(`Writing text: ${text}`);
await penUp();
let xOffset = 0; // Start X offset for the first letter
const yOffset = 0; // Fixed Y offset
for (const char of text.toUpperCase()) {
if (charMap[char]) {
const pts = charMap[char];
console.log(`Drawing letter: ${char}`);
for (let i = 0; i < pts.length; i += 2) {
const [x1, y1] = pts[i];
const [x2, y2] = pts[i + 1];
await goTo(xOffset + x1, yOffset + y1); // Move to the start point
delay(1000);
await penDown(); // Start drawing
await goTo(xOffset + x2, yOffset + y2); // Draw the line
delay(1000);
await penUp(); // Lift the pen
}
xOffset += 10; // Space between letters (8 for the letter width + 2 for spacing)
} else {
console.log(`Character "${char}" is not mapped.`);
xOffset += 10; // Skip unsupported characters with spacing
}
}
}
// Function to move to specified coordinates
async function goToCoordinate(x, y) {
console.log(`Moving to coordinate: (${x}, ${y})`);
await penUp(); // Lift the pen before moving
await goTo(x, y); // Use the existing goTo function to move
console.log(`Arrived at (${x}, ${y})`);
}
// Pen functions
let penDown = async () => {
servo.writeAngle(72);
};
let penUp = async () => {
servo.writeAngle(130);
};
// Home function
async function goToHome() {
while (await motorA.getLimitState()) {
motorA.velocity(10);
motorB.velocity(10);
}
while (await motorB.getLimitState()) {
motorA.velocity(10);
motorB.velocity(-10);
}
motorA.velocity(0);
motorB.velocity(0);
machine.setPosition([0, 0]);
await delay(1000);
goTo(3, 3);
machine.setPosition([0, 0]);
await delay(1000);
}
// Movement function
async function goTo(x, y) {
console.log(`Moving to (${x}, ${y})`);
await machine.absolute([-x - y, -x + y]);
}
// Shapes
var pts = [
[50, 0],
[50, 50],
[0, 50],
[0, 0],
];
var ptsStar = [
[20, 10],
[30, 50],
[40, 10],
[10, 40],
[50, 40],
[20, 10],
];
async function draw() {
await goTo(0, 0);
await delay(200);
await penDown();
for (let i = 0; i < pts.length; i++) {
await goTo(pts[i][0], pts[i][1]);
await delay(200);
}
await penUp();
}
async function drawStar() {
await goTo(20, 10);
await penDown();
for (let i = 0; i < ptsStar.length; i++) {
await goTo(ptsStar[i][0], ptsStar[i][1]);
await delay(200);
}
await penUp();
}
await penUp();
await goToHome();
await goTo(3,3);
trouble shotting
We added print statements to try to figure out where and if the code is the problem. Andri also figured that adjusting the pen up pen down function changes the results. When we tyred drawing the shape they seam to work with out trouble so we took some of the letter code and put it in the shape section to see what would happen. It was still making error when we did that.
We then looked into understanding better when and how the code is lifting the pen up and down. We figured that the drawing function might need some improvement. It's suppose to lift up the pen when ever the cordites are in a different location from the last one. When we tyred a new drawing function we sort of got a new font and it still was not working but at least we were onto something.
Moving on
Since we did't really figure out how to make the letter's perfect we talked about some concepts.
abstract line art
Something that could just run and be interesting. We talk about using halftoner but the link on the site is no longer working.
Letters
Can be used to show case how things are working. We also looked into if we can go from Inkscape to plotting using XML viewer or turn g-code into JavaScript plot code. This site also seams useful: https://shinao.github.io/PathToPoints/
Maze
Can we make the machine draw a maze and then draw a part thought it.
- just make the machine do a maze
- make the machine solve it
With tools like JSfiddle and Codepen iy's possible do see the plot this might be useful to understand how the drawing function is working.
Maze code
const canvas = document.getElementById("mazeCanvas");
const ctx = canvas.getContext("2d");
const cellSize = 20; // Scale factor
const mazePath = [
[0, 0], [0, 2], [1, 2], [1, 1], [2, 1], [2, 2], [3, 2], [3, 0], [4, 0], [4, 1],
[5, 1], [5, 0], [7, 0], [7, 1], [8, 1], [8, 0], [9, 0], [9, 1], [11, 1], [11, 0],
[13, 0], [13, 2], [12, 2], [12, 3], [11, 3], [11, 2], [10, 2], [10, 4], [13, 4], [13, 5],
[18, 5], [18, 7], [19, 7], [19, 8], [18, 8], [18, 9], [17, 9], [17, 6], [14, 6], [14, 7],
[13, 7], [13, 6], [12, 6], [12, 5], [11, 5], [11, 6], [10, 6], [10, 7], [12, 7], [12, 8],
[16, 8], [16, 7], [15, 7], [16, 9], [16, 10], [15, 10], [15, 9], [13, 9], [13, 10], [11, 10],
[11, 9], [4, 9], [4, 5], [5, 5], [5, 4], [7, 4], [7, 3], [8, 3], [8, 2], [4, 2],
[4, 3], [2, 3], [2, 4], [1, 4], [1, 3], [0, 3], [0, 5], [3, 5], [3, 4], [4, 4],
[3, 6], [0, 6], [0, 8], [1, 8], [1, 7], [3, 7], [3, 8], [2, 8], [2, 9], [3, 9],
[3, 13], [1, 13], [1, 14], [0, 14], [0, 12], [1, 12], [1, 11], [0, 11], [0, 10], [1, 10],
[1, 9], [0, 9], [2, 10], [2, 12], [0, 15], [1, 15], [1, 16], [0, 16], [0, 18], [1, 18],
[1, 17], [2, 17], [2, 18], [3, 18], [3, 19], [4, 19], [4, 18], [5, 18], [5, 17], [4, 17],
[4, 15], [3, 15], [3, 16], [2, 16], [2, 14], [5, 14], [5, 16], [7, 16], [7, 17], [6, 17],
[6, 19], [9, 19], [9, 18], [7, 18], [8, 17], [8, 16], [10, 16], [10, 14], [9, 14], [9, 13],
[11, 13], [11, 11], [10, 11], [10, 10], [9, 10], [9, 12], [8, 12], [8, 13], [6, 13], [6, 12],
[4, 12], [4, 11], [5, 11], [5, 10], [8, 10], [8, 11], [6, 11], [7, 12], [4, 10], [4, 13],
[5, 13], [6, 14], [6, 15], [8, 15], [8, 14], [7, 14], [9, 15], [10, 12], [12, 11], [12, 16],
[11, 16], [11, 17], [13, 17], [13, 13], [14, 13], [14, 14], [15, 14], [15, 12], [19, 12], [19, 9],
[18, 10], [17, 10], [17, 11], [14, 11], [14, 12], [13, 12], [13, 11], [14, 10], [19, 13], [19, 15],
[18, 15], [18, 17], [17, 17], [17, 18], [19, 18], [19, 19], [16, 19], [16, 18], [15, 18], [15, 17],
[14, 17], [14, 19], [13, 19], [13, 18], [10, 18], [10, 17], [9, 17], [10, 19], [15, 19], [14, 16],
[14, 15], [16, 15], [16, 16], [17, 16], [17, 14], [18, 14], [18, 13], [16, 13], [16, 14], [15, 16],
[16, 17], [19, 17], [19, 16], [5, 19], [3, 17], [2, 19], [0, 19], [6, 1], [9, 2],
[9, 4], [8, 4], [8, 5], [10, 5], [9, 6], [9, 8], [8, 8], [8, 7], [6, 7], [6, 8],
[5, 8], [5, 6], [6, 6], [6, 5], [7, 5], [7, 6], [19, 6], [19, 4], [17, 4], [17, 3],
[16, 3], [16, 4], [14, 4], [14, 2], [15, 2], [15, 1], [17, 1], [17, 0], [14, 0], [14, 1],
[18, 0], [19, 0], [19, 1], [18, 1], [18, 2], [19, 2], [19, 3], [18, 3], [17, 2], [16, 2],
[15, 3], [13, 3]
];
function drawMaze(path) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
// Move to the first point
ctx.moveTo(path[0][0] * cellSize, path[0][1] * cellSize);
// Draw the continuous path
for (let i = 1; i < path.length; i++) {
let [x, y] = path[i];
ctx.lineTo(x * cellSize, y * cellSize);
}
ctx.stroke();
}
// Draw the maze
drawMaze(mazePath);
Return of the the TeaManator
Sine time was running out it was decided to fall back to the good old TeaManator from 2024. The machine was hooked up and worked flawlessly after the good rest.