Compare commits
3 Commits
fa1122b32c
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ade1aba1b1 | ||
| 6447a11768 | |||
| a1d73a26da |
@@ -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
210
gcodeinterpreter.py
Normal 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]]
|
||||||
6
main.py
6
main.py
@@ -1,6 +1,6 @@
|
|||||||
#! /usr/bin/env python
|
#! /usr/bin/env python
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from gcode_parser import GCodeParser
|
from gcodeinterpreter import GcodeInterpreter
|
||||||
from pwm_driver import PWMDriver
|
from pwm_driver import PWMDriver
|
||||||
from visualizer import Visualizer
|
from visualizer import Visualizer
|
||||||
|
|
||||||
@@ -18,8 +18,8 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
args = argparser.parse_args()
|
args = argparser.parse_args()
|
||||||
|
|
||||||
parser = GCodeParser(args.file)
|
parser = GcodeInterpreter().parse_file(args.file)
|
||||||
positions = parser.get_positions()
|
positions = parser.get_coordinates()
|
||||||
|
|
||||||
if args.visualize:
|
if args.visualize:
|
||||||
screen_dimensions = (1024*1.5, 1024*1.5 * args.height/args.width)
|
screen_dimensions = (1024*1.5, 1024*1.5 * args.height/args.width)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
pygame==2.6.1
|
pygame==2.6.1
|
||||||
pygcode==0.2.1
|
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
pyserial
|
pyserial
|
||||||
26
set_pos.py
Executable file
26
set_pos.py
Executable 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()
|
||||||
1561
test/test_hatch_fill.gcode
Normal file
1561
test/test_hatch_fill.gcode
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user