diff --git a/python/.gitignore b/python/.gitignore new file mode 100644 index 0000000..f43d809 --- /dev/null +++ b/python/.gitignore @@ -0,0 +1,162 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +*.gcode \ No newline at end of file diff --git a/python/README.md b/python/README.md new file mode 100644 index 0000000..6312519 --- /dev/null +++ b/python/README.md @@ -0,0 +1,11 @@ +# Python Implementation of the Extrusion System Benchmark +by Julian Schill + +## Usage: +1. Install Python (if not already installed) +2. Edit settings.py and set your parameters (same parameters as the Excel implementation) +3. Run esb_cli.py: `python esb_cli.py` +4. Print the gcode on your printer and do the measurements +5. Use the Excel table to do the analysis + +The gcode file will be written to the filename specified in settings.py \ No newline at end of file diff --git a/python/esb_cli.py b/python/esb_cli.py new file mode 100644 index 0000000..9b0fce7 --- /dev/null +++ b/python/esb_cli.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Extrusion System Benchmark + +# Copyright (c) Julian Schill +# All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. + +from extrusionsystembenchmark import esb +from settings import esb_settings + +def main(): + esb_generator = esb.generator() + gcode = esb_generator.generate_gcode(esb_settings) + + f = open(esb_settings.outputFilename, "w") + f.write(gcode) + f.close + +if __name__ == "__main__": + main() diff --git a/python/extrusionsystembenchmark/__init__.py b/python/extrusionsystembenchmark/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/python/extrusionsystembenchmark/esb.py b/python/extrusionsystembenchmark/esb.py new file mode 100644 index 0000000..d605c86 --- /dev/null +++ b/python/extrusionsystembenchmark/esb.py @@ -0,0 +1,123 @@ +# Extrusion System Benchmark GCode Generator + +# Copyright (c) Julian Schill +# All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. + +import math +from . import gcode + +class generator: + def __init__(self): + self.settings = None + self.gcode = gcode.gcode_helper() + + def print_header(self): + self.gcode.comment("*** CNC Kitchen Auto Flow Pattern Generator 0.93") + self.gcode.comment("*** 02/04/22 Stefan Hermann") + self.gcode.comment("*** Python Version: 04/04/22 by Julian Schill") + self.gcode.comment("") + + def print_settings(self): + #Generation Settings + self.gcode.comment("####### Settings") + self.gcode.comment(" {}".format(self.settings.comment)) + self.gcode.comment(" bedWidth = {}".format(self.settings.bedWidth)) + self.gcode.comment(" bedLength = {}".format(self.settings.bedLength)) + self.gcode.comment(" bedMargin = {}".format(abs(self.settings.bedMargin))) + self.gcode.comment(" filamentDiameter = {}".format(self.settings.filamentDiameter)) + self.gcode.comment(" movementSpeed = {}".format(self.settings.movementSpeed)) + self.gcode.comment(" stabilizationTime = {}".format(self.settings.stabilizationTime)) + self.gcode.comment(" bedTemp = {}".format(self.settings.bedTemp)) + self.gcode.comment(" primeLength = {}".format(self.settings.primeLength)) + self.gcode.comment(" primeAmount = {}".format(self.settings.primeAmount)) + self.gcode.comment(" primeSpeed = {}".format(self.settings.primeSpeed)) + self.gcode.comment(" retractionDistance = {}".format(self.settings.retractionDistance)) + self.gcode.comment(" retractionSpeed = {}".format(self.settings.retractionSpeed)) + self.gcode.comment(" blobHeight = {}".format(self.settings.blobHeight)) + self.gcode.comment(" extrusionAmount = {}".format(self.settings.extrusionAmount)) + self.gcode.comment(" xSpacing = {}".format(self.settings.xSpacing)) + self.gcode.comment(" ySpacing = {}".format(self.settings.ySpacing)) + self.gcode.comment(" startFlow = {}".format(self.settings.startFlow)) + self.gcode.comment(" flowOffset = {}".format(self.settings.flowOffset)) + self.gcode.comment(" flowSteps = {}".format(self.settings.flowSteps)) + self.gcode.comment(" startTemp = {}".format(self.settings.startTemp)) + self.gcode.comment(" tempOffset = {}".format(self.settings.tempOffset)) + self.gcode.comment(" tempSteps = {}".format(self.settings.tempSteps)) + self.gcode.comment(" direction = {}".format(self.settings.direction)) + self.gcode.comment("") + + def do_test(self, x, y, flow, temp): + self.gcode.flow_and_temp_msg(flow, temp) + # move in position + self.gcode.move_xyz(x, y, (0.5 + self.settings.blobHeight + 5), self.settings.movementSpeed) + self.gcode.stabilize(self.settings.stabilizationTime) + # prime + self.gcode.move_z(0.3) + self.gcode.prime(x, self.settings.primeLength, self.settings.primeAmount, self.settings.primeSpeed, self.settings.retractionDistance, self.settings.retractionSpeed, self.settings.wipeLength, self.settings.movementSpeed) + # make a blob + lift_speed=self.gcode.feedrate_from_flow(flow, self.settings.extrusionAmount, self.settings.blobHeight, self.settings.filamentDiameter) + self.gcode.deretract(self.settings.retractionDistance, self.settings.retractionSpeed) + self.gcode.make_blob(self.settings.blobHeight, self.settings.extrusionAmount, lift_speed) + self.gcode.retract(self.settings.retractionDistance, self.settings.retractionSpeed ) + self.gcode.move_z(0.5 + self.settings.blobHeight + 5) + # move back to wipe + self.gcode.move_xy(x, y, self.settings.movementSpeed) + self.gcode.reset_extruder() + + def generate_gcode(self, settings): + self.settings = settings + start_flow = self.settings.startFlow + flow_steps = self.settings.flowSteps + + temp_steps = self.settings.tempSteps + y_spacing = self.settings.ySpacing + + bed_length = self.settings.bedLength + bed_margin = self.settings.bedMargin + test_width = self.settings.primeLength + self.settings.wipeLength + self.settings.xSpacing + temp_offset = self.settings.tempOffset + end_flow = self.settings.startFlow+(flow_steps-1)*self.settings.flowOffset + + #Check if "Fill Mode" is used + if self.settings.tempSteps == 1: + flow_steps = math.floor((bed_length - 2 * bed_margin) / y_spacing) + temp_steps = math.ceil(self.settings.flowSteps / flow_steps) + temp_offset = 0 + + #change variables depending on direction + if self.settings.direction == 1: + bed_length = 0 + bed_margin = -bed_margin + y_spacing = -y_spacing + + self.print_header() + self.print_settings() + self.gcode.start_print(self.settings.startTemp, self.settings.bedTemp, self.settings.fanSpeed) + + for column in range(temp_steps): + x = abs(bed_margin) + (column * test_width) + # Check if "Fill Mode" is active + if temp_offset == 0 and column > 0: + start_flow = start_flow + flow_steps * self.settings.flowOffset + + temp = self.settings.startTemp + column * temp_offset + self.gcode.set_extruder_temp(temp) + + for row in range(flow_steps): + # Check if "Fill Mode" is active + if temp_offset == 0: + if column == temp_steps: + if (start_flow + (row - 1) * self.settings.flowOffset) == end_flow: + break + + y = (bed_length - bed_margin) - row * y_spacing + flow = start_flow + row * self.settings.flowOffset + self.do_test(x,y,flow, temp) + + corner_x = self.settings.bedWidth - abs(bed_margin) + corner_y = self.settings.bedLength - abs(bed_margin) + + self.gcode.end_print( corner_x, corner_y, self.settings.movementSpeed) + + return self.gcode.get_gcode() diff --git a/python/extrusionsystembenchmark/gcode.py b/python/extrusionsystembenchmark/gcode.py new file mode 100644 index 0000000..e23ed27 --- /dev/null +++ b/python/extrusionsystembenchmark/gcode.py @@ -0,0 +1,87 @@ +# GCode Helper + +# Copyright (c) Julian Schill +# All rights reserved. +# Licensed under the MIT license. See LICENSE file in the project root for details. + +import math + +class gcode_helper: + def __init__(self): + self.gcode = "" + + def gcode_line(self, line): + self.gcode += line + '\n' + + def get_gcode(self): + return self.gcode + + def start_print(self, nozzle_temp, bed_temp, fan_speed): + self.gcode_line("M104 S{} ; Set Nozzle Temperature".format(nozzle_temp)) + self.gcode_line("M140 S{} ; Set Bed Temperature".format(bed_temp)) + self.gcode_line("G90") + self.gcode_line("G28 ; Move to home position") + self.move_z(10) + self.gcode_line("G21; unit in mm") + self.reset_extruder() + self.gcode_line("M83; set extruder to relative mode") + self.gcode_line("M190 S{} ; Set Bed Temperature & Wait".format(bed_temp)) + self.gcode_line("M106 S{} ; Set Fan Speed".format(int(fan_speed * 255 / 100))) + + def end_print(self, park_position_x, park_position_y, speed): + self.comment("") + self.comment("####### End G-Code") + self.move_xy(park_position_x,park_position_y,speed) + self.gcode_line("M104 S0 T0 ; Turn Off Hotend") + self.gcode_line("M140 S0 ; Turn Off Bed") + self.gcode_line("M84") + + def move_xy(self, x, y, speed): + self.gcode_line("G0 X{} Y{} F{}".format(x, y , (speed * 60))) + + def move_xyz(self, x, y, z, speed): + self.gcode_line("G0 X{} Y{} Z{} F{}".format(x,y,z, (speed * 60))) + + def comment(self, comment): + self.gcode_line(";"+comment) + + def set_extruder_temp(self, temp): + self.gcode_line("") + self.comment("####### {}C".format(temp)) + self.gcode_line("G4 S0 ; Dwell") + self.gcode_line("M109 S{} R{}".format(temp, temp)) + + def retract(self, distance, speed): + self.gcode_line("G1 E{} F{} ; Retract".format((-1 * distance),(speed * 60))) + + def deretract(self, distance, speed): + self.gcode_line("G1 E{} F{} ; De-Retract".format(distance, speed * 60)) + + def move_z(self, z): + self.gcode_line("G0 Z{}".format(z)) + + def stabilize(self, time): + self.gcode_line("G4 S{}; Stabilize".format(time)) + + def prime(self, start, length, amount, prime_speed, retract_distance, retract_speed, wipe_length, wipe_speed): + self.gcode_line("G1 X{} E{} F{} ; Prime".format(start + length, amount, prime_speed * 60)) + self.retract(retract_distance, retract_speed) + self.gcode_line("G0 X{} F{} ; Wipe".format(start + length + wipe_length, wipe_speed * 60)) + self.move_z(0.5) + + def make_blob(self, height, extrusion_length, feedrate): + self.gcode_line("G1 Z{} E{} F{} ; Extrude".format((0.5 + height), extrusion_length, feedrate)) + + def reset_extruder(self): + self.gcode_line( "G92 E0 ; Reset Extruder") + + def flow_and_temp_msg(self, flow, temp): + self.gcode_line( "") + self.comment("####### {}mm3/s".format(flow)) + self.gcode_line( "M117 {}C // {}mm3/s".format(temp, flow)) + + def feedrate_from_flow(self, flow_rate, amount, movement_length, filament_diameter): + filament_cross_section = math.pi/4.0 * filament_diameter**2 + extruder_feedrate = flow_rate / filament_cross_section + extrude_time = amount / extruder_feedrate + return round((movement_length / extrude_time) * 60, 2) \ No newline at end of file diff --git a/python/settings.py b/python/settings.py new file mode 100644 index 0000000..34d321b --- /dev/null +++ b/python/settings.py @@ -0,0 +1,47 @@ +class esb_settings: +# Put your settings here: +# IMPORTANT: Don't change the indentation! + +# General Settings + bedWidth = 220 + bedLength = 220 + + bedMargin = 5 + filamentDiameter = 1.75 + movementSpeed = 100 + stabilizationTime = 20 + bedTemp = 40 + fanSpeed = 0 + + primeLength = 25 + primeAmount = 20 + primeSpeed = 5 + wipeLength = 15 + retractionDistance = 4 + retractionSpeed = 60 + + blobHeight = 10 + extrusionAmount = 200 + + direction = -1 #1=front to back, -1=back to front + xSpacing = 40 + ySpacing = 25 + +# Flow Variation + startFlow = 2 + flowOffset = 2 + flowSteps = 8 + + +# Temperature Variation + startTemp = 240 + tempOffset = -20 + tempSteps = 3 #If Steps = 1 "Fill Mode" is used + + comment = "Ender3" + + endFlow = startFlow+(flowSteps-1)*flowOffset # Don't change + endTemp = startTemp+(tempSteps-1)*tempOffset # Don't change + + #outputFilename = "esb.gcode" + outputFilename = "{}_{}-{}mm3_{}-{}C.gcode".format(comment,startFlow,endFlow,startTemp,endTemp)