DEV Community

ATSU
ATSU

Posted on • Edited on

Creating Kerf-Checker tool with Vite, Vue, and Flask

Image description

Here is the code, App test video

In the article titled [Web Development with Vite, Vue, and Flask] I detailed the process of creating a web application template with a Vue and a Python backend. The application, Kerf Tool Test Fit Guides Generator, is designed to assist in determining the precise kerf needed for materials that will be joined together through laser cutting or CNC machining, particularly useful in furniture design and fabrication.

Overview

Purpose
It's a tool to determine the right kerf adjustments for materials before cutting, aiming to achieve a snug fit without the need for adhesives.

Functionality
Users enter various parameters like start width, pitch, gap, number of kerfs, and blade diameter. The application then generates a downloadable SVG file that visually represents the kerfs to be tested.

User Interface
Developed in Vue, the application provides a simple form where users input the necessary parameters and receive immediate visual feedback, including error messages if the input values are not within acceptable ranges.

Python Backend Processing
The Flask backend handles the SVG generation logic. It receives parameters from the frontend, validates them (e.g., ensuring the pitch is not too small), and then either returns an SVG file or an error message.

Laser Cutting Specifics
For those using laser cutters, the blade diameter input can be set to zero, as the kerf adjustment takes into account the laser's precision, which does not require accounting for a physical blade's width.

You can run the application with the following command

git clone https://github.com/ATSU3/kerf-tool-test-fit-guides-generator.git
Enter fullscreen mode Exit fullscreen mode
cd kerf-tool-test-fit-guides-generator
yarn
yarn build
flask run
Enter fullscreen mode Exit fullscreen mode

What is Kerf Check tool?

The Kerf Check tool is designed to ensure parts cut by laser or CNC fit together perfectly. It's essential for making press-fit kits where the right fit is key to holding everything together. For example, with 3 mm boards, matching slots can be too loose. This tool helps find the best fit by letting users test different gaps with an SVG file, for a tight, secure assembly.

Image description

Image description

If you are using a laser cutting machine, enter 0 for blade-diameter: since you do not need to add a T-bone filet (since you do not need to consider the bit diameter).

Image description

I've found that 2.65mm is just right for 3mm cardboard, so it will hold firm when we finally create a Press-fit -construction like this one.

Image description

Image description

When machining with CNC, it is not possible to make right angles, so giving them grooves that are larger than the radius of the drill, and we make escape routes for the drill by adding t-bone filet.

Image description

Image description

Image description

I found 15.3 to be the appropriate value when connecting a 15 mm lumber-core

Code

Here is the code

The app uses Vue.js for the user interface and relies on Python for SVG calculations. This way, as the app gets more complex, we can make the most of Vue for the design and Python for the heavy math.

App.vue

This code generate custom SVG images based on user-provided parameters. It features a user interface where users can input values for the starting width, pitch, gap, number of kerfs, and blade diameter.

Clicking the "Create SVG" button, the app validates the pitch value and, if it meets the required minimum, sends a request to the server to generate the SVG. If the pitch is too small, an error message is displayed immediately, enhancing the user experience by preventing invalid requests. Successful SVG generation results in an image preview on the page, along with a prompt to click the image for downloading.

<script setup>
import { ref, reactive, watch } from 'vue';

const appState = reactive({
  clicked: false,
  success: false,
  downloadURL: '#',
  displayDownloadText: false,
  errorMessage: '',
  parameters: {
    stw: 14,
    inc: 0.05,
    gap: 20,
    num: 4,
    tbone: 12.7
  }
});

watch(() => appState.parameters.inc, (newValue) => {
  if (newValue >= 0.01) {
    appState.errorMessage = '';
  }
});

const createSVG = async () => {
  appState.clicked = true;
  appState.displayDownloadText = false;

  if (appState.parameters.inc < 0.01) {
    appState.errorMessage = "(pitch) is too small. (pitch) can only be set to 1/100 mm increments. (刻み幅)は1/100mm単位までしか設定できません。";
    appState.success = false;
    return;
  }

  try {
    const response = await fetch(`/kerf_check`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json; charset=utf-8",
      },
      body: JSON.stringify(appState.parameters),
    });

    if (!response.ok) {
      const errorData = await response.json();
      appState.errorMessage = errorData.error || response.statusText;
      appState.success = false;
      return;
    }

    const data = await response.text();
    const blob = new Blob([data], { type: "image/svg+xml" });
    appState.downloadURL = window.URL.createObjectURL(blob);
    appState.success = true;
    appState.displayDownloadText = true;
  } catch (error) {
    appState.errorMessage = "An unexpected error occurred.";
    appState.success = false;
  }
};
</script>

<template>
  <div id="app-1" class="kerf-check-container">
    <h1>Kerf Tool Test Fit Guides Generator</h1>

    <div class="input-container">
      <label>基準幅(start-width): <input v-model.number="appState.parameters.stw" placeholder="14"> mm</label>
      <label>刻み幅(pitch): <input v-model.number="appState.parameters.inc" placeholder="0.05"> mm</label>
      <label>隙間(gap): <input v-model.number="appState.parameters.gap" placeholder="20"> mm</label>
      <label>個数(number-of-kerfs): <input v-model.number="appState.parameters.num" placeholder="4"> 個</label>
      <label>刃径(blade-diameter): <input v-model.number="appState.parameters.tbone" placeholder="12.7"> mm</label>
    </div>

    <button @click="createSVG" class="create-svg-button">Create SVG</button>

    <div v-if="appState.displayDownloadText" class="download-instruction">Click the image to download</div>

    <div v-if="appState.success" class="image-container">
      <a :href="appState.downloadURL" download="kerf_check.svg">
        <img :src="appState.downloadURL" alt="Generated SVG" class="generated-image">
      </a>
    </div>

    <div v-if="appState.errorMessage" class="error-message">
      {{ appState.errorMessage }}
    </div>
  </div>
</template>

<style scoped>
.kerf-check-container {
  text-align: center;
  max-width: 600px;
  margin: auto;
}

.input-container {
  display: flex;
  flex-direction: column;
  margin-bottom: 20px;
}

.input-container label {
  margin-bottom: 10px;
}

.create-svg-button {
  margin-bottom: 20px;
  padding: 10px 20px;
  font-size: 16px;
  cursor: pointer;
}

.download-instruction {
  margin-bottom: 10px;
  font-style: italic;
}

.image-container {
  margin-bottom: 20px;
}

.generated-image {
  cursor: pointer;
  max-width: 100%;
  border: 1px solid #ccc;
}

.error-message {
  color: red;
}
</style>

Enter fullscreen mode Exit fullscreen mode

app.py

The /kerf_check route, which accepts POST requests and processes JSON data sent by the client. This route expects to receive specific parameters: stw (start width), inc (increment, which is the pitch), gap, num (number of kerfs), and tbone (blade diameter).

If the absolute value of inc is less than 0.01, the server responds with a 500 Internal Server Error
Otherwise, it calls the generate_svg function with the received parameters to generate an SVG image representation of kerfs.
The generate_svg function creates an SVG image as a string with the specified dimensions and kerf patterns. It calculates the width and height of the SVG canvas based on the parameters and uses a series of SVG path commands to draw the kerfs. The function also adds text elements to the SVG to label each kerf with its width. The SVG string is then returned to the client with a content type of image/svg+xml.

If the request is successful and no error is encountered in the generate_svg function, the server responds with the generated SVG data and a 200 OK status.

import json
from flask import Flask, render_template, request

args = dict()
try:
    with open("args.json") as f:
        args = json.load(f)
except FileNotFoundError:
    pass

app = Flask(__name__, **args)


@app.route("/")
def hello():
    return render_template("index.html")


@app.route("/kerf_check", methods=["POST"])
def kerf_check():
    json = request.json
    stw = json["stw"]
    inc = json["inc"]
    gap = json["gap"]
    num = json["num"]
    tbone = json["tbone"]

    if abs(inc) < 0.01:
        return (
            "(pitch) is too small. (pitch) can only be set to 1/100 mm increments. (刻み幅)は1/100mm単位までしか設定できません。",
            500,
        )

    txt = generate_svg(stw, inc, gap, num, tbone)

    return txt, 200, {"Content-Type": "image/svg+xml"}


def generate_svg(stw, inc, gap, num, tbone):
    # GAP = 16
    MARGIN = 10
    PARTS_HEIGHT = 50
    KERF_HEIGHT = 20

    if inc < 0:
        inc = -inc
        stw = stw - inc * (num - 1)

    cw = (stw + inc * num + gap) * num + gap + MARGIN * 2
    ch = PARTS_HEIGHT + MARGIN * 2
    orign_x = MARGIN
    orign_y = MARGIN

    st1 = f'<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="{cw}mm" height="{ch}mm" viewBox="0 0 {cw} {ch}">'
    ed1 = "</svg>"

    temp = f'<path stroke="red" stroke-width="0.1" fill="none" d="M{orign_x},{orign_y}'
    txt = ""

    x = orign_x
    y = orign_y
    kerf_width = stw

    y += PARTS_HEIGHT
    temp += f" V{y}"
    x += gap
    temp += f" H{x}"

    for i in range(num):
        kerf_width = stw + inc * i
        kerf_width = round(kerf_width, 2)
        if len(str(kerf_width)) == 1:
            adjst = len(str(kerf_width)) - 1
        else:
            adjst = len(str(kerf_width)) - 2

        txt += f'<text x="{x - adjst}" y="25" font-size="3" fill="black">{kerf_width}</text>'

        y -= KERF_HEIGHT
        temp += f" V{y}"
        t_bone = f" A {tbone / 2} {tbone / 2} 0 0 1 {x} {y - tbone}"
        temp += t_bone
        x += kerf_width
        temp += f" H{x}"
        t_bone = f" A {tbone / 2} {tbone / 2} 0 0 1 {x} {y}"
        temp += t_bone
        y += KERF_HEIGHT
        temp += f" V{y}"
        x += gap
        temp += f" H{x}"

    y = y - PARTS_HEIGHT
    temp += f" V{y}"
    x = orign_x
    temp += f" H{x}"
    temp += '"/>'

    result = f"{st1}\n{temp}\n{txt}\n{ed1}"

    return result

Enter fullscreen mode Exit fullscreen mode

Top comments (0)