<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Product on Schallbert's Blog</title><link>https://blog.schallbert.de/en/tags/product/</link><description>Recent content in Product on Schallbert's Blog</description><generator>Hugo</generator><language>en</language><lastBuildDate>Mon, 08 Sep 2025</lastBuildDate><atom:link href="https://blog.schallbert.de/en/tags/product/index.xml" rel="self" type="application/rss+xml"/><item><title>passtdas - a helper for your CNC</title><link>https://blog.schallbert.de/en/passtdas/</link><pubDate>Mon, 08 Sep 2025</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/en/passtdas/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-passtdas-thumb.avif"&#10; class="post-cover"&#10; alt="Image: Visualizazion of how passtdas analyzes and determines workpiece edges"&#10; title="passtdas - a helper for your CNC" /&gt;&#10;&lt;h2 id="the-problem"&gt;The Problem&lt;/h2&gt;&#10;&lt;p&gt;A friend of mine wrote to me. He uses a CNC gantry milling machine as a hobby, just like me, and often produces unique pieces. However, the resulting leftovers are sometimes too valuable to throw away. To maximize material utilization, he tries to use these offcuts as blanks for future projects.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Do you know how I can find out on the machine whether my milled part fits on the blank?&amp;rdquo; - Anonymous&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;This happens: According to the dimensions, the workpiece just fits on the blank. If you set the zero point even slightly incorrectly, the milling cutter will move beyond the workpiece boundaries, and scrap it is.&lt;/p&gt;&#10;&lt;h2 id="the-solution"&gt;The Solution&lt;/h2&gt;&#10;&lt;p&gt;It would be great if the machine followed the contour of the workpiece before starting the milling process, wouldn&amp;rsquo;t it? This way, you could easily find out whether the blank is actually completely within the workpiece boundaries.&lt;/p&gt;&#10;&lt;p&gt;Since I can&amp;rsquo;t find a quick solution online right away, I&amp;rsquo;m thinking about writing my own software to implement this. So I&amp;rsquo;m getting to work.&lt;/p&gt;&#10;&lt;p&gt;TODO: ADD VIDEO LINK&lt;/p&gt;&#10;&lt;h2 id="the-software"&gt;The Software&lt;/h2&gt;&#10;&lt;p&gt;For a quick prototype, I choose the high-level language &lt;a href="https://www.python.org/downloads/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;python&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Why? I have a fairly good command of &lt;em&gt;python&lt;/em&gt;, libraries for the necessary geometric calculations are available, and the written program can be easily compiled into an all-in-one file.&lt;/p&gt;&#10;&lt;p&gt;The program is designed to read machine instructions (G-code) and analyze the milling paths. The positions at which the maximum and minimum X and Y values are approached in the milling program are to be saved and output as a motion command in a path file.&lt;/p&gt;&#10;&lt;p&gt;If this program is loaded onto the machine, an &amp;ldquo;extreme value&amp;rdquo; determined in this way is approached and the Z-axis is lowered from the safety height to the measuring height. The program is then paused so that the operator can make adjustments to the workpiece position. Only by entering another start command will the next extreme value be reached, and so on.&lt;/p&gt;&#10;&lt;p&gt;This program will be called &lt;em&gt;passtdas&lt;/em&gt;.&lt;/p&gt;&#10;&lt;h3 id="program-type"&gt;Program type&lt;/h3&gt;&#10;&lt;p&gt;I&amp;rsquo;m choosing a console program with few parameters:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Shortcut&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Parameter name&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Description&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;-f&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;--file&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;The source file to be analyzed. Required.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;-s&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;--zsafety&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Safety height the machine will do flyovers with. Optional.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;-p&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;--zprobe&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Probe height the machine will pinpoint to. Optional.&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The two height parameters are given default values, so they don&amp;rsquo;t always have to be specified.&lt;/p&gt;&#10;&lt;h3 id="input"&gt;Input&lt;/h3&gt;&#10;&lt;p&gt;&lt;em&gt;passtdas&lt;/em&gt; reads files and searches them line by line for motion commands like &lt;code&gt;G00, G01, G02, G03&lt;/code&gt;. While the analysis is simple for linear movements like &lt;code&gt;G00, G01&lt;/code&gt; - linear movements can be expressed using point-to-point connections whose extreme values cannot lie between the points - it can be quite challenging for circular segments.&lt;/p&gt;&#10;&lt;p&gt;The height information on the Z-axis is also read and saved. This allows the machining technician to quickly check whether the maximum milling depth matches the desired one.&lt;/p&gt;&#10;&lt;p&gt;If the G-code file contains no instructions or incorrectly formulated instructions, &lt;em&gt;passtdas&lt;/em&gt; displays corresponding error messages and aborts the analysis.&lt;/p&gt;&#10;&lt;h3 id="definition-of-circular-segments"&gt;Definition of Circular Segments&lt;/h3&gt;&#10;&lt;p&gt;Background: Circular segments can be defined in two ways in G-code.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X5.660 Y-0.000 Z10.000&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G02 X-5.660 Y0.000 R5.660 F1600&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Command:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Move a circular arc clockwise from point &lt;code&gt;X5.66 Y0&lt;/code&gt; with radius &lt;code&gt;5.66mm&lt;/code&gt; to point &lt;code&gt;X-5.66 Y0&lt;/code&gt; with feed rate &lt;code&gt;1600mm/min&lt;/code&gt; at height &lt;code&gt;10mm&lt;/code&gt;.&amp;rdquo;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Drawing these commands creates a hanging semicircle. Circles can be defined using three points. However, only two points are given. The third point must be calculated from the specified arc radius using the start and end points.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-arcr.avif" alt="Image: drawing an arc using given radius, start- and endpoint"&gt;&lt;/figure&gt;&#10;&lt;p&gt;&lt;em&gt;Task: Find the extreme points of this arc in the XY plane.&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;But an arc can also be defined like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X-2.5696 Y5.0431&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G03 X-4.6258 Y3.2616 Z-0.5000 I2.5696 J-5.0431&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Command:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;ldquo;Traverse a circular arc counterclockwise from point &lt;code&gt;X-2.56 Y5.04&lt;/code&gt; with center coordinates &lt;code&gt;I2.56 J-5.04&lt;/code&gt; to point &lt;code&gt;X-4.62 Y3.26&lt;/code&gt;.&amp;rdquo;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Drawing these commands creates a short circular segment in the upper left quadrant. Although three points (start and end points, as well as the center of the circle) are given, it can sometimes be difficult to determine whether an extreme value is being reached somewhere along the arc.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-arcij.avif" alt="Image: drawing an arc using center point, start- and endpoint"&gt;&lt;/figure&gt;&#10;&lt;p&gt;&lt;em&gt;Task: Find the extreme points of this circle in the XY range.&lt;/em&gt;&lt;/p&gt;&#10;&lt;h3 id="calculating-the-extreme-points-of-circle-segments"&gt;Calculating the extreme points of circle segments&lt;/h3&gt;&#10;&lt;p&gt;A circle segment can have up to four additional extreme points, depending on its range: X+, Y+, X-, Y-. For a full circle, these are always the coordinate intersection points as seen from the radius.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_extremes_from_arc&lt;/span&gt;(arc, coordinates):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; x_plus_radius &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [center_x &lt;span style="color:#f92672"&gt;+&lt;/span&gt; radius, center_y, xyz[&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;]]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; y_plus_radius &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [center_x, center_y &lt;span style="color:#f92672"&gt;+&lt;/span&gt; radius, xyz[&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;]]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; x_minus_radius &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [center_x &lt;span style="color:#f92672"&gt;-&lt;/span&gt; radius, center_y, xyz[&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;]]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; y_minus_radius &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [center_x, center_y &lt;span style="color:#f92672"&gt;-&lt;/span&gt; radius, xyz[&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;]]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extremevalue_order &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [x_plus_radius, y_plus_radius, x_minus_radius, y_minus_radius]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (arc[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;-&lt;/span&gt; arc[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]) &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# crossing the 0° line (x-axis), handle overflow with nested if below&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; arc[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;360&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; []&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; crossing_angle &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;721&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;90&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; crossing_angle &lt;span style="color:#f92672"&gt;in&lt;/span&gt; range(int(arc[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]), int(arc[&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;+&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; i &lt;span style="color:#f92672"&gt;=&lt;/span&gt; int(crossing_angle &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;90&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; i &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; i &lt;span style="color:#f92672"&gt;-=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(extremevalue_order[i])&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the circle center and radius are known, the swept angle can be determined from the start and end points. This makes it clear whether the circle&amp;rsquo;s start and/or end points themselves represent extreme values, or whether additional extreme values arise from sweeping over a coordinate origin. It is precisely what the above code example does.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-arcext.avif" alt="Image: Possible coordinate extreme values of an arc segment"&gt;&lt;/figure&gt;&#10;&lt;h3 id="data-table"&gt;Data table&lt;/h3&gt;&#10;&lt;p&gt;All possible extreme values (two for a line, up to four for a circle segment) are now collected in a data table. Once each G-code line has been evaluated and the list is thus complete, the maximum and minimum values for each axis are determined from this list.&lt;/p&gt;&#10;&lt;p&gt;This ultimately creates four XYZ coordinates, which describe the maximum extent of the workpiece for each spatial direction.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-ext.avif" alt="Image: Visual example of an extreme value dataset for a simple geometry"&gt;&lt;/figure&gt;&#10;&lt;p&gt;At the same time, the program searches all commands to the Z-axis and remembers the maximum plunge depth.&lt;/p&gt;&#10;&lt;h3 id="output"&gt;Output&lt;/h3&gt;&#10;&lt;p&gt;The command-line program has no visual output. Therefore, I&amp;rsquo;ll limit myself to creating a simulation program for the milling machine in which the workpiece extension is approached and paused at each point:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G90&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Go to coordinate zero&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MSG &lt;span style="color:#e6db74"&gt;&amp;#34;Zmin of this job: -1.0&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 Z40&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X0 Y0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G01 Z15 F1200&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MSG &lt;span style="color:#e6db74"&gt;&amp;#34;PathPreview: Hit START to go to Ymin: [&amp;#39;0.0&amp;#39;, &amp;#39;-5.66&amp;#39;]&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;M00&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 Z40&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X0.0 Y-5.66&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G01 Z15 F1200&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MSG &lt;span style="color:#e6db74"&gt;&amp;#34;PathPreview: Hit START to go to Xmin: [&amp;#39;-5.66&amp;#39;, &amp;#39;0.0&amp;#39;]&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;M00&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 Z40&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X-5.66 Y0.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G01 Z15 F1200&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MSG &lt;span style="color:#e6db74"&gt;&amp;#34;PathPreview: Hit START to go to Ymax: [&amp;#39;0.0&amp;#39;, &amp;#39;5.66&amp;#39;]&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;M00&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 Z40&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X0.0 Y5.66&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G01 Z15 F1200&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;MSG &lt;span style="color:#e6db74"&gt;&amp;#34;PathPreview: Hit START to go to Xmax: [&amp;#39;5.66&amp;#39;, &amp;#39;0.0&amp;#39;]&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;M00&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 Z40&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G00 X5.66 Y0.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;G01 Z15 F1200&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The above example traverses the extreme values of a circle with &lt;code&gt;r=5.66mm&lt;/code&gt;. It always moves to a safety height of &lt;code&gt;Z40&lt;/code&gt; to cover the points and, upon arrival, slowly moves the Z axis at &lt;code&gt;F1200&lt;/code&gt; to the target height of &lt;code&gt;Z15&lt;/code&gt;. It is important that the zero point in &lt;code&gt;Z&lt;/code&gt; has been correctly measured beforehand - otherwise, a collision with the workpiece may occur.&lt;/p&gt;&#10;&lt;h2 id="testing-and-verification"&gt;Testing and Verification&lt;/h2&gt;&#10;&lt;p&gt;To see if the program works as expected, I created three test files:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Unit tests: Tests individual functions in the program, e.g., whether line and circle segments are correctly recognized&lt;/li&gt;&#10;&lt;li&gt;Integration tests: Tests whether functions function correctly in conjunction, e.g., the correct calculation of extreme values based on input values&lt;/li&gt;&#10;&lt;li&gt;End-to-end tests: These tests check the entire function chain, e.g., whether incorrectly formatted input files are recognized&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2025-09-08-unittest.avif" alt="Image: Test result summary for passtdas"&gt;&lt;/figure&gt;&#10;&lt;p&gt;Finally, I create several test files with the program and move to the extreme values generated from them with my CNC.&lt;/p&gt;&#10;&lt;h2 id="open-source-software"&gt;Open-source software&lt;/h2&gt;&#10;&lt;p&gt;The source code for &lt;em&gt;passtdas&lt;/em&gt; is available for free on &lt;a href="https://github.com/Schallbert/passtdas" target="_blank" rel="noopener noreferrer" class="external-link"&gt;GitHub&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, but must be interpreted using Python.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;ll build a user-friendly and easy-to-use program once enough people have expressed a desire for it.&lt;/p&gt;&#10;</description></item><item><title>Klingenbutler</title><link>https://blog.schallbert.de/en/klingenbutler/</link><pubDate>Mon, 14 Oct 2024</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/en/klingenbutler/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_klingenbutler-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: First prototype of Klingenbutler"&#10; title="Klingenbutler" /&gt;&#10;&lt;h2 id="why-klingenbutler"&gt;Why Klingenbutler?&lt;/h2&gt;&#10;&lt;p&gt;After I had solved the problem of the lack of a shelf for solid shampoos in my shower - the result was &lt;a href="https://blog.schallbert.de/en/seifenbutler-pilot-run/"&gt;Seifenbutler&lt;/a&gt; - I noticed razors flying around there more and more as a nuisance.&lt;/p&gt;&#10;&lt;p&gt;If they lie on the edge of the shower, a puddle forms around them. This creates a lot of limescale stains and makes cleaning more difficult. If you put them on the fittings, they will fall down sooner or later and get damaged. The same applies to the wobbly shelf on the edge of the shower cubicle.&lt;/p&gt;&#10;&lt;p&gt;It makes sense to reuse the Seifenbutler&amp;rsquo;s clamping mechanism. This means that only the &amp;ldquo;front part&amp;rdquo; needs to be redesigned and I have a stylish shower tray for razors - the &lt;em&gt;Klingenbutler&lt;/em&gt;.&lt;/p&gt;&#10;&lt;h2 id="the-design"&gt;The design&lt;/h2&gt;&#10;&lt;p&gt;I like the round shape of Seifenbutler and want to design Klingenbutler in a similar way. However, the razors should not be touched too easily by accident. So I decide to attach them as &amp;ldquo;inside&amp;rdquo; as possible. To do this, I create a ring with an opening, which in turn has three notches for razors. I design the radii of the notches so that all the wet razors I have available fit in there and even toothbrushes can be hung on the Klingenbutler without the bristles touching.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_klingenbutlercad.jpg" alt="Image: CAD drawing of Klingenbutler V1.0"&gt;&lt;/figure&gt;&#10;&lt;p&gt;I design the opening (in the drawing on the left) for upper and lower shells so that the upper shell always covers the lower shell in all possible closed positions. This avoids protruding edges with particularly large or small shower rod diameters.&lt;/p&gt;&#10;&lt;h3 id="snapping-the-razors-into-place"&gt;Snapping the razors into place&lt;/h3&gt;&#10;&lt;p&gt;It was more difficult to plan snap-in aids so that the razors cannot come loose on their own. In the first draft, I decided on a notch in the shape of a &lt;a href="https://en.wikipedia.org/wiki/Tetrahedron" target="_blank" rel="noopener noreferrer" class="external-link"&gt;double tetrahedron&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. In the picture above, the notch is labeled &amp;ldquo;drain&amp;rdquo;. It has a gradient of &lt;code&gt;3mm&lt;/code&gt; over a length of approx. &lt;code&gt;15mm&lt;/code&gt;, allows splash water to run off and enables the razors to dry quickly.&lt;/p&gt;&#10;&lt;h3 id="g2-curves"&gt;G2 curves&lt;/h3&gt;&#10;&lt;p&gt;&lt;figure class="media-frame media-frame--left"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_twodifferentradii.jpg" alt="Image: two different radii joined. There is a visible kink due to differences in slope at that point."&gt;&lt;/figure&gt;&#10;When I showed one of my designs to a friend of mine shortly before starting production - he is a 3D designer by profession - he immediately noticed that I had not planned for a smooth transition between curve radii. He said that in such cases &amp;ldquo;G2 curves&amp;rdquo; are used in CAD and that the model would have much more harmonious transitions.&lt;/p&gt;&#10;&lt;p&gt;In technical jargon, this seems to be referred to as &lt;a href="https://en.wikipedia.org/wiki/Smoothness#Geometric_continuity" target="_blank" rel="noopener noreferrer" class="external-link"&gt;geometric continuity&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. I immediately asked the software company behind my CAD tool whether they &lt;a href="https://hobbyline.info/forum/index.php?thread/822-wie-mache-ich/&amp;amp;postID=11565#post11565" target="_blank" rel="noopener noreferrer" class="external-link"&gt;supported something like this&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; and lo and behold - there is the tool &lt;em&gt;Create tangent continuous connection&lt;/em&gt;. From now on I will use this tool when I connect radii.&lt;/p&gt;&#10;&lt;p&gt;The tool creates a &lt;a href="https://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Bezier curve&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; whose endpoints are tangentially connected to the two circle segments and thus eliminate the kink.&lt;/p&gt;&#10;&lt;p&gt;&lt;figure class="media-frame media-frame--left"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_twodifferentradiig2.jpg" alt="Image: radii joined with tangential continuity using a Bezier Curve"&gt;&lt;/figure&gt;&#10;In addition to the more pleasing appearance, this approach also has an advantage in production: When traveling along bends, the CNC machine has to make sudden changes in direction, similar to connected line segments that meet at an angle. This means that it has to slow down and accelerate at the new curve radius. By using tangent-continuous connections, however, acceleration peaks are significantly reduced and the machine runs at a more consistent speed.&lt;/p&gt;&#10;&lt;p&gt;If you have projects with lots of &amp;ldquo;bends&amp;rdquo; in the drawing, like with the &lt;em&gt;Klingenbutler&lt;/em&gt;, you can quickly add up to a few seconds of production time per part, which can easily be saved - perhaps more on this later.&lt;/p&gt;&#10;&lt;h3 id="first-prototype---version-10"&gt;First prototype - version 1.0&lt;/h3&gt;&#10;&lt;p&gt;I started production with my &amp;ldquo;classic&amp;rdquo; prototype material: cast PMMA (acrylic glass) with a thickness of &lt;code&gt;5mm&lt;/code&gt; in opaque white. If you use reasonably suitable parameters, the material behaves extremely well. At the same time, it mercilessly reveals all errors: here a bevel a little too wide, there slight chatter marks caused by the machine accelerating out of a corner, chips caught in the bottom of threads - all of this is clearly evident in the slightly translucent material composition and strong reflections on the material edges.&#10;This material is therefore ideally suited to improving the design and production of products.&lt;/p&gt;&#10;&lt;p&gt;Thanks to my experience with Seifenbutler, holding it down during production and assembly of Klingenbutler went smoothly. From now on, toothbrushes and razors can be found in a fixed place in our shower 🙂.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_firstproto.jpg" alt="Image: First prototype of Klingenbutler V1.0"&gt;&lt;/figure&gt;&#10;&lt;h2 id="version-11---production-in-bamboo"&gt;Version 1.1 - Production in bamboo&lt;/h2&gt;&#10;&lt;p&gt;&lt;figure class="media-frame media-frame--right"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_bambus.jpg" alt="Image: First time Bamboo on my CNC"&gt;&lt;/figure&gt;&#10;I actually want to get away from plastic. Over the course of the year I have already switched the production of my Seifenbutlers to &amp;ldquo;greencast&amp;rdquo; acrylic glass - i.e. PMMA, which itself is made from 100% recycled material - but it would be even better not to use any plastic at all.&lt;/p&gt;&#10;&lt;p&gt;For this I had a few samples of &amp;ldquo;Moso bamboo&amp;rdquo; sent to me: stick glued wood cross-glued in three layers, similar to plywood, just under &lt;code&gt;7.5mm&lt;/code&gt; thick. For production in bamboo, I made the fitting tolerances a little more generous (&lt;code&gt;0.1mm&lt;/code&gt; undersize) and simply converted metric threads in the plan into simple holes for wood screws.&lt;/p&gt;&#10;&lt;h3 id="omitting-unnecessary-things"&gt;Omitting unnecessary things&lt;/h3&gt;&#10;&lt;p&gt;When assembling the first prototype, it became clear to me that the third bayonet had become superfluous. Together with a few other smaller changes, I developed version 1.1:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Omitting the third screw connection in the locking circle&lt;/li&gt;&#10;&lt;li&gt;Locking lugs/drain channel: Continuous engraving without stopping at &amp;ldquo;keyhole&amp;rdquo;&lt;/li&gt;&#10;&lt;li&gt;Bayonet: Chamfering the insides on the back and thus breaking through the surface&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="speed-and-feed"&gt;Speed and feed&lt;/h3&gt;&#10;&lt;p&gt;For full groove milling, I use a DLC-coated (diamond-like-carbon) &lt;code&gt;4mm&lt;/code&gt; two-tooth milling cutter without twist with &lt;code&gt;12mm&lt;/code&gt; cutting length. I operate it at &lt;code&gt;S30000rpm&lt;/code&gt; with a feed of &lt;code&gt;F3000mm/min&lt;/code&gt;. I go into the full material depth with &lt;code&gt;Z+ 7.5mm&lt;/code&gt; and drill holes and material ramps at &lt;code&gt;20°&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;I make bevels using a &lt;code&gt;90°&lt;/code&gt; engraving cutter, three-edged, diameter &lt;code&gt;10mm&lt;/code&gt;. I operate it at &lt;code&gt;F5000mm/min&lt;/code&gt; at &lt;code&gt;S15000rpm&lt;/code&gt; and a center distance of &lt;code&gt;2mm&lt;/code&gt;, which for a smooth edge with a width of &lt;code&gt;0.5mm&lt;/code&gt; already means that it needs &lt;code&gt;2.5mm&lt;/code&gt; of space &amp;ldquo;downwards&amp;rdquo; in the Z direction.&lt;/p&gt;&#10;&lt;p&gt;For holes, pockets and small recesses I use a relief-ground &lt;code&gt;2mm&lt;/code&gt; two-cutter fishtail with &lt;code&gt;15°&lt;/code&gt; twist and &lt;code&gt;20mm&lt;/code&gt; cutting length. I operate it at a cautious &lt;code&gt;1700mm/min&lt;/code&gt; at &lt;code&gt;S30000rpm&lt;/code&gt; and a depth of only &lt;code&gt;Z+1.5mm&lt;/code&gt;. Here, too, I dive into the material at &lt;code&gt;20°&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h3 id="production"&gt;Production&lt;/h3&gt;&#10;&lt;p&gt;Working with the material &amp;ldquo;bamboo&amp;rdquo;, which was completely unknown to me until then, was surprisingly easy. Thanks to my new and very sharp cutters, there were no tears to complain about and the material itself also had hardly any defects or damage.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center media-frame--video"&gt;&#10; &lt;div class="media-video"&gt;&lt;iframe&#10; src="https://makertube.net/videos/embed/ayYr653wLVLzVpmiMkhyz8"&#10; title="Making of Klingenbutler"&#10; loading="lazy"&#10; allow="autoplay; fullscreen; picture-in-picture"&#10; allowfullscreen&#10; style="width: 100%; height: auto; min-height: 315px; aspect-ratio: 16/9;"&#10; &gt;&lt;/iframe&gt;&lt;/div&gt;&#10; &lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;Making of Klingenbutler&lt;/span&gt;&lt;a href="https://makertube.net/videos/embed/ayYr653wLVLzVpmiMkhyz8" class="attr-link" target="_blank" rel="noopener noreferrer"&gt;↗&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p&gt;During processing, quite fine chips were created, which are a bit reminiscent of dust from processing MDF boards. I am not yet sure whether I should reduce the speed or whether this is due to the straight cutting edges of the cutter. When milling full slot, the cutter may have to carry chips with it for several revolutions before it can leave them behind.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_klingenbutler.jpg" alt="Image: Klingenbutler V1.1 Bamboo edition"&gt;&lt;/figure&gt;&#10;&lt;p&gt;In any case, drilling with this cutter and unchanged parameters is not recommended. All of the drill holes have charred edges, which suggests that the speed was far too high. I also drilled in one piece instead of retracting the cutter several times. Let&amp;rsquo;s use the rule of three to make this obvious: At a feed rate of &lt;code&gt;FZ+2000mm/min&lt;/code&gt;, &lt;code&gt;S30000/rpm&lt;/code&gt; and &lt;code&gt;Z7.5mm&lt;/code&gt;, the cutter makes &lt;code&gt;112.5&lt;/code&gt; revolutions in the drill hole, so the cutting edges rub senselessly along the wall of the drill hole 225 times - only the center cutting edge at the bottom removes material. In addition, the chips are not ejected but are carried along the whole time.&lt;/p&gt;&#10;&lt;p&gt;For the next prototype, I will either make drill holes with retractions and a drastically reduced speed or simply use a smaller cutter.&lt;/p&gt;&#10;&lt;h3 id="result"&gt;Result&lt;/h3&gt;&#10;&lt;p&gt;&lt;figure class="media-frame media-frame--right"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-10-14_bambusdetail.jpg" alt="Image: Klingenbutler detail view (underside)"&gt;&lt;/figure&gt;&#10;This is impressive. I took the bevel for edge breaking from the production in acrylic glass. It is very narrow at &lt;code&gt;0.5mm&lt;/code&gt;. Nevertheless, the detail is really clean. The upper and lower shells of Klingenbutler can be smoothly moved against each other and the clamp, my problem child &lt;a href="https://makertube.net/w/64TS5d5cp2eNqf8SSPvY77" target="_blank" rel="noopener noreferrer" class="external-link"&gt;from this video&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, could also be milled without tearing.&lt;/p&gt;&#10;&lt;p&gt;Assembly is problem-free and the wood screws can also be easily screwed into the pre-drilled holes. The bamboo Klingenbutler variant holds on to the shower rail even better than its acrylic glass counterpart - thanks to the rougher surface and greater material thickness.&lt;/p&gt;&#10;&lt;p&gt;It remains to be seen whether the material is durable enough in damp rooms. It is still popular for cutting boards in the kitchen, so I guess it will be just fine.&lt;/p&gt;&#10;&lt;h2 id="outlook"&gt;Outlook&lt;/h2&gt;&#10;&lt;p&gt;The use of bamboo has left me very motivated: a great material. Hard and strong but light, easy to machine, dimensionally stable and hardly any material defects: that&amp;rsquo;s how you want to work with natural materials!&lt;/p&gt;&#10;&lt;p&gt;This prototype is now in our shower for endurance testing. In the meantime, I&amp;rsquo;ll continue to refine the design.&lt;/p&gt;&#10;&lt;h3 id="version-12"&gt;Version 1.2&lt;/h3&gt;&#10;&lt;p&gt;I have the following small improvements planned for V1.2:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Drilling: Choose a smaller cutter (at the expense of the cycle time)&lt;/li&gt;&#10;&lt;li&gt;Fastening: Mill a cutout so that the knurled wheel no longer protrudes&lt;/li&gt;&#10;&lt;li&gt;Remove superfluous recess on the inside of the upper shell&lt;/li&gt;&#10;&lt;li&gt;Allow different radii to be merged with G2 curves&lt;/li&gt;&#10;&lt;li&gt;Replace locking lugs with a more harmonious, round drainage channel&lt;/li&gt;&#10;&lt;li&gt;Align the grain: Manufacture all parts &amp;ldquo;vertically&amp;rdquo;&lt;/li&gt;&#10;&lt;li&gt;Mill the clamps in the first setup and make them &lt;code&gt;0.04mm&lt;/code&gt; thicker&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Here too, the aim is to achieve a good product through continuous improvement.&lt;/p&gt;&#10;</description></item><item><title>Seifenbutler - Pilot run!</title><link>https://blog.schallbert.de/en/seifenbutler-pilot-run/</link><pubDate>Thu, 08 Feb 2024</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/en/seifenbutler-pilot-run/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-08-seifenbutler-white.jpg"&#10; class="post-cover"&#10; alt="Image: Seifenbutler ready for sale"&#10; title="Seifenbutler - Pilot run!" /&gt;&#10;&lt;p&gt;My Hardware project &lt;a href="https://blog.schallbert.de/en/projects/move-blog-to-own-server/"&gt;Seifenbutler&lt;/a&gt; is complete. After more than a year of development, I have manufactured some in white, black, transparent, and white translucent. They even made it to a first &lt;a href="https://mopemaster.com/produkt/seifenbutler-seifenhalter-dusche/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;online shop&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-08-seifenbutler-collection.jpg" alt="Image: Seifenbutler in three different colours"&gt;&lt;/figure&gt;&#10;&lt;h2 id="how-do-i-determine-the-sales-price"&gt;How do I determine the sales price?&lt;/h2&gt;&#10;&lt;p&gt;As I use high-quality materials and the production and assembly time is long, I don&amp;rsquo;t have much room for maneuver when it comes to pricing. On the other hand, I can&amp;rsquo;t ask moon prices for a commodity like Seifenbutler - unless I use precious woods or other unusual or complex materials. There is a lot of material online for such calculations. I used a &lt;a href="https://www.youtube.com/watch?v=mxwggzhe_hs" target="_blank" rel="noopener noreferrer" class="external-link"&gt;YouTube video from the &amp;ldquo;Cutting It Close&amp;rdquo; channel&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; as a guide.&lt;/p&gt;&#10;&lt;p&gt;My price calculation is made up of the following items:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Final price = material price&#10; + machine time (investment, wear, tools, consumables)&#10; + production and assembly time &#10; + packaging costs &#10; + fees (postage, payment service providers, stores)&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can see, I have not included my own time for product development in the calculation - if I had to do this, it would easily add the equivalent of &lt;code&gt;250h&lt;/code&gt; of working time.&lt;/p&gt;&#10;&lt;h3 id="material-prices"&gt;Material prices&lt;/h3&gt;&#10;&lt;p&gt;&lt;strong&gt;As of the end of 2023&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Ok, let&amp;rsquo;s quickly list the expenses per part:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;quantity&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;item&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;price/unit&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;unit used&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;price&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;PMMA 5mm XT&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;90€/m²&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,03&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2,70€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Knurl M4 A2 22mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7,54€/25pcs&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,04&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,30€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Washer M4 A2 12mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,94€/100pcs&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,01&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,01€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;pin M4 A2 12mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6,70€/50pcs&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,02&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,13€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;screw M4 A2 8mm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;12,40€/200pcs&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,025&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0,31€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Material cost/part&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3,45€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="machine-time"&gt;Machine time&lt;/h3&gt;&#10;&lt;p&gt;I estimate the machine time for this project at &lt;code&gt;100€/h&lt;/code&gt;. The lion&amp;rsquo;s share of this is the investment, maintenance and wear costs of the machine at &lt;code&gt;€50/h&lt;/code&gt; plus my own working time, which I simply estimate at &lt;code&gt;€30/h&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;The tool costs are quite high at around &lt;code&gt;€15/h&lt;/code&gt;, as I have to use four different tools, all of which are small in diameter and therefore subject to high wear.&lt;/p&gt;&#10;&lt;p&gt;Consumables are vacuum fleeces, positioning pins, cut protection gloves and adhesive tape. I need the latter to fix the small milled parts and the spiral. Their share of the machine time costs is around &lt;code&gt;5€/h&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;The machine time is currently &lt;code&gt;8min/part&lt;/code&gt;. This means that the item &lt;code&gt;machine time&lt;/code&gt; amounts to &lt;code&gt;13.30€/piece&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h3 id="assembly-time"&gt;Assembly time&lt;/h3&gt;&#10;&lt;p&gt;It takes me about 5 minutes to assemble a soap holder. If I apply an hourly rate of &lt;code&gt;30€&lt;/code&gt;, I arrive at &lt;code&gt;2.50€&lt;/code&gt; per item.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2024-02-08-seifenbutler-assembly.jpg" alt="Image: Seifenbutler ready to assemble"&gt;&lt;/figure&gt;&#10;&lt;h3 id="packaging-costs-and-purchase-of-materials"&gt;Packaging costs and purchase of materials&lt;/h3&gt;&#10;&lt;p&gt;I pack the soap holders in simple DIN A6 envelopes after wrapping them carefully in wrapping paper. The costs here amount to just &lt;code&gt;0.20€&lt;/code&gt; per item. However, if I now include my working time for packaging and shipping labels, we are already at &lt;code&gt;0.90€&lt;/code&gt; per item. Added to this are the costs for postage when purchasing materials and the costs for paper and printing the operating instructions, which I calculate at a further &lt;code&gt;0.50€/unit&lt;/code&gt; - the small quantities produced are weighing heavy on this.&lt;/p&gt;&#10;&lt;h3 id="fees"&gt;Fees&lt;/h3&gt;&#10;&lt;p&gt;Fees are incurred for shipping and payment service providers, for example. Here the prices vary depending on the provider, so I plan a shipping surcharge of &lt;code&gt;3.50€&lt;/code&gt; (shipping by large letter) - this does not apply to sales &amp;ldquo;at the counter&amp;rdquo;.&lt;/p&gt;&#10;&lt;h2 id="the-total-price"&gt;The total price&lt;/h2&gt;&#10;&lt;p&gt;If I add up all the items, I would have to call up a store price of &lt;code&gt;20.15€&lt;/code&gt; (cash) for the unpacked goods and &lt;code&gt;24.75€&lt;/code&gt; in the case of shipping with cashless payment. And then the company would still not have made a cent of profit.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m not sure whether the soap butler can be sold for such prices. But if you&amp;rsquo;re honest and don&amp;rsquo;t want to end up paying more, such calculations are unavoidable, at least roughly.&lt;/p&gt;&#10;&lt;h2 id="the-assumed-merchant-with-a-store"&gt;The assumed merchant with a store&lt;/h2&gt;&#10;&lt;p&gt;If I were a merchant&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, I would still have to add VAT and would also want to see the development costs amortized somehow. I&amp;rsquo;m also not sure whether I would get through the door &amp;ldquo;as a merchant&amp;rdquo; with an hourly rate of &lt;code&gt;30€&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;Phew. If you now include things like store rent, costs for storage space, workshop rent, electricity, waste disposal, membership of the &amp;ldquo;Green Dot&amp;rdquo; in accordance with the Packaging Act, contributions to the Chamber of Industry and Commerce, the employers&amp;rsquo; liability insurance association as well as taxes and duties, then even with a further 20% mark-up, a whole heap of soap holders would have to be sold in order not to end up poorer than before!&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s assume I can rent a workshop and open a tiny store.&lt;/p&gt;&#10;&lt;h3 id="a-fictitious-store"&gt;A fictitious store&lt;/h3&gt;&#10;&lt;p&gt;Let&amp;rsquo;s do a quick calculation with a few assumed values.&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Item&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Price/Unit&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Used&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Final price&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Shop&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;16€/m² per month&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;40m²&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;640€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Storage space&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4€/m² per month&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8m²&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;32€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;workshop&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8€/m² per month&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;30m²&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;240€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Service charges&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;per month&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;200€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Taxes&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;IHK / BG / insurance&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;125€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Fees&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;20€/month&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;60€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Total&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1297€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Staff&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1600€&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;And if I were to hire someone to work in this store for 20hours/week, I would incur an estimated €1600 in costs.&lt;/p&gt;&#10;&lt;h3 id="summa-summarum"&gt;Summa summarum&lt;/h3&gt;&#10;&lt;p&gt;Ok, let&amp;rsquo;s summarize: I start with a mark-up of 20% on the production costs. As an assumed merchant, however, I would have to pay VAT, which I would have to add on again.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Retail price = production price &#10; + contribution margin (20%)&#10; + value added tax (19%)&#10; = (1,2 * 20,15€) * 1,19 = 28,77€&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Oh well, that puts us at almost thirty euros per item.&lt;/p&gt;&#10;&lt;p&gt;So how many soap butlers have to be sold per month before I would make a profit as a merchant with a store?&#10;The contribution margin is &lt;code&gt;4.03€&lt;/code&gt; (20% of the production price). If I now divide the costs generated by production and sales by this amount, I get my minimum sales quantity: &lt;code&gt;719 units/month&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;So how many hours per week would I have to spend at least at my CNC milling machine or assembling and packaging soap butlers?&#10;If I take the values from above, I get a total of &lt;code&gt;8 + 5 + 2 = 15min/part&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;With the minimum sales volume, that would give me a working week of about 45 hours!&#10;Oh, 60 hours a week is easy, isn&amp;rsquo;t it? So I could produce 960 units per month - and assuming they sell excellently, I would earn an extra &lt;code&gt;€971&lt;/code&gt; to amortize the development costs (&lt;code&gt;estimate 250h*€30 = €7,500&lt;/code&gt;). That&amp;rsquo;s a bang!&lt;/p&gt;&#10;&lt;p&gt;I would then march into the profit zone with the company after just eight months (with 60 hours per week, of course). Oh, or do you still have to pay taxes? And when was my machine paid off?&lt;/p&gt;&#10;&lt;h2 id="conclusion-on-profitability"&gt;Conclusion on profitability&lt;/h2&gt;&#10;&lt;p&gt;I realize that the calculation is not that simple. Some parameters cannot be scaled up or down arbitrarily and linearly and I cannot realistically estimate the machine costs, for example, because I simply don&amp;rsquo;t have any experience in continuous operation - keyword wear and tear.&lt;/p&gt;&#10;&lt;p&gt;Nevertheless, the amount of work I have to do is enough for me to say:&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;It won&amp;rsquo;t work. Nice project, but not worth it&lt;/strong&gt;, at least not with my own funds and on my own initiative. It remains a pilot series and my joy at a successful, functional product.&lt;/p&gt;&#10;&lt;p&gt;Fortunately, this is all &amp;ldquo;just my hobby&amp;rdquo;, so I&amp;rsquo;m not forced to draw the disastrous conclusions 😉&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;Careful. My vocation is not to be a trader or a businessman, but to be an engineer. That&amp;rsquo;s why I only have a very rudimentary knowledge of business administration (just that one semester at university when nobody really wanted to listen). All my comments here are rough estimates. They should therefore be taken with a grain of salt. However, I am very happy to receive corrections and constructive criticism!&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>QR-codengrave V1.1 / V1.2</title><link>https://blog.schallbert.de/en/qr-codengrave1-1/</link><pubDate>Sun, 12 Feb 2023</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/en/qr-codengrave1-1/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-02-12_qr-codengrave-thumb.jpg"&#10; class="post-cover"&#10; alt="Image: Splash screen of QR-codengrave V1.1"&#10; title="QR-codengrave V1.1 / V1.2" /&gt;&#10;&lt;h2 id="in-a-nutshell"&gt;In a nutshell&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Faster Engrave algorithm&lt;/li&gt;&#10;&lt;li&gt;Several bugfixes&lt;/li&gt;&#10;&lt;li&gt;Usability improvements&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="demo-video"&gt;Demo video&lt;/h2&gt;&#10;&lt;figure class="media-frame media-frame--center media-frame--video"&gt;&#10; &lt;div class="media-video"&gt;&lt;iframe&#10; src="https://makertube.net/videos/embed/jFD7nxftVHjUy6LKPCFE3N"&#10; title="QR-codengrave Quick Demo"&#10; loading="lazy"&#10; allow="autoplay; fullscreen; picture-in-picture"&#10; allowfullscreen&#10; style="width: 100%; height: auto; min-height: 315px; aspect-ratio: 16/9;"&#10; &gt;&lt;/iframe&gt;&lt;/div&gt;&#10; &lt;figcaption class="media-caption"&gt;&#10; &lt;span class="caption-text"&gt;QR-codengrave Quick Demo&lt;/span&gt;&lt;a href="https://makertube.net/videos/embed/jFD7nxftVHjUy6LKPCFE3N" class="attr-link" target="_blank" rel="noopener noreferrer"&gt;↗&lt;/a&gt;&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 id="version-11"&gt;Version 1.1&lt;/h2&gt;&#10;&lt;h3 id="engrave-algorithm-upgrade"&gt;Engrave algorithm upgrade&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The algorithm now scans the lines horizontally&lt;/li&gt;&#10;&lt;li&gt;One line &amp;ldquo;Left-to-Right&amp;rdquo;, next line &amp;ldquo;Right-to-Left&amp;rdquo; etc. to reduce machine travel&lt;/li&gt;&#10;&lt;li&gt;prefers lines over dots, and longer lines over shorter ones for time optimization&lt;/li&gt;&#10;&lt;li&gt;draws lines vertically or horizontally&lt;/li&gt;&#10;&lt;li&gt;rapid move positioning now is packed into vectors that can be called to new Points(X, Y) simultaneously, not only one dimension at a time&lt;/li&gt;&#10;&lt;li&gt;New algorithm just needs 80% of the time for engraving compared to the old one&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2023-02-12_qr-codengrave_algo.jpg" alt="Image: Algorithm comparison 1.0 vs. 1.1"&gt;&lt;/figure&gt;&#10;&lt;h3 id="bugfixes"&gt;Bugfixes&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;splash image (&lt;code&gt;qruwu.jpg&lt;/code&gt;) is not shown on startup when &lt;code&gt;Persistence.dat&lt;/code&gt; is not available&lt;/li&gt;&#10;&lt;li&gt;Crash when pressing &lt;code&gt;Stop Draw&lt;/code&gt; although drawing has never been initiated before&lt;/li&gt;&#10;&lt;li&gt;Crash when adding a tool on an empty selecion list&lt;/li&gt;&#10;&lt;li&gt;Initialization of application with no tool selected disables dropdown menu&lt;/li&gt;&#10;&lt;li&gt;Fix a calculation error in QR-code size estimation&lt;/li&gt;&#10;&lt;li&gt;Fix an error pixel width calculation for tapered tools&lt;/li&gt;&#10;&lt;li&gt;Actually the currently selected tool is not shown &amp;ldquo;selected&amp;rdquo; in the dropdown on startup&lt;/li&gt;&#10;&lt;li&gt;QR-codes can grow larger than the display area&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="usability-improvements"&gt;Usability improvements&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Change default tool name from &lt;code&gt;name&lt;/code&gt; to &lt;code&gt;default&lt;/code&gt; for clarity&lt;/li&gt;&#10;&lt;li&gt;Make non-tapered (V-cut) tool the default tool for better usability&lt;/li&gt;&#10;&lt;li&gt;Setting a new XY-zero offset became more intuitive (Y down is negative, X right is positive)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="version-12"&gt;Version 1.2&lt;/h2&gt;&#10;&lt;p&gt;Minor change over 1.1, most importantly a Bugfix that could lead to tool change macro not being called.&#10;In addition, tapered tool angles are now taken into account when calculating track width and QR-code engrave size.&lt;/p&gt;&#10;&lt;h3 id="bugfixes-1"&gt;Bugfixes&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Tool select &lt;code&gt;Tx&lt;/code&gt; and tool change command &lt;code&gt;M06&lt;/code&gt; are now in the same line of the G-code to correctly trigger tool change macros&lt;/li&gt;&#10;&lt;li&gt;Actual selected tool not shown on startup, instead the first tool of the list was shown&lt;/li&gt;&#10;&lt;li&gt;QR-code drawing could grow larger than the screen. This is fixed now.&lt;/li&gt;&#10;&lt;li&gt;Crash fix when trying to update status although no engrave parameters available&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="usability-improvements-1"&gt;Usability improvements&lt;/h3&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Add application version to G-code as comment&lt;/li&gt;&#10;&lt;li&gt;Add &lt;code&gt;qr_&lt;/code&gt; prefix to G-code save-to dialog&amp;rsquo;s default name&lt;/li&gt;&#10;&lt;li&gt;Improve job duration estimate&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="availability"&gt;Availability&lt;/h2&gt;&#10;&lt;p&gt;The latest release is available for download &lt;a href="https://github.com/Schallbert/QR-codengrave/releases" target="_blank" rel="noopener noreferrer" class="external-link"&gt;here&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, free of charge for home and private use. For more info on the project, please find the &lt;a href="https://github.com/Schallbert/QR-codengrave" target="_blank" rel="noopener noreferrer" class="external-link"&gt;readme&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; and the &lt;a href="https://blog.schallbert.de/en/projects/qr-codengrave/"&gt;background information&lt;/a&gt; linked here.&lt;/p&gt;&#10;&lt;h2 id="migration-from-an-earlier-version"&gt;Migration from an earlier version&lt;/h2&gt;&#10;&lt;p&gt;It is possible to copy the persistence file to the new version&amp;rsquo;s directory. This way, saved tool lists, engrave parameters, and XY0 offsets can be ported to the new version. How it&amp;rsquo;s done:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Navigate into &lt;code&gt;src/assets&lt;/code&gt; of your QR-codengrave folder.&lt;/li&gt;&#10;&lt;li&gt;copy &lt;code&gt;Persistence.dat&lt;/code&gt; to a temporary folder or e.g. your Desktop.&lt;/li&gt;&#10;&lt;li&gt;Remove the current installation by deleting the folder.&lt;/li&gt;&#10;&lt;li&gt;Download the latest release, move it where your previous release was deployed.&lt;/li&gt;&#10;&lt;li&gt;Move the &lt;code&gt;Persistence.dat&lt;/code&gt; into &lt;code&gt;src/assets&lt;/code&gt; of your new installation and start the application.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item></channel></rss>