Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Prusa Slicer Compatibility #47

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions post_processors/asmbl_fff.cps
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ function getPrinterGeometry() {

function onClose() {
writeComment("layer end,")
writeComment("ASMBL_LAYER_CHANGE_FLAG")
writeComment("END OF GCODE");

// turn off the ESC
Expand Down Expand Up @@ -224,6 +225,7 @@ function onExtrusionReset(length) {

function onLayer(num) {
writeComment("layer " + integerFormat.format(num) + " of " + integerFormat.format(layerCount) + ","); // comment format to match Simplify3D
writeComment("ASMBL_LAYER_CHANGE_FLAG"); // layer change flag for ASMBL
}

// Temp controller not needed for ASMBL
Expand Down
9 changes: 4 additions & 5 deletions src/ASMBL_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def open_files(self, config):

def split_additive_layers(self, gcode_add):
""" Takes Simplify3D gcode and splits in by layer """
tmp_list = re.split('(; layer)', gcode_add)
tmp_list = re.split('(\n; ASMBL_LAYER_CHANGE_FLAG)', gcode_add)

gcode_add_layers = []
initialise_layer = AdditiveGcodeLayer(
Expand All @@ -105,14 +105,13 @@ def split_additive_layers(self, gcode_add):
for i in range(ceil(len(tmp_list)/2)):

layer = tmp_list[2*i] + tmp_list[2*i+1]
name = layer.split(',')[0][2:]

if 2*i + 1 == len(tmp_list) - 1:
gcode_add_layers.append(AdditiveGcodeLayer(
layer, 'end', inf))
continue

gcode_add_layers.append(AdditiveGcodeLayer(layer, name))
gcode_add_layers.append(AdditiveGcodeLayer(layer, f"FFF layer {i}"))

return gcode_add_layers

Expand Down Expand Up @@ -316,9 +315,9 @@ def tool_change(self, layer, prev_layer):
return # no need to add a tool change
first_gcode = layer.gcode.split('\n')[1]
if first_gcode[0] != 'T':
self.merged_gcode_script += self.last_additive_tool + '\n'
self.merged_gcode_script += f"\n{self.last_additive_tool}\n"
elif type(layer) == CamGcodeLayer:
self.merged_gcode_script += layer.tool + '\n'
self.merged_gcode_script += f"\n{layer.tool}\n"

def create_output_file(self, gcode, folder_path="output/", relative_path=True):
""" Saves the file to the output folder """
Expand Down
21 changes: 7 additions & 14 deletions src/additive_gcode.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from math import inf

from .gcode_parser import GcodeParser, Commands


class AdditiveGcodeLayer:
""" Stores a complete layer of gcode produced in Simplify3d """
Expand All @@ -11,33 +13,24 @@ def __init__(self, gcode, name=None, layer_height=None):

self.remove_park_gcode()

if name is None:
self.name = self.get_name(self.gcode)

if layer_height is None:
self.layer_height = self.get_layer_height(self.gcode)

def get_name(self, gcode):
return gcode.split(',')[0][2:]

def get_layer_height(self, gcode):
height = None
lines = gcode.split('\n')
lines = GcodeParser(gcode, include_comments=True).lines

# Check for Simplify3D end of file code
if lines[0] == '; layer end':
if lines[0].to_gcode == '; layer end':
height = inf

else:
line_heights = []
for line in lines:
if line == '':
continue
if line[0] == ';':
if line.type == Commands.COMMENT:
continue
line_segments = line.split('Z')
if len(line_segments) > 1:
line_height = float(line_segments[1].split(' ')[0])
line_height = line.get_param('Z')
if line_height is not None:
line_heights.append(line_height)

height = min(line_heights)
Expand Down
6 changes: 6 additions & 0 deletions src/gcode_parser/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Original code from https://github.com/AndyEveritt/GcodeParser

__version__ = "0.0.6"

from .gcode_parser import GcodeParser, get_lines
from .commands import Commands
8 changes: 8 additions & 0 deletions src/gcode_parser/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from enum import Enum


class Commands(Enum):
COMMENT = 0
MOVE = 1
OTHER = 2
TOOLCHANGE = 3
122 changes: 122 additions & 0 deletions src/gcode_parser/gcode_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from typing import List, Dict, Tuple, Union
from dataclasses import dataclass
import re
from .commands import Commands


@dataclass
class GcodeLine:
command: Tuple[str, int]
params: Dict[str, float]
comment: str

def __post_init__(self):
if self.command[0] == 'G' and self.command[1] in (0, 1, 2, 3):
self.type = Commands.MOVE
elif self.command[0] == ';':
self.type = Commands.COMMENT
elif self.command[0] == 'T':
self.type = Commands.TOOLCHANGE
else:
self.type = Commands.OTHER

@property
def command_str(self):
return f"{self.command[0]}{self.command[1] if self.command[1] is not None else ''}"

def get_param(self, param: str, return_type=None, default=None):
"""
Returns the value of the param if it exists, otherwise it will the default value.
If `return_type` is set, the return value will be type cast.
"""
try:
if return_type is None:
return self.params[param]
else:
return return_type(self.params[param])
except KeyError:
return default

def update_param(self, param: str, value: Union[int, float]):
if self.get_param(param) is None:
return
if type(value) not in (int, float):
raise TypeError(f"Type {type(value)} is not a valid parameter type")
self.params[param] = value
return self.get_param(param)

def delete_param(self, param: str):
if self.get_param(param) is None:
return
self.params.pop(param)

@property
def to_gcode(self):
command = self.command_str
params = " ".join(f"{param}{self.get_param(param)}" for param in self.params.keys())
comment = f"; {self.comment}" if self.comment != '' else ""
if command == ';':
return comment
return f"{command} {params} {comment}".strip()


class GcodeParser:
def __init__(self, gcode: str, include_comments=False):
self.gcode = gcode
self.lines: List[GcodeLine] = get_lines(self.gcode, include_comments)
self.include_comments = include_comments


def get_lines(gcode, include_comments=False):
regex = r'(?!; *.+)(G|M|T|g|m|t)(\d+)(([ \t]*(?!G|M|g|m)\w(".*"|([-\d\.]*)))*)[ \t]*(;[ \t]*(.*))?|;[ \t]*(.+)'
regex_lines = re.findall(regex, gcode)
lines = []
for line in regex_lines:
if line[0]:
command = (line[0].upper(), int(line[1]))
comment = line[-2]
params = split_params(line[2])

elif include_comments:
command = (';', None)
comment = line[-1]
params = {}

else:
continue

lines.append(
GcodeLine(
command=command,
params=params,
comment=comment.strip(),
))

return lines


def element_type(element: str):
if re.search(r'"', element):
return str
if re.search(r'\..*\.', element):
return str
if re.search(r'[+-]?\d+\.\d+', element):
return float
return int


def split_params(line):
regex = r'((?!\d)\w+?)(".*"|(\d+\.?)+|-?\d+\.?\d*)'
elements = re.findall(regex, line)
params = {}
for element in elements:
params[element[0].upper()] = element_type(element[1])(element[1])

return params


if __name__ == '__main__':
with open('3DBenchy.gcode', 'r') as f:
gcode = f.read()
parsed_gcode = GcodeParser(gcode)
pass
83 changes: 40 additions & 43 deletions src/utils.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,78 @@
import sys
import re
import os
import subprocess
import time

from .gcode_parser import GcodeParser, Commands


def convert_relative(gcode_abs):
""" Converts absolute extrusion gcode into relative extrusion gcode """
absolute_mode = False
last_tool = None
last_e = {} # {'tool': last extrusion value}

lines = gcode_abs.split('\n')
lines = GcodeParser(gcode_abs, include_comments=True).lines

gcode_rel = ''

for line in lines:
if line == '':
if line.type == Commands.COMMENT:
gcode_rel += line.to_gcode + '\n'
continue

line_start = line.split(' ')[0]
# Check for absolute extrusion
if line_start == ('M82'):
if line.command_str == 'M82':
absolute_mode = True
line = 'M83'
line.command = ('M', 83)

# Check for relative extrusion
elif line_start == ('M83'):
elif line.command_str == 'M83':
absolute_mode = False

# Check for tool change
elif line_start[0] == 'T':
last_tool = line_start
elif line.command[0] == 'T':
last_tool = line.command_str

# Check for extrusion reset
elif line_start == 'G92':
extrusion_reset = line.split('E')[1]
elif line.command_str == 'G92':
extrusion_reset = line.get_param('E')
last_e[last_tool] = extrusion_reset

# Convert Extrusion coordinates to relative if in absolute mode
if absolute_mode:
if line_start == 'G0' or line_start == 'G1':
try:
line_split = line.split('E')
current_extrusion = line_split[1]
extrusion_diff = float(current_extrusion) - float(last_e[last_tool])
extrusion_diff = round(extrusion_diff, 3)
last_e[last_tool] = current_extrusion
line = line_split[0] + 'E' + str(extrusion_diff)
except IndexError:
pass
gcode_rel += line + '\n'
if absolute_mode and line.type == Commands.MOVE:
current_extrusion = line.get_param('E')
if current_extrusion is not None:
extrusion_diff = float(current_extrusion) - float(last_e[last_tool])
extrusion_diff = round(extrusion_diff, 5)
last_e[last_tool] = current_extrusion
line.update_param('E', extrusion_diff)

gcode_rel += line.to_gcode + '\n'

return gcode_rel


def offset_gcode(gcode, offset):
gcode_segments = gcode.split(' ')
offset_gcode = ''
for gcode_segment in gcode_segments:
if gcode_segment[0] == 'X':
x_pos = float(gcode_segment[1:])
x_pos += offset[0]
gcode_segment = gcode_segment[0] + str(x_pos)

elif gcode_segment[0] == 'Y':
y_pos = float(gcode_segment[1:])
y_pos += offset[1]
gcode_segment = gcode_segment[0] + str(y_pos)

elif gcode_segment[0] == 'Z':
z_pos = float(gcode_segment[1:])
z_pos += offset[2]
gcode_segment = gcode_segment[0] + str(z_pos)

offset_gcode += ' ' + gcode_segment

return offset_gcode[1:]
parsed_gcode = GcodeParser(gcode).lines[0]

if parsed_gcode.get_param('X') is not None:
x_pos = parsed_gcode.get_param('X')
x_pos += offset[0]
parsed_gcode.update_param('X', x_pos)

if parsed_gcode.get_param('Y') is not None:
y_pos = parsed_gcode.get_param('Y')
y_pos += offset[1]
parsed_gcode.update_param('Y', y_pos)

if parsed_gcode.get_param('Z') is not None:
z_pos = parsed_gcode.get_param('Z')
z_pos += offset[2]
parsed_gcode.update_param('Z', z_pos)

return parsed_gcode.to_gcode


def find_maxima(numbers):
Expand Down