ATSU
Posted on December 12, 2023
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
cd kerf-tool-test-fit-guides-generator
yarn
yarn build
flask run
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.
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).
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.
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.
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>
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
Posted on December 12, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.