Compare commits

...

10 Commits

12 changed files with 6145 additions and 29 deletions

View File

@@ -1,21 +0,0 @@
class GCodeParser():
def __init__(self, gcode_file):
self.gcode_file = gcode_file
self.lines = []
self._parse_gcode()
def _parse_gcode(self):
from pygcode import Line, Machine
machine = Machine()
positions = []
with open(self.gcode_file, "r") as gcode_file:
for line_text in gcode_file:
line = Line(line_text)
if line.block.gcodes:
machine.process_gcodes(*line.block.gcodes)
positions.append((machine.pos.X, machine.pos.Y, machine.pos.Z))
self.lines = positions
def get_positions(self):
return self.lines

210
gcodeinterpreter.py Normal file
View File

@@ -0,0 +1,210 @@
import math
import re
class GcodeInterpreter:
"""
A basic G-code interpreter that converts G-code commands into a list of coordinates.
Supports G00, G01, G02, and G03 commands.
Attributes:
current_position (list): The current position in 3D space [X, Y, Z].
feed_rate (float): The current feed rate.
coordinates (list): The list of calculated coordinates.
segment_multiplier (float): Multiplier for distance to determine number of segments.
max_segments (int): Maximum number of segments allowed.
"""
def __init__(self, initial_position=[0, 0, 0], initial_feed_rate=0, segment_multiplier=2, max_segments=50):
"""
Initializes the GcodeInterpreter with the initial position, feed rate, segment multiplier, and maximum segments.
Args:
initial_position (list, optional): The initial position [X, Y, Z]. Defaults to [0, 0, 0].
initial_feed_rate (float, optional): The initial feed rate. Defaults to 0.
segment_multiplier (float, optional): Multiplier for distance to determine number of segments. Defaults to 10.0
max_segments (int, optional): Maximum number of segments allowed. Defaults to 100.
"""
self.current_position = initial_position[:] # Use a copy to avoid modifying the original
self.feed_rate = initial_feed_rate
self.coordinates = [initial_position[:]] # Store the initial position
self.segment_multiplier = segment_multiplier # Added segment multiplier
self.max_segments = max_segments # Added max segments
def parse_file(self, filename):
"""
Parses a G-code file and processes each line.
Args:
filename (str): The path to the G-code file.
Returns:
GcodeInterpreter: The instance of the GcodeInterpreter with processed coordinates.
"""
with open(filename, 'r') as file:
for line in file:
self.parse_line(line)
return self
def parse_line(self, line):
"""
Parses a single line of G-code.
Args:
line (str): The G-code line to parse.
"""
line = line.strip()
if not line or line.startswith('%') or line.startswith('('): # Skip empty lines and lines starting with '%'
return
# Use regular expression for more robust parsing
parts = re.findall(r'([A-Za-z])([-+]?\d*\.?\d*)', line) # Find all letter-number pairs
if not parts:
return # Skip lines without any recognized G-code commands
command = parts[0][0].upper() + parts[0][1]
args = {}
for part in parts:
if part[0].upper() != command:
try:
args[part[0].upper()] = float(part[1]) if part[1] else 0.0 # convert the number part to float
except ValueError:
print(f"Warning: Could not convert value '{part[1]}' to float for argument {part[0]}. Skipping.")
args[part[0].upper()] = 0.0
self.process_command(command, args)
def process_command(self, command, args):
"""
Processes a G-code command and calls the appropriate function.
Args:
command (str): The G-code command (e.g., 'G00', 'G01', 'G02', 'G03').
args (dict): A dictionary of arguments for the command (e.g., {'X': 10, 'Y': 20}).
"""
if not command: # Add this check
return
if command in ('G00', 'G01'):
self.process_g00_g01(command, args)
elif command == 'G02':
self.process_g02_g03(command, args, clockwise=True)
elif command == 'G03':
self.process_g02_g03(command, args, clockwise=False)
elif command == 'G04':
self.process_g04(args)
elif command == 'F':
self.feed_rate = args.get('F', self.feed_rate) # Keep the current feed rate if not provided
# Add other G-code commands as needed (e.g., G02, G03, G28, etc.)
else:
print(f"Unsupported G-code command: {command}")
def process_g00_g01(self, command, args):
"""
Processes G00 (Rapid Linear Move) and G01 (Controlled Linear Move) commands.
Args:
command (str): The G-code command ('G00' or 'G01').
args (dict): A dictionary of arguments for the command (e.g., {'X': 10, 'Y': 20, 'Z': 5}).
"""
new_position = [args.get('X', self.current_position[0]),
args.get('Y', self.current_position[1]),
args.get('Z', self.current_position[2])]
# Calculate the distance between the current position and the new position
distance = math.sqrt(
(new_position[0] - self.current_position[0]) ** 2 +
(new_position[1] - self.current_position[1]) ** 2 +
(new_position[2] - self.current_position[2]) ** 2
)
# Number of segments. Increase for smoother lines, but use a reasonable maximum.
segments = int(distance * self.segment_multiplier) + 1
segments = min(segments, self.max_segments) # Limit the maximum number of segments
for i in range(segments + 1):
x = self.current_position[0] + (new_position[0] - self.current_position[0]) * i / segments
y = self.current_position[1] + (new_position[1] - self.current_position[1]) * i / segments
z = self.current_position[2] + (new_position[2] - self.current_position[2]) * i / segments
self.current_position = [x, y, z]
self.coordinates.append([x, y, z])
def process_g02_g03(self, command, args, clockwise=True):
"""
Processes G02 (Clockwise Circular Interpolation) and G03 (Counter-Clockwise Circular Interpolation) commands.
Args:
command (str): The G-code command ('G02' or 'G03').
args (dict): A dictionary of arguments for the command.
clockwise (bool): True for G02 (clockwise), False for G03 (counter-clockwise).
"""
new_position = [args.get('X', self.current_position[0]),
args.get('Y', self.current_position[1]),
args.get('Z', self.current_position[2])]
center = [self.current_position[0] + args.get('I', 0),
self.current_position[1] + args.get('J', 0),
self.current_position[2] + args.get('K', 0)]
radius = math.sqrt((self.current_position[0] - center[0]) ** 2 +
(self.current_position[1] - center[1]) ** 2 +
(self.current_position[2] - center[2]) ** 2)
# Handle the case where the radius is zero
if radius == 0:
print(f"Warning: Radius is zero for {command}. No interpolation performed.")
return
# Calculate the angle between the current point and the target point
start_angle = math.atan2(self.current_position[1] - center[1], self.current_position[0] - center[0])
end_angle = math.atan2(new_position[1] - center[1], new_position[0] - center[0])
# Ensure angles are between 0 and 2*pi
if start_angle < 0:
start_angle += 2 * math.pi
if end_angle < 0:
end_angle += 2 * math.pi
# Calculate the total angle
total_angle = end_angle - start_angle
if clockwise:
if total_angle > 0:
total_angle -= 2 * math.pi
else:
if total_angle < 0:
total_angle += 2 * math.pi
# Number of segments. Increase for smoother curves.
arc_length = radius * abs(total_angle)
segments = int(arc_length * self.segment_multiplier) + 1 # Dynamic segments, at least 1.
segments = min(segments, self.max_segments) # Limit max segments
angle_increment = total_angle / segments
for i in range(segments + 1):
angle = start_angle + i * angle_increment
x = center[0] + radius * math.cos(angle)
y = center[1] + radius * math.sin(angle)
z = (self.current_position[2] + (new_position[2] - self.current_position[2]) * i / segments)
self.current_position = [x, y, z]
self.coordinates.append([x, y, z])
def process_g04(self, args):
"""Processes G04 (Dwell) command. For now, just prints a message.
Args:
args (dict): A dictionary of arguments, containing 'P' for the dwell time in milliseconds
"""
dwell_time = args.get('P', 0)
print(f"G04 Dwell for {dwell_time} milliseconds")
# In a real implementation, you would pause execution here. For this example, we just print.
pass
def get_coordinates(self):
"""
Returns the list of calculated coordinates.
Returns:
list: The list of coordinates.
"""
return self.coordinates
def reset(self):
"""Resets the interpreter to its initial state."""
self.current_position = [0, 0, 0]
self.feed_rate = 0
self.coordinates = [[0,0,0]]

16
main.py
View File

@@ -1,6 +1,7 @@
#! /usr/bin/env python
from argparse import ArgumentParser
from gcode_parser import GCodeParser
from gcodeinterpreter import GcodeInterpreter
from pwm_driver import PWMDriver
from visualizer import Visualizer
# A4
@@ -12,12 +13,19 @@ if __name__ == "__main__":
argparser.add_argument("-f", "--file", type=str)
argparser.add_argument("-W", "--width", type=float, default=WIDTH_MM, required=False)
argparser.add_argument("-H", "--height", type=float, default=HEIGHT_MM, required=False)
argparser.add_argument("-V", "--visualize", action="store_true", default=False, required=False)
argparser.add_argument("-P", "--port", type=str, default=None)
args = argparser.parse_args()
parser = GCodeParser(args.file)
positions = parser.get_positions()
parser = GcodeInterpreter().parse_file(args.file)
positions = parser.get_coordinates()
screen_dimensions = (1024*1.5, 1024*1.5 * HEIGHT_MM/WIDTH_MM)
if args.visualize:
screen_dimensions = (1024*1.5, 1024*1.5 * args.height/args.width)
visualizer = Visualizer(positions, screen_dimensions, (args.width, args.height))
visualizer.visualize()
if args.port:
pwm = PWMDriver(positions, (args.width, args.height), args.port)
pwm.run()

18
port-finder.py Executable file
View File

@@ -0,0 +1,18 @@
#! /usr/bin/env python3
import serial.tools.list_ports
from serial.tools.list_ports_common import ListPortInfo
if __name__ == "__main__":
ports = serial.tools.list_ports.comports()
for port in ports:
if port.interface and port.interface.startswith("CircuitPython CDC2"): # CDC2 is the data-port
print(f"Port: {port.device}")
print(f" Description: {port.description}")
print(f" Manufacturer: {port.manufacturer}")
print(f" VID: {port.vid}")
print(f" PID: {port.pid}")
print(f" Serial Number: {port.serial_number}")
print(f" Location: {port.location}")
print(f" Interface: {port.interface}")
print()

39
pwm_driver.py Normal file
View File

@@ -0,0 +1,39 @@
class PWMDriver():
def __init__(self, positions : list[tuple[float, float, float]], gcode_size : tuple[float, float], port : str, baudrate : int = 115200) -> None:
self.positions = positions
self.gcode_size = gcode_size
self.port = port
self.baudrate = baudrate
self._init_pwm()
def _init_pwm(self) -> None:
from serial import Serial
self.serial = Serial(self.port, self.baudrate)
def _send_position(self, pos : tuple[float, float, float]) -> None:
x, y, z = pos
# Convert coordinates to PWM values
precision = ((2 ** 16) - 1)
pwm_x : int = int(x / self.gcode_size[0] * precision)
pwm_y : int = int(y / self.gcode_size[1] * precision)
pen : int = 1 if z < 0 else 0
# Send PWM values to the serial port
print(f"Sending PWM values: X={pwm_x:<5}, Y={pwm_y:<5}, Pen={pen}")
self.serial.write(f"{pwm_x},{pwm_y},{pen}\n".encode())
def _position_reached(self) -> bool:
# Check if the position has been reached
return self.serial.readline().decode().strip() == "OK"
def _wait_for_position(self) -> None:
# Wait until the position is reached
while not self._position_reached():
pass
def run(self) -> None:
for pos in self.positions:
self._send_position(pos)
self._wait_for_position()
print("All positions sent successfully.")

View File

@@ -1,2 +1,3 @@
pygame==2.6.1
pygcode==0.2.1
colorama==0.4.6
pyserial

26
set_pos.py Executable file
View File

@@ -0,0 +1,26 @@
#! /usr/bin/env python3
import serial
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Set the position of the PWM driver.")
parser.add_argument("x", type=float, help="X coordinate [0 - 1]")
parser.add_argument("y", type=float, help="Y coordinate [0 - 1]")
parser.add_argument("pen", type=int, help="pen down (True) or pen up (False)")
parser.add_argument("-p", "--port", type=str, required=True, help="Serial port")
args = parser.parse_args()
# Open the serial port
ser = serial.Serial(args.port, 115200)
# Saturate values to [0, 1]
args.x = int(max(0, min(1, args.x)) * 0xFFFF)
args.y = int(max(0, min(1, args.y)) * 0xFFFF)
args.z = int(max(0, min(1, args.pen)))
# Send the position
print(f"{args.x},{args.y},{args.z}\n")
ser.write(f"{args.x},{args.y},{args.z}\n".encode())
# Close the serial port
ser.close()

3669
test/gothenburg.gcode Normal file

File diff suppressed because it is too large Load Diff

217
test/test_arc.gcode Normal file
View File

@@ -0,0 +1,217 @@
%
(Header)
(Generated by gcodetools from Inkscape.)
(Using default header. To add your own header create file "header" in the output dir.)
M3
(Header end.)
G21 (All units in mm)
(Start cutting path id: path686)
(Change tool to Cylindrical cutter)
G00 Z4.000000
G00 X14.552637 Y93.606430
G01 Z-0.100000 F100.0(Penetrate)
G02 X20.744531 Y97.132218 Z-0.100000 I58.837019 J-96.128067 F400.000000
G02 X25.716552 Y99.525614 Z-0.100000 I34.485847 J-65.279503
G02 X30.860300 Y101.491015 Z-0.100000 I23.790996 J-54.550899
G02 X34.959887 Y102.600926 Z-0.100000 I12.260142 J-37.158180
G02 X39.150265 Y103.209091 Z-0.100000 I6.697716 J-31.408261
G02 X42.532580 Y103.224539 Z-0.100000 I1.795934 J-22.932442
G02 X45.870181 Y102.713250 Z-0.100000 I-1.404588 J-20.318145
G02 X48.684570 Y101.788627 Z-0.100000 I-4.188822 J-17.495591
G02 X51.317759 Y100.411842 Z-0.100000 I-7.068443 J-16.725319
G02 X53.665796 Y98.685361 Z-0.100000 I-11.078526 J-17.526859
G02 X55.775079 Y96.652958 Z-0.100000 I-15.188138 J-17.873400
G02 X57.726196 Y94.306916 Z-0.100000 I-23.584596 J-21.598798
G02 X59.470711 Y91.791387 Z-0.100000 I-30.810456 J-23.229671
G02 X61.115709 Y89.045465 Z-0.100000 I-50.136040 J-31.900659
G02 X62.630031 Y86.220124 Z-0.100000 I-67.202070 J-37.837378
G02 X64.084273 Y83.293181 Z-0.100000 I-144.036213 J-73.388621
G02 X65.494590 Y80.343272 Z-0.100000 I-291.514429 J-141.180087
G03 X66.881828 Y77.442236 Z-0.100000 I283.572929 J133.817113
G03 X68.313824 Y74.564333 Z-0.100000 I137.241882 J66.494060
G03 X69.758311 Y71.884805 Z-0.100000 I61.141728 J31.231317
G03 X71.336939 Y69.288112 Z-0.100000 I44.576782 J25.321713
G03 X72.963662 Y67.013060 Z-0.100000 I26.152579 J16.980690
G03 X74.802248 Y64.922485 Z-0.100000 I19.649002 J15.426825
G03 X76.747819 Y63.219174 Z-0.100000 I12.369484 J12.165982
G03 X78.923535 Y61.857370 Z-0.100000 I9.054792 J12.047641
G03 X81.360720 Y60.895321 Z-0.100000 I6.109585 J11.909471
G03 X83.931304 Y60.421258 Z-0.100000 I3.758224 J13.172327
G03 X87.052305 Y60.433674 Z-0.100000 I1.492311 J17.147166
G03 X90.141616 Y60.965854 Z-0.100000 I-1.957952 J20.598780
G03 X94.072512 Y62.226406 Z-0.100000 I-7.883990 J31.344689
G03 X97.849430 Y63.933050 Z-0.100000 I-15.102771 J38.456045
G03 X102.671280 Y66.665690 Z-0.100000 I-30.887708 J60.123069
G02 X107.455492 Y69.516107 Z-0.100000 I76.319751 J-122.656834
G02 X111.778145 Y71.864657 Z-0.100000 I59.849304 J-105.004055
G02 X116.195789 Y74.026840 Z-0.100000 I50.953186 J-98.510484
G02 X120.197678 Y75.765423 Z-0.100000 I39.542572 J-85.544426
G02 X124.277762 Y77.309185 Z-0.100000 I32.945222 J-80.908914
G02 X127.992363 Y78.503646 Z-0.100000 I25.153027 J-71.849106
G02 X131.764395 Y79.500062 Z-0.100000 I20.181063 J-68.759565
G02 X135.224685 Y80.214984 Z-0.100000 I14.823846 J-63.017244
G02 X138.719390 Y80.736201 Z-0.100000 I10.923189 J-61.262385
G02 X141.957129 Y81.035094 Z-0.100000 I7.031183 J-58.479198
G02 X145.206983 Y81.153633 Z-0.100000 I3.741895 J-57.979350
G02 X148.252179 Y81.099633 Z-0.100000 I0.492298 J-58.128277
G02 X151.291365 Y80.887398 Z-0.100000 I-2.594382 J-59.017970
G02 X154.172321 Y80.544259 Z-0.100000 I-6.004970 J-62.682596
G02 X157.036011 Y80.071812 Z-0.100000 I-9.343840 J-65.551964
G02 X159.780039 Y79.504629 Z-0.100000 I-14.036409 J-74.829451
G02 X162.503404 Y78.841104 Z-0.100000 I-18.436483 J-81.591141
G02 X165.137818 Y78.116401 Z-0.100000 I-27.191313 J-103.995462
G02 X167.755226 Y77.329975 Z-0.100000 I-35.376756 J-122.491044
G02 X170.308142 Y76.515232 Z-0.100000 I-63.095750 J-202.111153
G02 X172.852727 Y75.673769 Z-0.100000 I-100.985659 J-309.649100
G03 X175.353497 Y74.836779 Z-0.100000 I824.726160 J2459.974416
G03 X177.856967 Y74.008305 Z-0.100000 I145.131966 J434.360678
G03 X180.336368 Y73.216701 Z-0.100000 I52.878481 J161.343222
G03 X182.828867 Y72.469528 Z-0.100000 I38.111423 J122.605193
G03 X185.319239 Y71.790655 Z-0.100000 I23.905804 J82.788507
G03 X187.829219 Y71.193050 Z-0.100000 I18.410824 J71.756814
G03 X190.364594 Y70.694298 Z-0.100000 I12.556310 J57.135618
G03 X192.918913 Y70.313651 Z-0.100000 I9.134546 J52.536396
G03 X195.534920 Y70.063287 Z-0.100000 I5.730078 J46.080297
G03 X198.159586 Y69.965048 Z-0.100000 I2.965597 J44.121682
G03 X200.892700 Y70.033280 Z-0.100000 I0.322653 J41.849195
G03 X203.634685 Y70.214064 Z-0.100000 I-9.642083 J167.128071
G03 X206.734190 Y70.468208 Z-0.100000 I-14.808021 J199.624135
G03 X209.828711 Y70.768943 Z-0.100000 I-19.054664 J212.141427
G03 X213.185728 Y71.144851 Z-0.100000 I-23.944887 J229.015273
G03 X216.536369 Y71.569433 Z-0.100000 I-27.820491 J232.982259
G03 X220.054927 Y72.068144 Z-0.100000 I-31.235128 J233.035128
G03 X223.565116 Y72.620309 Z-0.100000 I-34.247524 J229.149691
G03 X227.149400 Y73.243018 Z-0.100000 I-35.615483 J215.627772
G03 X230.722353 Y73.926542 Z-0.100000 I-37.577673 J206.108108
G03 X234.276760 Y74.674407 Z-0.100000 I-36.862694 J184.019161
G03 X237.815350 Y75.493254 Z-0.100000 I-37.806210 J171.432212
G03 X241.244620 Y76.367245 Z-0.100000 I-35.310162 J145.710840
G03 X244.651119 Y77.325701 Z-0.100000 I-35.390941 J132.317698
G03 X247.860593 Y78.326465 Z-0.100000 I-31.591268 J106.960795
G03 X251.036164 Y79.429240 Z-0.100000 I-31.024541 J94.462390
G03 X253.932293 Y80.556999 Z-0.100000 I-26.474034 J72.268933
G03 X256.775961 Y81.809146 Z-0.100000 I-25.498067 J61.762086
G03 X259.267331 Y83.063781 Z-0.100000 I-20.772732 J44.350028
G03 X261.673924 Y84.469733 Z-0.100000 I-19.631861 J36.366877
G03 X263.673323 Y85.851744 Z-0.100000 I-15.307078 J24.282539
G03 X265.530243 Y87.411006 Z-0.100000 I-14.246357 J18.851271
G03 X266.957880 Y88.925821 Z-0.100000 I-10.886437 J11.690065
G03 X268.146784 Y90.619795 Z-0.100000 I-10.156820 J8.392689
G03 X268.928616 Y92.290945 Z-0.100000 I-8.308777 J4.905647
G03 X269.357297 Y94.072485 Z-0.100000 I-8.181325 J2.910968
G03 X269.393144 Y95.952050 Z-0.100000 I-8.386143 J1.100063
G03 X269.027656 Y97.799340 Z-0.100000 I-9.115328 J-0.843674
G03 X268.159077 Y99.914069 Z-0.100000 I-11.976068 J-3.683172
G03 X266.946743 Y101.867828 Z-0.100000 I-13.731658 J-7.167668
G03 X265.034028 Y104.181934 Z-0.100000 I-19.881289 J-14.485266
G03 X262.873021 Y106.280809 Z-0.100000 I-22.835912 J-21.349997
G03 X259.825610 Y108.760580 Z-0.100000 I-32.852748 J-37.260623
G03 X256.476576 Y110.709846 Z-0.100000 I-10.010231 J-13.346960
G03 X252.773344 Y111.883993 Z-0.100000 I-7.493702 J-17.207922
G03 X248.877241 Y112.327664 Z-0.100000 I-4.407126 J-21.372573
G03 X244.450008 Y112.121603 Z-0.100000 I-0.742684 J-31.706144
G03 X240.060530 Y111.353088 Z-0.100000 I4.553065 J-38.925229
G03 X235.163093 Y109.981508 Z-0.100000 I13.543583 J-57.788781
G03 X230.378159 Y108.229403 Z-0.100000 I22.565424 J-69.035121
G03 X225.220090 Y105.971807 Z-0.100000 I40.057628 J-98.543426
G03 X220.174534 Y103.464869 Z-0.100000 I54.183655 J-115.382898
G03 X214.928487 Y100.600598 Z-0.100000 I84.046615 J-160.171681
G03 X209.770717 Y97.577215 Z-0.100000 I106.036659 J-186.805411
G03 X204.595777 Y94.375981 Z-0.100000 I159.527010 J-263.666062
G03 X199.478162 Y91.081669 Z-0.100000 I201.070883 J-317.979798
G03 X194.529447 Y87.806053 Z-0.100000 I348.426084 J-531.768904
G03 X189.605017 Y84.491293 Z-0.100000 I534.035069 J-798.681601
G02 X185.036990 Y81.398913 Z-0.100000 I-3409.804231 J5032.001950
G02 X180.458155 Y78.318762 Z-0.100000 I-623.481769 J921.900652
G02 X176.425895 Y75.662661 Z-0.100000 I-186.252784 J278.363925
G02 X172.343973 Y73.079173 Z-0.100000 I-119.461839 J184.233742
G02 X169.003652 Y71.105394 Z-0.100000 I-52.095598 J84.350515
G02 X165.571823 Y69.298462 Z-0.100000 I-32.343612 J57.266406
G02 X163.077751 Y68.235212 Z-0.100000 I-11.428252 J23.350514
G02 X160.502116 Y67.547415 Z-0.100000 I-5.104505 J13.948698
G02 X158.955683 Y67.560212 Z-0.100000 I-0.737926 J4.270941
G02 X157.731476 Y68.200123 Z-0.100000 I0.444587 J2.341498
G02 X156.944937 Y69.588495 Z-0.100000 I2.023651 J2.063417
G02 X156.801982 Y71.348238 Z-0.100000 I5.571122 J1.338259
G02 X157.353005 Y74.828158 Z-0.100000 I21.087233 J-1.555446
G02 X158.370785 Y78.264326 Z-0.100000 I36.642233 J-8.984475
G02 X160.487376 Y83.787300 Z-0.100000 I91.589403 J-31.933101
G02 X162.889369 Y89.212416 Z-0.100000 I134.487773 J-56.300725
G02 X166.655540 Y96.974020 Z-0.100000 I267.631027 J-125.068347
G03 X170.453824 Y104.728830 Z-0.100000 I-353.386875 J177.895474
G03 X173.157092 Y110.620878 Z-0.100000 I-203.339069 J96.858011
G03 X175.655698 Y116.592772 Z-0.100000 I-151.906226 J67.065324
G03 X177.230787 Y120.906722 Z-0.100000 I-80.003800 J31.655140
G03 X178.518148 Y125.298438 Z-0.100000 I-56.752677 J19.020677
G03 X179.092855 Y128.259078 Z-0.100000 I-26.514778 J6.683036
G03 X179.249550 Y131.234152 Z-0.100000 I-17.602420 J2.418768
G03 X178.959525 Y133.105472 Z-0.100000 I-7.748214 J-0.242714
G03 X178.164821 Y134.770613 Z-0.100000 I-5.274347 J-1.495021
G03 X177.047028 Y135.873431 Z-0.100000 I-3.627994 J-2.559367
G03 X175.594363 Y136.576383 Z-0.100000 I-3.048814 J-4.447978
G03 X173.571592 Y136.990480 Z-0.100000 I-3.105214 J-10.020832
G03 X171.471477 Y137.068245 Z-0.100000 I-1.635210 J-15.763692
G03 X168.749449 Y136.884146 Z-0.100000 I1.048042 J-35.711634
G03 X166.037085 Y136.527390 Z-0.100000 I5.761018 J-54.289385
G03 X162.796828 Y135.981956 Z-0.100000 I23.329119 J-148.488603
G03 X159.565658 Y135.385276 Z-0.100000 I59.156603 J-329.394985
G02 X155.929958 Y134.711435 Z-0.100000 I-80.995633 J426.866156
G02 X152.288355 Y134.082554 Z-0.100000 I-41.132700 J227.325234
G02 X148.365069 Y133.500111 Z-0.100000 I-20.596158 J125.229171
G02 X144.427460 Y133.053734 Z-0.100000 I-13.792412 J104.075983
G02 X140.318392 Y132.775508 Z-0.100000 I-7.322865 J77.668032
G02 X136.205438 Y132.728765 Z-0.100000 I-2.839383 J68.865364
G02 X132.006156 Y132.965154 Z-0.100000 I1.029488 J55.704941
G02 X127.846119 Y133.531548 Z-0.100000 I4.767945 J50.579969
G02 X123.644591 Y134.496575 Z-0.100000 I7.714789 J43.217395
G02 X119.569924 Y135.877058 Z-0.100000 I11.283147 J40.007304
G02 X115.449927 Y137.797297 Z-0.100000 I14.557774 J36.614621
G02 X111.583891 Y140.175584 Z-0.100000 I18.672319 J34.684253
G02 X107.638393 Y143.294847 Z-0.100000 I24.065426 J34.494836
G02 X104.081169 Y146.849471 Z-0.100000 I29.795594 J33.374617
G02 X100.426220 Y151.416750 Z-0.100000 I39.923690 J35.694859
G03 X96.861751 Y156.067796 Z-0.100000 I-70.859008 J-50.613537
G03 X93.897268 Y159.392445 Z-0.100000 I-42.365964 J-34.792383
G03 X90.647615 Y162.421331 Z-0.100000 I-30.882655 J-29.875911
G03 X87.974250 Y164.392723 Z-0.100000 I-16.928804 J-20.158463
G03 X85.062213 Y165.954787 Z-0.100000 I-11.208750 J-17.400236
G03 X82.631681 Y166.757136 Z-0.100000 I-5.489126 J-12.545506
G03 X80.100449 Y167.054290 Z-0.100000 I-2.630925 J-11.481487
G03 X77.844074 Y166.825240 Z-0.100000 I-0.087721 J-10.364200
G03 X75.677709 Y166.114652 Z-0.100000 I2.251124 J-10.520571
G03 X73.585940 Y164.936589 Z-0.100000 I5.495451 J-12.203832
G03 X71.703619 Y163.417209 Z-0.100000 I8.984648 J-13.056514
G03 X69.831793 Y161.430738 Z-0.100000 I15.706928 J-16.675572
G03 X68.181100 Y159.244563 Z-0.100000 I21.305085 J-17.802887
G03 X66.556147 Y156.647241 Z-0.100000 I33.052975 J-22.485788
G03 X65.116782 Y153.938128 Z-0.100000 I41.144863 J-23.597379
G03 X63.733513 Y150.925653 Z-0.100000 I58.201905 J-28.548995
G03 X62.495117 Y147.849036 Z-0.100000 I68.612114 J-29.405211
G03 X61.338406 Y144.605528 Z-0.100000 I90.031382 J-33.935324
G03 X60.292015 Y141.323793 Z-0.100000 I101.989494 J-34.327319
G03 X59.345337 Y138.026420 Z-0.100000 I125.607517 J-37.846608
G03 X58.481449 Y134.705405 Z-0.100000 I137.732403 J-37.600881
G03 X57.728820 Y131.527885 Z-0.100000 I160.170234 J-39.615880
G03 X57.037175 Y128.335124 Z-0.100000 I170.460702 J-38.598048
G03 X56.463368 Y125.449476 Z-0.100000 I187.118800 J-38.708174
G03 X55.428496 Y119.545207 Z-0.100000 I195.277299 J-37.270122
G03 X54.883711 Y115.911257 Z-0.100000 I188.415983 J-30.104235
G03 X54.641709 Y114.113864 Z-0.100000 I168.111543 J-23.549653
G03 X54.518532 Y113.130555 Z-0.100000 I126.811802 J-16.384825
G03 X54.451092 Y112.564881 Z-0.100000 I135.732881 J-16.469000
G03 X54.402469 Y112.128198 Z-0.100000 I38.359439 J-4.492203
G00 Z4.000000
(End cutting path id: path686)
(Footer)
M5
G00 X0.0000 Y0.0000
M2
(Using default footer. To add your own footer create file "footer" in the output dir.)
(end)
%

364
test/test_arc.svg Normal file
View File

@@ -0,0 +1,364 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
sodipodi:docname="test_arc.svg"
id="svg5"
version="1.1"
viewBox="0 0 297 210"
height="210mm"
width="297mm"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="0.81512706"
inkscape:cx="222.05127"
inkscape:cy="435.51492"
inkscape:window-width="2560"
inkscape:window-height="1368"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2">
<rect
x="204.85658"
y="194.19897"
width="876.10419"
height="315.01569"
id="rect244" />
<marker
id="CheckToolsAndOPMarker"
orient="auto"
refX="-4"
refY="-1.687441"
style="overflow:visible">
<path
d="m 4.588864,-1.687441 0.0,0.0 L 9.177728,0.0 c -0.73311,-0.996261 -0.728882,-2.359329 0.0,-3.374882"
style="fill:#000044;fill-rule:evenodd;stroke:none"
id="path5372" />
</marker>
<marker
id="DrawCurveMarker"
orient="auto"
refX="-4"
refY="-1.687441"
style="overflow:visible">
<path
d="m 4.588864,-1.687441 v 0 L 9.177728,0 c -0.73311,-0.996261 -0.728882,-2.359329 0,-3.374882"
style="fill:#000044;fill-rule:evenodd;stroke:none"
id="path5375" />
</marker>
<marker
id="DrawCurveMarker_r"
orient="auto"
refX="4"
refY="-1.687441"
style="overflow:visible">
<path
d="m -4.588864,-1.687441 0.0,0.0 L -9.177728,0.0 c 0.73311,-0.996261 0.728882,-2.359329 0.0,-3.374882"
style="fill:#000044;fill-rule:evenodd;stroke:none"
id="path5378" />
</marker>
<marker
id="InOutPathMarker"
orient="auto"
refX="-4"
refY="-1.687441"
style="overflow:visible">
<path
d="m 4.588864,-1.687441 0.0,0.0 L 9.177728,0.0 c -0.73311,-0.996261 -0.728882,-2.359329 0.0,-3.374882"
style="fill:#0072a7;fill-rule:evenodd;stroke:none"
id="path5381" />
</marker>
</defs>
<g
inkscape:groupmode="layer"
id="layer1"
inkscape:label="Layer 1">
<path
style="fill:none;stroke:#000000;stroke-width:0.264583"
d="m 14.552637,116.39357 c 65.106764,-39.849838 37.604718,58.37159 88.118643,26.94074 50.51392,-31.43086 69.03563,-1.68378 98.22142,-3.36759 29.1858,-1.68381 92.60881,-13.47034 58.93291,-38.7273 C 226.14969,75.982452 125.1219,195.53198 166.65554,113.02598 208.18918,30.519984 136.90844,109.65839 100.42622,58.58325 63.944003,7.5081061 54.402469,97.871802 54.402469,97.871802"
id="path686" />
<g
gcodetools="Gcodetools orientation group"
id="g748">
<g
gcodetools="Gcodetools orientation point (2 points)"
id="g738">
<path
style="stroke:none;fill:#000000;"
gcodetools="Gcodetools orientation point arrow"
d="m 0 210 l 2.9375 -6.34375 l 0.8125 1.90625 l 6.84375 -6.84375 l 0 0 l 0.6875 0.6875 l -6.84375 6.84375 l 1.90625 0.8125 z"
id="path732" />
<text
x="10.0"
y="200.0"
style="font-family:DejaVu Sans;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:DejaVu Sans;fill:#000000;fill-opacity:1;stroke:none;font-size:10.000000px;"
gcodetools="Gcodetools orientation point text"
xml:space="preserve"
id="text736"><tspan
x="10.0"
y="200.0"
sodipodi:role="line"
id="tspan734">(0.0; 0.0; 0.00000)</tspan></text>
</g>
<g
gcodetools="Gcodetools orientation point (2 points)"
id="g746">
<path
style="stroke:none;fill:#000000;"
gcodetools="Gcodetools orientation point arrow"
d="m 100 210 l 2.9375 -6.34375 l 0.8125 1.90625 l 6.84375 -6.84375 l 0 0 l 0.6875 0.6875 l -6.84375 6.84375 l 1.90625 0.8125 z"
id="path740" />
<text
x="110.0"
y="200.0"
style="font-family:DejaVu Sans;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-family:DejaVu Sans;fill:#000000;fill-opacity:1;stroke:none;font-size:10.000000px;"
gcodetools="Gcodetools orientation point text"
xml:space="preserve"
id="text744"><tspan
x="110.0"
y="200.0"
sodipodi:role="line"
id="tspan742">(100.0; 0.0; -0.10000)</tspan></text>
</g>
</g>
<g
gcodetools="Gcodetools tool definition"
transform="translate(-398.17915,30.917996)"
id="g915">
<path
gcodetools="Gcodetools tool background"
style="fill:#00ff00;fill-opacity:0.5;stroke:#444444"
d="M -20,-20 H 380 V 155 H -20 Z"
id="path833" />
<g
gcodetools="Gcodetools tool parameter"
id="g843">
<text
x="0"
y="0"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field name"
xml:space="preserve"
id="text837"><tspan
x="0"
y="0"
sodipodi:role="line"
id="tspan835">name</tspan></text>
<text
x="150"
y="0"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:20px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field value"
xml:space="preserve"
id="text841"><tspan
x="150"
y="0"
sodipodi:role="line"
id="tspan839">Cylindrical cutter</tspan></text>
</g>
<g
gcodetools="Gcodetools tool parameter"
id="g853">
<text
x="0"
y="20"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field name"
xml:space="preserve"
id="text847"><tspan
x="0"
y="20"
sodipodi:role="line"
id="tspan845">id</tspan></text>
<text
x="150"
y="20"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field value"
xml:space="preserve"
id="text851"><tspan
x="150"
y="20"
sodipodi:role="line"
id="tspan849">Cylindrical cutter 0001</tspan></text>
</g>
<g
gcodetools="Gcodetools tool parameter"
id="g863">
<text
x="0"
y="35"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field name"
xml:space="preserve"
id="text857"><tspan
x="0"
y="35"
sodipodi:role="line"
id="tspan855">diameter</tspan></text>
<text
x="150"
y="35"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field value"
xml:space="preserve"
id="text861"><tspan
x="150"
y="35"
sodipodi:role="line"
id="tspan1039">1</tspan></text>
</g>
<g
gcodetools="Gcodetools tool parameter"
id="g873">
<text
x="0"
y="50"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field name"
xml:space="preserve"
id="text867"><tspan
x="0"
y="50"
sodipodi:role="line"
id="tspan865">feed</tspan></text>
<text
x="150"
y="50"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field value"
xml:space="preserve"
id="text871"><tspan
x="150"
y="50"
sodipodi:role="line"
id="tspan869">400</tspan></text>
</g>
<g
gcodetools="Gcodetools tool parameter"
id="g883">
<text
x="0"
y="65"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field name"
xml:space="preserve"
id="text877"><tspan
x="0"
y="65"
sodipodi:role="line"
id="tspan875">penetration angle</tspan></text>
<text
x="150"
y="65"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field value"
xml:space="preserve"
id="text881"><tspan
x="150"
y="65"
sodipodi:role="line"
id="tspan879">90</tspan></text>
</g>
<g
gcodetools="Gcodetools tool parameter"
id="g893">
<text
x="0"
y="80"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field name"
xml:space="preserve"
id="text887"><tspan
x="0"
y="80"
sodipodi:role="line"
id="tspan885">penetration feed</tspan></text>
<text
x="150"
y="80"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field value"
xml:space="preserve"
id="text891"><tspan
x="150"
y="80"
sodipodi:role="line"
id="tspan889">100</tspan></text>
</g>
<g
gcodetools="Gcodetools tool parameter"
id="g903">
<text
x="0"
y="95"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field name"
xml:space="preserve"
id="text897"><tspan
x="0"
y="95"
sodipodi:role="line"
id="tspan895">depth step</tspan></text>
<text
x="150"
y="95"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field value"
xml:space="preserve"
id="text901"><tspan
x="150"
y="95"
sodipodi:role="line"
id="tspan899">1</tspan></text>
</g>
<g
gcodetools="Gcodetools tool parameter"
id="g913">
<text
x="0"
y="110"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field name"
xml:space="preserve"
id="text907"><tspan
x="0"
y="110"
sodipodi:role="line"
id="tspan905">tool change gcode</tspan></text>
<text
x="150"
y="110"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;font-family:'DejaVu Sans';fill:#000000;fill-opacity:1;stroke:none"
gcodetools="Gcodetools tool definition field value"
xml:space="preserve"
id="text911"><tspan
x="150"
y="110"
sodipodi:role="line"
id="tspan909">(None)</tspan></text>
</g>
</g>
</g>
<g
id="g2335" />
<g
id="g3000" />
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

1561
test/test_hatch_fill.gcode Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,6 @@
import pygame
import sys
from colorama import Fore, Style
class Visualizer:
def __init__(self, positions : list[tuple[float, float, float]], screen_dimensions : tuple[int, int], gcode_size : tuple[float, float]):
@@ -30,6 +32,26 @@ class Visualizer:
self.screen = pygame.display.set_mode(self.screen_dimensions)
self.clock = pygame.time.Clock()
def _print_status(self) -> None:
normalized_x = self.prev_x / self.screen.get_width()
normalized_y = self.prev_y / self.screen.get_height()
pen_active = self.prev_z < 0
# Clear the terminal with ANSI code
print("\033c", end="")
# X-axis bar
x_bar = f"{Fore.GREEN}{'#'*int(normalized_x*100)}{Style.RESET_ALL}{' '*int((1.0-normalized_x)*100)}"
print(f"X: |{x_bar}| {(normalized_x*100):.2f}%")
# Y-axis bar
y_bar = f"{Fore.BLUE}{'#'*int(normalized_y*100)}{Style.RESET_ALL}{' '*int((1.0-normalized_y)*100)}"
print(f"Y: |{y_bar}| {(normalized_y*100):.2f}%")
# Pen status
pen_status = f"{Fore.GREEN}Active{Style.RESET_ALL}" if pen_active else f"{Fore.RED}Inactive{Style.RESET_ALL}"
print(f"Pen: {pen_status}")
def visualize(self) -> None:
running = True
while running:
@@ -61,5 +83,7 @@ class Visualizer:
x1, y1, x2, y2 = drawn_line
pygame.draw.line(self.screen, (255, 255, 255), (int(x1), int(y1)), (int(x2), int(y2)), 2)
self._print_status()
pygame.display.flip()
self.clock.tick(60)
self.clock.tick(10)