Skip to content

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.

  1. just make the machine do a maze
  2. 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

Maze code

<canvas id="mazeCanvas" width="400" height="400"></canvas>
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.