Main Components
Where to buy [Amazon Affiliate]:
Raspberry Pi 5 8Gb: https://amzn.to/44Y0caN
Arduino UNO: https://amzn.to/3U7xAoT
Extruded Aluminum: https://amzn.to/3zWdZBL
NEMA 17 Stepper Motors: https://amzn.to/4depfaN
Power Distribution Strip: https://amzn.to/3WvY71v
12V Power Supply Adapter 10A: https://amzn.to/3WDdXHW
Linear Bearing System (8mm diameter x 300mm long rods): https://amzn.to/4bQAc1e
SG90 9g Micro Servos: https://amzn.to/4domh43
TMC2208 Stepper Motor Driver: https://amzn.to/3YUF6pp
GT2 Timing Belt, Pulleys, Idlers, etc: https://amzn.to/40LqbQn
Pen Holder: https://www.thingiverse.com/thing:2459624
Raspberry Pi Python Code
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
from huggingface_hub import InferenceClient import numpy as np from PIL import Image, ImageFilter import os, io, serial, time from svg_to_gcode.svg_parser import parse_file from svg_to_gcode.compiler import Compiler, interfaces from svg_to_gcode.formulas import linear_map from svg_to_gcode import formulas from svg_to_gcode.geometry import Vector verbose = False # === AI CONFIG === HF_API_TOKEN = "hf_" #Enter API Token here Desired_Sketch = "Dog holding a drill" Sketch_Requirement_1 = "A black and white line drawing of " Sketch_Requirement_2 = ", minimal detail, no shading, no color (only black and white, no gray or colors), all lines must be thin (1 pixel thick), clean white background, sketch style, clean contours, no fill areas." PROMPT = Sketch_Requirement_1 + Desired_Sketch + Sketch_Requirement_2 #Initialize client (automatically uses a supported image model) client = InferenceClient(token=HF_API_TOKEN) # === CNC & GRBL FILE CONFIG === OUTPUT_FOLDER = "Output" PORT = "/dev/ttyUSB0" # Update to your serial port BAUD = 115200 class CustomInterface(interfaces.Gcode): def set_laser_power(self, power): if power < 0 or power > 1: raise ValueError(f"{power} is out of bounds. Laser power must be given between 0 and 1. " f"The interface will scale it correctly.") return f"M3 S{formulas.linear_map(0, 1000, power)};"#Ensure the output speed of the spindle pin is set to S1000 def linear_move(self, x=None, y=None, z=None): if self._next_speed is None: raise ValueError("Undefined movement speed. Call set_movement_speed before executing movement commands.") # Don't do anything if linear move was called without passing a value. if x is None and y is None and z is None: warnings.warn("linear_move command invoked without arguments.") return '' # Todo, investigate G0 command and replace movement speeds with G1 (normal speed) and G0 (fast move) command = "G1" if self._current_speed != self._next_speed: self._current_speed = self._next_speed command += f" F{self._current_speed}" # Move if not 0 and not None command += f" X{x:.3f}" if x is not None else '' command += f" Y{y:.3f}" if y is not None else '' command += f" Z{z:.3f}" if z is not None else '' if self.position is not None or (x is not None and y is not None): if x is None: x = self.position.x if y is None: y = self.position.y self.position = Vector(x, y) if verbose: print(f"Move to {x}, {y}, {z}") return command + ';' def generate_image(prompt): print("Generating AI image using InferenceClient...") img = client.text_to_image(prompt) out_path = os.path.join(OUTPUT_FOLDER, "ai_image.png") img.save(out_path) print("Saved image to:", out_path) return out_path def detect_edges(image_path): print("Converting thick lines to single-pixel centerlines...") img = Image.open(image_path).convert("L") img_array = np.array(img) # Method 1: Morphological skeleton (recommended) # This finds the centerline of thick shapes from scipy import ndimage from skimage.morphology import skeletonize, thin # Threshold the image to binary (assuming dark lines on light background) # Adjust threshold as needed - 128 works for most cases binary = img_array < 128 # Apply morphological skeletonization to get centerlines skeleton = skeletonize(binary) # Convert back to image format (white lines on black background for consistency) skeleton_img = (skeleton * 255).astype(np.uint8) # Remove border artifacts by setting a larger border to black width, height = img.size border_size = 5 skeleton_img[:border_size, :] = 0 # Top border skeleton_img[-border_size:, :] = 0 # Bottom border skeleton_img[:, :border_size] = 0 # Left border skeleton_img[:, -border_size:] = 0 # Right border # Convert back to PIL Image result_img = Image.fromarray(skeleton_img) edge_path = os.path.join(OUTPUT_FOLDER, "edges.png") result_img.save(edge_path) print(f"Saved centerline image to: {edge_path}") return edge_path def convert_to_svg(edge_path, max_size=160): print("Converting PNG to SVG with scaling...") pbm = edge_path.replace(".png", ".pbm") svg = edge_path.replace(".png", ".svg").replace("edges", "vector") # Get image dimensions to calculate scaling img = Image.open(edge_path) width, height = img.size # Calculate scale factor to fit within max_size scale_factor = min(max_size / width, max_size / height) if max(width, height) > max_size else 1.0 new_width = width * scale_factor new_height = height * scale_factor print(f"Scaling SVG from {width}x{height} to {new_width:.1f}x{new_height:.1f}") os.system(f"convert {edge_path} {pbm}") # Use potrace with width/height parameters to scale the SVG output os.system(f"potrace {pbm} -s -W {new_width:.0f}pt -H {new_height:.0f}pt -o {svg}") return svg def send_gcode(gcode_path,PORT,BAUD): try: print(f"Opening {gcode_path}...") with open(gcode_path, 'r') as f: # Filter out comments and empty lines lines = [] for line in f: line = line.strip() if line and not line.startswith(';') and not line.startswith('('): lines.append(line) print(f"Connecting to GRBL on {PORT} at {BAUD} baud...") # Open serial connection s = serial.Serial(PORT, BAUD) time.sleep(2) # Wait for GRBL to initialize s.flushInput() # Flush startup text in serial input # Wake up grbl s.write(b"\r\n\r\n") time.sleep(2) s.flushInput() # Unlock GRBL print("Unlocking GRBL...") s.write(b"$X\n") grbl_out = s.readline().decode().strip() print(f"Unlock: {grbl_out}") # Pen up s.write(b"M5\n") grbl_out = s.readline().decode().strip() print(f"Pen up: {grbl_out}") # GRBL streaming with proper buffer management RX_BUFFER_SIZE = 128 char_count = 0 for i, line in enumerate(lines): # Send line to GRBL line_bytes = (line + '\n').encode() char_count += len(line_bytes) # Check if buffer will overflow while char_count >= RX_BUFFER_SIZE - 1: out_temp = s.readline().decode().strip() if 'ok' in out_temp or 'error' in out_temp: char_count -= len(lines[i - (len(lines) - 1)]) + 1 if 'error' in out_temp: print(f"GRBL Error: {out_temp}") s.write(line_bytes) # Progress indicator if i % 50 == 0: print(f"Sent {i+1}/{len(lines)} lines") # Wait for all commands to complete print("Waiting for final commands to complete...") while char_count > 0: out_temp = s.readline().decode().strip() if 'ok' in out_temp or 'error' in out_temp: char_count -= 1 # Simplified counting for final cleanup if out_temp: print(f"GRBL: {out_temp}") # Close connection s.close() print("G-code streaming completed successfully!") return True except Exception as e: print(f"Error during G-code streaming: {e}") return False if __name__ == "__main__": os.makedirs(OUTPUT_FOLDER, exist_ok=True) try: #img = os.path.join(OUTPUT_FOLDER, "ai_image.png")#Use line below to use AI instead of saved file img = generate_image(PROMPT) edges = detect_edges(img) # === Show the edges image === Image.open(edges).show() input("Press ENTER after viewing the image to continue...") svg = convert_to_svg(edges) gcode_path = os.path.join(OUTPUT_FOLDER, "output.gcode") gcode_compiler = Compiler(CustomInterface, movement_speed=10000, cutting_speed=10000, pass_depth=0, dwell_time=0.25, unit=None, custom_header=["G21 ; mm units","G17 ; XY plane","G40 ; Cancel cutter comp","G49 ; Cancel tool length","M5 ; Pen up","$H; Home Machine"], custom_footer=["M5 ; Pen up","G0 X 10.000 Y 10.000","$H; Home Machine","M2 ; End"]) curves = parse_file(svg) gcode_compiler.append_curves(curves) gcode_compiler.compile_to_file(gcode_path, passes=1) send_gcode(gcode_path,PORT,BAUD) except Exception as e: print("? Pipeline failed:", e) |
Arduino IDE Code (for Servo)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
#include <config.h> #include <coolant_control.h> #include <cpu_map.h> #include <defaults.h> #include <eeprom.h> #include <gcode.h> #include <grbl.h> #include <jog.h> #include <limits.h> #include <motion_control.h> #include <nuts_bolts.h> #include <planner.h> #include <print.h> #include <probe.h> #include <protocol.h> #include <report.h> #include <serial.h> #include <settings.h> #include <spindle_control.h> #include <stepper.h> #include <system.h> #include <config.h> #include <coolant_control.h> #include <cpu_map.h> #include <defaults.h> #include <eeprom.h> #include <gcode.h> #include <grbl.h> #include <jog.h> #include <limits.h> #include <motion_control.h> #include <nuts_bolts.h> #include <planner.h> #include <print.h> #include <probe.h> #include <protocol.h> #include <report.h> #include <serial.h> #include <settings.h> #include <spindle_control.h> #include <stepper.h> #include <system.h> #include <Servo.h> const int controlPin = 12; const int servoPin = 11; Servo myServo; void setup() { pinMode(controlPin, INPUT_PULLUP); // Use internal pull-up myServo.attach(servoPin); } void loop() { if (digitalRead(controlPin) == LOW) { myServo.write(130); } else { myServo.write(180); } } |
