Spline Cutting Macro (HAAS Macro)
Wrote a quick spline cutting routine for cutting splines for the side. Supports starting at a given tooth, roughing depth, taper adjustment and finish depths. Can come from -/+Y and go any direction on X. Nothing too spectacular but I leave it here for fun.
%
O2023 (SPLINE CUTTING PROGRAM)
(STEVEN MACKAAY 2025)
(!!!USE AT YOUR OWN RISK!!!)
(SETUP VARIABLES)
#100 = 38. (TOTAL NUMBER OF TEETH/SPLINES)
#101 = -.5 (START X POSITION)
#102 = 4.13 (END X POSITION)
#103 = -2.55 (CLEARANCE Y POSITION - ABSOLUTE DISTANCE FROM Y0)
#104 = -2.35 (START Y POSITION - ABSOLUTE DISTANCE FROM Y0)
#105 = -2.2323 (FINISH Y POSITION - ABSOLUTE DISTANCE FROM Y0)
#106 = 0.02 (Y STEPDOWN AMOUNT - POSITIVE VALUE)
#107 = 0.001 (Y FINISH PASS AMOUNT - POSITIVE VALUE)
#108 = 1.25 (CUTTER RADIUS - POSITIVE VALUE)
#109 = 0.002 (TAPER AMOUNT Y PER UNIT X - POSITIVE TAPER WIDENS AWAY FROM X AXIS AT ENDX)
#110 = 15. (CUTTING FEEDRATE F)
#111 = 5.0 (PLUNGE FEEDRATE F)
#112 = 50.0 (CLEARANCE FEEDRATE F)
#113 = 460 (SPINDLE SPEED S)
#114 = 1.0 (START TOOTH NUMBER 1 TO #100)
#115 = 4.0 (SPINDLE DIRECTION 3 FOR M03 CW, 4 FOR M04 CCW)
(CALCULATED VARIABLES)
#120 = 0 (CURRENT TOOTH NUMBER)
#121 = 0 (ANGLE FOR CURRENT TOOTH)
#122 = 0 (CURRENT Y POSITION FOR STEPDOWN)
#123 = 0 (CUTTER COMPENSATION - WILL BE POSITIVE OR NEGATIVE BASED ON Y SIGN)
#124 = 0 (DIRECTION FLAG - 1 FOR POSITIVE Y, -1 FOR NEGATIVE Y)
#125 = 0 (TOTAL NUMBER OF STEPDOWNS)
#126 = 0 (CURRENT STEPDOWN NUMBER)
#127 = 0 (STEP DIRECTION - SIGN OF STEP TOWARDS Y0)
#128 = 0 (FINISH PASS Y POSITION)
#130 = 0 (REMAINING DISTANCE AFTER FULL STEPDOWNS)
(DETERMINE APPROACH DIRECTION)
IF [[#103 LT 0]] THEN #124 = -1 (APPROACHING FROM NEGATIVE Y)
IF [[#103 GT 0]] THEN #124 = 1 (APPROACHING FROM POSITIVE Y)
(DETERMINE STEP DIRECTION - TOWARDS Y0)
#127 = -#124 (STEP DIRECTION TOWARDS Y0)
(CALCULATE CUTTER COMPENSATION)
IF [[#124 LT 0]] THEN #123 = -#108 (NEGATIVE Y - SUBTRACT CUTTER RADIUS)
IF [[#124 GT 0]] THEN #123 = #108 (POSITIVE Y - ADD CUTTER RADIUS)
(CALCULATE FINISH PASS Y POSITION)
#128 = #105 - #127 * #107 (POSITION BEFORE FINISH PASS)
(CALCULATE NUMBER OF STEPDOWNS)
#125 = FIX[ABS[#104 - #128] / #106] (NUMBER OF FULL STEPDOWNS)
#130 = ABS[#104 - #128] - [#125 * #106] (REMAINING DISTANCE AFTER FULL STEPDOWNS)
IF [[#130 GT 0.0001]] THEN #125 = #125 + 1 (ADD ONE MORE STEPDOWN IF NEEDED)
G90 (ABSOLUTE POSITIONING)
G17 (XY PLANE SELECTION)
G20 (INCH PROGRAMMING)
G40 (CANCEL CUTTER COMPENSATION)
G49 (CANCEL TOOL LENGTH COMPENSATION)
G80 (CANCEL CANNED CYCLES)
T15 M6 (SELECT TOOL 15)
G43 H15 (APPLY TOOL LENGTH COMPENSATION)
S#113 (SET SPINDLE SPEED)
(MAIN LOOP FOR EACH TOOTH)
#120 = #114 (START AT SPECIFIED TOOTH)
M08 (COOLANT ON)
WHILE [[#120 LE #100]] DO1
(CALCULATE TOOTH ANGLE)
#121 = 360.0 * [#120 - 1] / #100 (ANGLE FOR CURRENT TOOTH)
(POSITION TO TOOTH START)
G0 A#121 (ROTATE TO TOOTH ANGLE)
G0 X#101 Y[#103 + #123] (RAPID TO START POSITION WITH CLEARANCE Y AND CUTTER COMPENSATION)
(START SPINDLE AND COOLANT)
IF [[#115 EQ 3]] THEN M03
IF [[#115 EQ 4]] THEN M04
(RAPID TO Z0)
G0 Z0
(MOVE TO START Y POSITION)
G1 Y[#104 + #123] F#112 (FEED TO START Y WITH CLEARANCE FEED AND CUTTER COMPENSATION)
(STEPDOWN LOOP)
#126 = 1 (INITIALIZE STEPDOWN COUNTER)
WHILE [[#126 LE #125]] DO2
(CALCULATE CURRENT Y STEPDOWN)
IF [[#126 LT #125]] THEN #122 = #104 + #127 * #106 * #126 (INCREMENTAL STEPDOWNS TOWARDS Y0)
IF [[#126 EQ #125]] THEN #122 = #128 (LAST STEPDOWN BEFORE FINISH PASS)
(PLUNGE TO STEPDOWN)
G1 Y[#122 + #123] F#111 (PLUNGE TO CURRENT DEPTH WITH PLUNGE FEED AND CUTTER COMPENSATION)
(CUT TO END X WITH TAPER)
G1 X#102 Y[#122 + #123 + [#109 * #124]] F#110 (CUT TO END X WITH TAPER ADJUSTMENT)
(RETRACT TO CLEARANCE)
G0 Y[#103 + #123] (RAPID BACK TO CLEARANCE Y WITH CUTTER COMPENSATION)
(RETURN TO START X)
G0 X#101 (RAPID BACK TO START X)
(INCREMENT STEPDOWN COUNTER)
#126 = #126 + 1
END2
(FINISH PASS)
G0 Y[#103 + #123] (RAPID TO CLEARANCE Y)
G0 X#101 (RAPID TO START X)
G1 Y[#105 + #123] F#111 (PLUNGE TO FINISH Y WITH PLUNGE FEED)
G1 X#102 Y[#105 + #123 + [#109 * #124]] F#110 (CUT FINISH PASS TO END X WITH TAPER)
G0 Y[#103 + #123] (RAPID BACK TO CLEARANCE Y)
(INCREMENT TOOTH COUNTER)
#120 = #120 + 1
END1
(PROGRAM END)
M5 (SPINDLE STOP)
M9 (COOLANT OFF)
G28 G91 Z0 (RETURN TO Z HOME)
G28 G91 Y0 (RETURN TO Y HOME)
M30 (END PROGRAM)
%

Still selling, Comparators!
Back in 2018 I designed a type of comparator (and setting master) for compressor valve grooves. Just finished another batch of 10 of them, it went well. Nowadays we wrap the master in paper along with oil in order to prevent rusting. Sending them to the southern states made them rust a fair amount.
Just leaving this here for posterity.





They are a pain in the ass to align well though, I am going to do a mild redesign on the next set. I always say that but we always need them yesterday.
| Posted in Design, Machining | Comments Off on Still selling, Comparators!
NCComm – An RS232 device for CNC communication
So, I wanted to try my hand at making a communication device between a standalone device and the CNC, something I wanted. I achieved that. I wanted it made out of the most common parts an average person can easily buy off Aliexpress. You can buy one for like 200 bucks but it was fun to make nonetheless. Things you need for this build:
- Arduino Mega 2560 or 1280 (I used this because it had enough IO, you could use a nano or a standard arduino but you’d wanna use a different keypad)
- A membrane keypad (This one is a 4×5 keypad, so 9 IO are needed)
- An SD card module that accepts 5v. (I used the large style, it is more hardy in an industrial environment.)
- A 2004 LCD module with an I2C backpack (You can use a standard LCD without a backpack but you’ll want to change the way the output is used, I made a wrapper for that.)
- An RS232 module. (This one should be with RTS and CTS, most CNC’s I know of need handshaking or just a CTS signal unless you tie the RTS CTS lines together on the cable)
- Not necessary but I used a hat for the 2560 just to simplify the connections to the board to use standard headers.
Simple, straightforward. Here is the code I have thus far. It’s not too solid and handshaking isn;t working perfect but it works thus far.
Here: https://www.smackaay.com/wp-content/uploads/2025/04/NCComm.zip
Enjoy
| Posted in Electronics, Machining, Programming | Comments Off on NCComm – An RS232 device for CNC communication
DooM WAD Level Selector
I was going through old CDs and came across some doom wads, levels for the old DooM. So I fired up GZdoom and tried a few but it got kind of tiring going through command line so I had AI (mostly) write a WAD file loader. The result is the aptly named Doom PWAD Manager. It can load pwads and display a preview and give you info.


You can define multiple engines and multiple directories. You can have directory specific command line terms or based on the engine. The WAD files MUST be unzipped into subdirectories in whatever directories you choose, it will not read from a zip.
Below is the download, I took the liberty of making it an EXE if you like, source is there too though.
https://www.smackaay.com/wp-content/uploads/2025/03/PWADManager.zip
enjoy.
| Posted in Miscellaneous stuff, Programming | Comments Off on DooM WAD Level Selector
Texture Generator and Claude 3.7 Sonnet
So, I was at the mall, waiting, and I was thinking about the old Wolfenstein 3D. I was thinking about the textures and wondering if you could algorithmically generate textures to make a game like that. I decided to ask Claude 3.7 Sonnet to make a program to do just that. Well, it made a 2800 line program in JS. Amazing.
Not much to say about it aside from the fact that it seems to work. Enjoy!
Oh, also, here is a sound generator too!
Ripping an image based PDF to text (and old TurboBASIC commands)
Recently, I wanted to pull text from a PDF that was scanned in from an old manual, namely the Borland Turbo Basic manual. The text that was in the document was garbage, nothing there to be done, so I decided to write something that would allow me to:
1. Load a PDF
2. Rip the images from the PDF
3. OCR the images to generate the text
So, here it is.
import fitz # PyMuPDF
from PIL import Image
import pytesseract
import io
# Set the path to the Tesseract executable
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"
def pdf_to_text(pdf_path: str):
# Open the PDF file
pdf_document = fitz.open(pdf_path)
text_output = []
# Iterate through each page in the PDF
for page_num in range(pdf_document.page_count):
print(f"Processing page {page_num + 1} of {pdf_document.page_count}...")
# Get the page object
page = pdf_document.load_page(page_num)
# Convert the page to a pixmap (image)
pix = page.get_pixmap()
# Convert the pixmap into a PIL image
img = Image.open(io.BytesIO(pix.tobytes("png")))
# Run OCR on the image using pytesseract
page_text = pytesseract.image_to_string(img)
# Append the extracted text to the list
text_output.append(f"Page {page_num + 1}:\n{page_text}\n")
# Close the document after processing
pdf_document.close()
# Join all pages' text into a single string
return "\n".join(text_output)
if __name__ == "__main__":
pdf_path = "BorTurboBasic.pdf" # Replace with the path to your PDF file
extracted_text = pdf_to_text(pdf_path)
# Save the text to a file
with open("output_text.txt", "w", encoding="utf-8") as f:
f.write(extracted_text)
print("OCR complete. Text extracted and saved to 'output_text.txt'.")
You need to install Tesseract in order to rip the text and put the location into the pytesseract.pytesseract.tesseract_cmd variable. Also, it’s not perfect and it’s not great but it’s better than nothing. If the text is super garbled then it works to simply dump the text into something like Google Gemini and have it rip info you need from it.
Speaking of which, here is a cheat-sheet formatted and parsed from an old Turbo Basic manual.
Turbo Basic Commands (smackaay.com)
Enjoy!
| Posted in Programming | Comments Off on Ripping an image based PDF to text (and old TurboBASIC commands)
Demographic charts
So, I’m pretty interested in the state of the world and how populations in wealthier countries are starting to fall. I was kind of looking at charts, population pyramid charts to be exact and I found that population pyramids, while interesting don’t show the decline in as interesting way as a line graph. So I took it upon myself to grab the info and change them into line graphs that can be viewed more easily. Below are a bunch for your perusal.
Working age population – WorldWorking age population – Canada
Working age population – Japan
Working age population – Least Developed Countries
Working age population – More Developed Countries
Working age population – Russian Federation
Working age population – United Kingdom
Working age population – United States
Population – World
Population – Canada
Population – Japan
Population – Least Developed Countries
Population – More Developed Countries
Population – Russian Federation
Population – United Kingdom
Population – United States
Population Percentage – World
Population Percentage – Canada
Population Percentage – Japan
Population Percentage – Least Developed Countries
Population Percentage – More Developed Countries
Population Percentage – Russian Federation
Population Percentage – United Kingdom
Population Percentage – United States
Working Population Percentage – World
Working Population Percentage – Canada
Working Population Percentage – Japan
Working Population Percentage – Least Developed Countries
Working Population Percentage – More Developed Countries
Working Population Percentage – Russian Federation
Working Population Percentage – United Kingdom
Working Population Percentage – United States
There we go, it’s pretty straightforward. You can click the labels on the side to turn on/off certain lines to highlight what you want to see. Clicking on a link will open new window, also, if you change the windows size, please refresh to make it fit your window. To explain we have a number of data sets here, population of a given region, percentage of population of a given region, population of people of working age of a given region and finally a percentage of the working population of a given region. I chose regions/countries that made sense to me. I could’ve picked China but I don’t trust their numbers on anything, even bad numbers. Enjoy!
I think one of the neat things to look at as a Canadian is the percentage based charts, particularly those of working age. I separated the charts out as a basis of percentage in groups from 0-14, 15-64 and 65+. You look at these things and you can see why we’re having some issues. For example, we here in Canada have 45% of the children we had in 1960 on a per capita basis. We also crossed the threshold of having more 65+ people than working age people back in 2010, the line was crossed for women back in 1988.
Another thing I see is that, for Canada, the percentage of adults goes up in bumps over the years but not so much for children. I suspect that is because despite having so much immigration in certain spots, even they are not having that many children either. That’s interesting to me as well.
Anyways, hope you enjoy. These charts are an interesting way to look at demographics in a different way from pyramid charts.
| Posted in Miscellaneous stuff | Comments Off on Demographic charts
New prompt permutation script
A while back I made a prompt permutation script for generating large numbers of image prompts for use in automatic1111. I updated it with a new operator, the incremental operator ‘&’ so it will cycle through the list items instead of choosing random ones. Here is a sample prompt and output. Basically a fancy search and replace but I use it quite often.
photo, a %SIZE brutalist &BUILDING on a sunny day (this is the base prompt)
photo, painting
brutalist, post-modern, deconstructivism
sunny day, night time
%SIZE, small, medium, large, huge
&BUILDING, house, tower, factory, school
Output:
photo, a small brutalist house on a sunny day
photo, a huge brutalist tower on a night time
photo, a medium post-modern factory on a sunny day
photo, a medium post-modern school on a night time
photo, a medium deconstructivism house on a sunny day
photo, a large deconstructivism tower on a night time
painting, a medium brutalist factory on a sunny day
painting, a small brutalist school on a night time
painting, a huge post-modern house on a sunny day
painting, a small post-modern tower on a night time
painting, a large deconstructivism factory on a sunny day
painting, a medium deconstructivism school on a night time
Anyways, here is the python script along with an html version so you can use it with an interface of sorts.
http://smackaay.com/files/ppermute/ppermute.html The little webpage for it.
Here is the python script.
import itertools
import random
# File path assignments
INPUT_FILE_PATH = 'img5.txt' # Change this to the path of your input file
OUTPUT_FILE_PATH = 'output.txt' # Change this to the desired path for the output file
def load_file(file_path):
with open(file_path, 'r') as file:
lines = file.readlines()
return [line.strip() for line in lines]
def generate_permutations(prompt, modifiers, random_modifiers, increment_modifiers):
all_combinations = list(itertools.product(*modifiers))
permutations = []
increment_counters = {key: 0 for key in increment_modifiers.keys()}
for combination in all_combinations:
new_prompt = prompt
for original, replacement in zip(modifiers, combination):
new_prompt = replace_first(new_prompt, original[0], replacement)
# Handle random modifiers
for placeholder, values in random_modifiers.items():
if placeholder in new_prompt:
replacement = random.choice(values)
new_prompt = new_prompt.replace(placeholder, replacement, 1)
# Handle increment modifiers
for placeholder, values in increment_modifiers.items():
if placeholder in new_prompt:
replacement = values[increment_counters[placeholder] % len(values)]
new_prompt = new_prompt.replace(placeholder, replacement, 1)
increment_counters[placeholder] += 1
# Remove placeholders from the final prompt
new_prompt = remove_placeholders(new_prompt, random_modifiers.keys() | increment_modifiers.keys())
permutations.append(new_prompt)
return permutations
def replace_first(text, search, replacement):
if search not in text:
raise ValueError(f"Term '{search}' not found in the prompt.")
return text.replace(search, replacement, 1)
def remove_placeholders(text, placeholders):
for placeholder in placeholders:
text = text.replace(placeholder, "")
return text
def save_to_file(output_path, permutations):
with open(output_path, 'w') as file:
for permutation in permutations:
file.write(permutation + '\n')
def main():
lines = load_file(INPUT_FILE_PATH)
if not lines:
print("The input file is empty.")
return
prompt = lines[0]
modifiers = [line.split(', ') for line in lines[1:] if not line.startswith('%') and not line.startswith('&')]
random_modifiers = {}
increment_modifiers = {}
for line in lines[1:]:
if line.startswith('%'):
parts = line.split(', ')
key = parts[0]
values = parts[1:]
random_modifiers[key] = values
elif line.startswith('&'):
parts = line.split(', ')
key = parts[0]
values = parts[1:]
increment_modifiers[key] = values
try:
all_permutations = generate_permutations(prompt, modifiers, random_modifiers, increment_modifiers)
save_to_file(OUTPUT_FILE_PATH, all_permutations)
print(f"Generated prompts have been saved to {OUTPUT_FILE_PATH}")
print(f"Total number of permutations: {len(all_permutations)}")
except ValueError as e:
print(f"Error: {e}")
if __name__ == "__main__":
main()
| Posted in Personal stuff, Programming | Comments Off on New prompt permutation script
Gage Block Buildup Calculator
So, I have that Python source code on the side of my site there for calculating gage block buildups. I figured it was time to turn it into a JS program so that people can just access it from the web. Not super complicated but useful nonetheless.
http://smackaay.com/files/gbcalc/gbcalc.html
Features as follows:
- Imperial 81, 28, 34, 36 and 92 pc sets
- Metric 88, 47 and 112 pc sets
- Multiple ( as many as you want) results
- The ability to remove blocks from the list if they are either missing or used in a previous buildup. This is handy.
Anyways, hope somebody out there enjoys this!
| Posted in Machining, Programming | Comments Off on Gage Block Buildup Calculator
The YouTube Recycle Bin
I was watching a video from a youtuber KVN AUST. The video: https://youtu.be/8uHFm6LK6PE?si=SLIaCEzNBx_iL97V It featured a map for looking at and searching for odd videos across YouTube. It’s pretty fun just to see little slices of life or weird things people would bother uploading so I made a little JS proggy to generate the most common search terms.
Select the prefix and the type of random term you want to find, click on Generate Search Term and then click Search on YouTube. You can select No Spaces or With Quotes if certain things don’t work. The random date is anything in the last 20 years. Enjoy!
YouTube Recycle Bin Search Generator
| Posted in Personal stuff, Programming | Comments Off on The YouTube Recycle Bin
| Posted in Machining, Programming | Comments Off on Spline Cutting Macro (HAAS Macro)