From 2a48b00bb5f1d95e0cd3928aba986adedcb7af47 Mon Sep 17 00:00:00 2001 From: tatarize Date: Mon, 29 Oct 2018 07:58:25 -0700 Subject: [PATCH 1/2] +Gcode, reader and writer. --- pyembroidery/CsvWriter.py | 197 +++++++++++++++++++++-------------- pyembroidery/GcodeReader.py | 79 ++++++++++++++ pyembroidery/GcodeWriter.py | 93 +++++++++++++++++ pyembroidery/PhcReader.py | 12 +-- pyembroidery/PmvWriter.py | 2 +- pyembroidery/PyEmbroidery.py | 40 ++++++- pyembroidery/Vp3Writer.py | 4 - test/test_convert_gcode.py | 139 ++++++++++++++++++++++++ test/test_writes.py | 11 ++ 9 files changed, 484 insertions(+), 93 deletions(-) create mode 100644 pyembroidery/GcodeReader.py create mode 100644 pyembroidery/GcodeWriter.py create mode 100644 test/test_convert_gcode.py diff --git a/pyembroidery/CsvWriter.py b/pyembroidery/CsvWriter.py index ec05c7e..af41fa8 100644 --- a/pyembroidery/CsvWriter.py +++ b/pyembroidery/CsvWriter.py @@ -32,12 +32,8 @@ def angle(dx, dy): return angle -def write(pattern, f, settings=None): +def write_data(pattern, f): names = get_common_name_dictionary() - - deltas = settings is not None and "deltas" in settings - displacement = settings is not None and "displacement" in settings - extends = pattern.bounds() width = extends[2] - extends[0] height = extends[3] - extends[1] @@ -79,6 +75,8 @@ def write(pattern, f, settings=None): write_string_utf8(f, "\n") + +def write_metadata(pattern, f): if len(pattern.extras) > 0: csv(f, ( '#', @@ -95,6 +93,8 @@ def write(pattern, f, settings=None): )) write_string_utf8(f, "\n") + +def write_threads(pattern, f): if len(pattern.threadlist) > 0: csv(f, ( '#', @@ -119,83 +119,118 @@ def write(pattern, f, settings=None): )) write_string_utf8(f, "\n") + +def decoded_name(names, data): + command = decode_embroidery_command(data) + try: + name = names[command[0]] + if command[1] is not None: + name = name + " t" + str(command[1]) + if command[2] is not None: + name = name + " n" + str(command[2]) + if command[3] is not None: + name = name + " o" + str(command[3]) + except (IndexError, KeyError): + name = "UNKNOWN " + str(data) + return name + + +def write_stitches_displacement(pattern, f): + names = get_common_name_dictionary() + csv(f, ( + '#', + '[STITCH_INDEX]', + '[STITCH_TYPE]', + '[X]', + '[Y]', + '[DX]', + '[DY]', + '[R]', + '[ANGLE]' + )) + + current_x = 0 + current_y = 0 + for i, stitch in enumerate(pattern.stitches): + name = decoded_name(names, stitch[2]) + dx = stitch[0] - current_x + dy = stitch[1] - current_y + csv(f, ( + '*', + str(i), + name, + str(stitch[0]), + str(stitch[1]), + str(dx), + str(dy), + str(distance(dx, dy)), + str(angle(dx, dy)) + )) + current_x = stitch[0] + current_y = stitch[1] + + +def write_stitches_deltas(pattern, f): + names = get_common_name_dictionary() + csv(f, ( + '#', + '[STITCH_INDEX]', + '[STITCH_TYPE]', + '[X]', + '[Y]', + '[DX]', + '[DY]' + )) + current_x = 0 + current_y = 0 + for i, stitch in enumerate(pattern.stitches): + name = decoded_name(names, stitch[2]) + dx = stitch[0] - current_x + dy = stitch[1] - current_y + csv(f, ( + '*', + str(i), + name, + str(stitch[0]), + str(stitch[1]), + str(dx), + str(dy) + )) + current_x = stitch[0] + current_y = stitch[1] + + +def write_stitches(pattern, f): + names = get_common_name_dictionary() + csv(f, ( + '#', + '[STITCH_INDEX]', + '[STITCH_TYPE]', + '[X]', + '[Y]' + )) + for i, stitch in enumerate(pattern.stitches): + name = decoded_name(names, stitch[2]) + csv(f, ( + '*', + str(i), + name, + str(stitch[0]), + str(stitch[1]), + )) + + +def write(pattern, f, settings=None): + deltas = settings is not None and "deltas" in settings + displacement = settings is not None and "displacement" in settings + write_data(pattern, f) + write_metadata(pattern, f) + write_threads(pattern, f) + if len(pattern.stitches) > 0: if displacement: - csv(f, ( - '#', - '[STITCH_INDEX]', - '[STITCH_TYPE]', - '[X]', - '[Y]', - '[DX]', - '[R]', - '[ANGLE]' - )) + write_stitches_displacement(pattern, f) elif deltas: - csv(f, ( - '#', - '[STITCH_INDEX]', - '[STITCH_TYPE]', - '[X]', - '[Y]', - '[DX]', - '[DY]' - )) + write_stitches_deltas(pattern, f) else: - csv(f, ( - '#', - '[STITCH_INDEX]', - '[STITCH_TYPE]', - '[X]', - '[Y]' - )) - current_x = 0 - current_y = 0 - for i, stitch in enumerate(pattern.stitches): - command = decode_embroidery_command(stitch[2]) - try: - name = names[command[0]] - if command[1] is not None: - name = name + " t" + str(command[1]) - if command[2] is not None: - name = name + " n" + str(command[2]) - if command[3] is not None: - name = name + " o" + str(command[3]) - except (IndexError, KeyError): - name = "UNKNOWN " + str(stitch[2]) - if displacement: - dx = stitch[0] - current_x - dy = stitch[1] - current_y - csv(f, ( - '*', - str(i), - name, - str(stitch[0]), - str(stitch[1]), - str(dx), - str(dy), - str(distance(dx, dy)), - str(angle(dx, dy)) - )) - elif deltas: - dx = stitch[0] - current_x - dy = stitch[1] - current_y - csv(f, ( - '*', - str(i), - name, - str(stitch[0]), - str(stitch[1]), - str(dx), - str(dy) - )) - else: - csv(f, ( - '*', - str(i), - name, - str(stitch[0]), - str(stitch[1]), - )) - current_x = stitch[0] - current_y = stitch[1] + write_stitches(pattern, f) diff --git a/pyembroidery/GcodeReader.py b/pyembroidery/GcodeReader.py new file mode 100644 index 0000000..3a43286 --- /dev/null +++ b/pyembroidery/GcodeReader.py @@ -0,0 +1,79 @@ +def parse(f): + comment = None + code = "" + value = "" + command_map = {} + ord_a = ord('a') + ord_A = ord('A') + ord_z = ord('z') + ord_Z = ord('Z') + while True: + byte = f.read(1) + if byte is None: + break + if len(byte) == 0: + break + if comment is not None: + if byte == b')': + command_map['comment'] = comment + comment = None + else: + comment += str(byte) + continue + if byte == b'(': + comment = "" + continue + elif byte == b'\t': + continue + elif byte == b' ': + continue + elif byte == b'/' and len(code) == 0: + continue + b = ord(byte) + if ord('0') <= b <= ord('9') \ + or byte == b'+' \ + or byte == b'-' \ + or byte == b'.': + value += str(byte) + continue + + if ord_A <= b <= ord_Z: + b = b - ord_A + ord_a + byte = chr(b) + is_letter = ord_a <= b <= ord_z + is_end = byte == b'\n' or byte == b'\r' + if (is_letter or is_end) and len(code) != 0: + command_map[code] = float(value) + code = "" + value = "" + if is_letter: + code += str(byte) + continue + elif is_end: + if len(command_map) == 0: + continue + yield command_map + command_map = {} + code = "" + value = "" + continue + + +def read(f, out, settings=None): + scale = -10.0 + for gc in parse(f): + if 'comment' in gc: + comment = gc['comment'] + if 'Thread' in comment: + split = comment.split(" ") + out.add_thread(split[1]) + + if 'g' in gc and 'x' in gc and 'y' in gc: + if gc['g'] == 0.0 or gc['g'] == 1.0: + out.stitch_abs(gc['x'] * scale, gc['y'] * scale) + if 'm' in gc: + v = gc['m'] + if v == 30 or v == 2: + out.end() + elif v == 0 or v == 1: + out.color_change() diff --git a/pyembroidery/GcodeWriter.py b/pyembroidery/GcodeWriter.py new file mode 100644 index 0000000..a6cf88c --- /dev/null +++ b/pyembroidery/GcodeWriter.py @@ -0,0 +1,93 @@ +from .EmbFunctions import * +from .WriteHelper import write_string_utf8 + +SEQUIN_CONTINGENCY = CONTINGENCY_SEQUIN_STITCH +SCALE = (-1, -1) # This performs a default X,Y flip. + + +def write_data(pattern, f): + bounds = [float(e) / 10.0 for e in pattern.bounds()] # convert to mm. + width = bounds[2] - bounds[0] + height = bounds[3] - bounds[1] + count_stitches = pattern.count_stitches() + count_threads = pattern.count_color_changes() + + write_string_utf8(f, '(STITCH_COUNT: %d)\n' % count_stitches) + write_string_utf8(f, '(THREAD_COUNT: %d)\n' % count_threads) + write_string_utf8(f, '(EXTENTS_LEFT: %.3f)\n' % bounds[0]) + write_string_utf8(f, '(EXTENTS_TOP: %.3f)\n' % bounds[1]) + write_string_utf8(f, '(EXTENTS_RIGHT: %.3f)\n' % bounds[2]) + write_string_utf8(f, '(EXTENTS_BOTTOM: %.3f)\n' % bounds[3]) + write_string_utf8(f, '(EXTENTS_WIDTH: %.3f)\n' % width) + write_string_utf8(f, '(EXTENTS_HEIGHT: %.3f)\n' % height) + + stitch_counts = {} + for s in pattern.stitches: + command = s[2] & COMMAND_MASK + if command in stitch_counts: + stitch_counts[command] += 1 + else: + stitch_counts[command] = 1 + + names = get_common_name_dictionary() + if len(stitch_counts) != 0: + for the_key, the_value in stitch_counts.items(): + try: + the_key &= COMMAND_MASK + name = "COMMAND_" + names[the_key] + except (IndexError, KeyError): + name = "COMMAND_UNKNOWN_" + str(the_key) + write_string_utf8(f, '(%s: %d)\n' % (name, the_value)) + + +def write_metadata(pattern, f): + if len(pattern.extras) > 0: + for the_key, the_value in pattern.extras.items(): + if isinstance(the_value, str): + write_string_utf8(f, '(%s: %s)\n' % (the_key, the_value)) + + +def write_threads(pattern, f): + if len(pattern.threadlist) > 0: + for i, thread in enumerate(pattern.threadlist): + write_string_utf8(f, '(Thread%d: %s %s %s %s)\n' % ( + i, + thread.hex_color(), + thread.description, + thread.brand, + thread.catalog_number, + )) + + +def write(pattern, f, settings=None): + if settings is None: + settings = {} + increment_value = settings.get('stitch_z_travel', 10.0) + write_data(pattern, f) + write_metadata(pattern, f) + write_threads(pattern, f) + + z = 0.0 + for i, stitch in enumerate(pattern.stitches): + x = float(stitch[0]) / 10.0 + y = float(stitch[1]) / 10.0 + cmd = decode_embroidery_command(stitch[2]) + command = cmd[0] + if command == STITCH: + write_string_utf8(f, 'G00 X%.3f Y%.3f\nG00 Z%.1f\n' % (x, y, z)) + z += increment_value + continue + elif command == JUMP: + continue + elif command == TRIM: + continue + elif command == COLOR_CHANGE: + write_string_utf8(f, 'M00\n') + continue + elif command == STOP: + write_string_utf8(f, 'M00\n') + continue + elif command == END: + write_string_utf8(f, 'M30\n') + continue + break # Code shouldn't reach this point. diff --git a/pyembroidery/PhcReader.py b/pyembroidery/PhcReader.py index cd17dde..48acf5a 100644 --- a/pyembroidery/PhcReader.py +++ b/pyembroidery/PhcReader.py @@ -1,5 +1,5 @@ -from .PecReader import read_pec_stitches, read_pec_graphics from .EmbThreadPec import get_thread_set +from .PecReader import read_pec_stitches, read_pec_graphics from .ReadHelper import read_int_8, read_int_32le, read_int_16le @@ -24,14 +24,14 @@ def read(f, out, settings=None): out.threadlist ) f.seek(0x2B, 0) - pec_add = read_int_8(f) - f.seek(4, 1) + pec_add = read_int_8(f) # Size of pre-graphics, post copyright header. + f.seek(4, 1) # 0x30, graphics end size. pec_offset = read_int_16le(f) f.seek(pec_offset + pec_add, 0) - bytes_in_section = read_int_16le(f) + bytes_in_section = read_int_16le(f) # Primary bounds. f.seek(bytes_in_section, 1) - bytes_in_section2 = read_int_32le(f) + bytes_in_section2 = read_int_32le(f) # Sectional bounds. f.seek(bytes_in_section2 + 10, 1) color_count2 = read_int_8(f) - f.seek(color_count2 + 0x1D, 1) + f.seek(color_count2 + 0x1D, 1) #1D toto back read_pec_stitches(f, out) diff --git a/pyembroidery/PmvWriter.py b/pyembroidery/PmvWriter.py index bfd6f04..c159ea8 100644 --- a/pyembroidery/PmvWriter.py +++ b/pyembroidery/PmvWriter.py @@ -29,7 +29,7 @@ def write(pattern, f, settings=None): break center_y = (min_y + max_y) / 2.0 normal_max_y = max_y - center_y - if normal_max_y > 35.0: # 14 * 2.5 = 350 + if normal_max_y > 35.0: # 14 * 2.5 = 35.0 scale_y = 14.0 / normal_max_y # 1 / (normal_max_y / 14.0) else: scale_y = 1.0 / 2.5 # pure unit conversion. diff --git a/pyembroidery/PyEmbroidery.py b/pyembroidery/PyEmbroidery.py index 164243e..f44e5d5 100644 --- a/pyembroidery/PyEmbroidery.py +++ b/pyembroidery/PyEmbroidery.py @@ -54,6 +54,8 @@ import pyembroidery.CsvReader as CsvReader import pyembroidery.PngWriter as PngWriter import pyembroidery.TxtWriter as TxtWriter +import pyembroidery.GcodeWriter as GcodeWriter +import pyembroidery.GcodeReader as GcodeReader def supported_formats(): @@ -397,6 +399,17 @@ def supported_formats(): "mimic": (True, False), }, }) + yield ({ + "description": "txt Format, Text File", + "extension": "gcode", + "mimetype": "text/plain", + "category": "embroidery", + "reader": GcodeReader, + "writer": GcodeWriter, + "options": { + "stitch_z_travel": (5.0, 10.0), + }, + }) def convert(filename_from, filename_to, settings=None): @@ -482,6 +495,11 @@ def read_csv(f, settings=None, pattern=None): return read_embroidery(CsvReader, f, settings, pattern) +def read_gcode(f, settings=None, pattern=None): + """Reads fileobject as GCode file""" + return read_embroidery(GcodeReader, f, settings, pattern) + + def read(filename, settings=None, pattern=None): """Reads file, assuming type by extension""" extension = get_extension_by_filename(filename) @@ -501,7 +519,6 @@ def write_embroidery(writer, pattern, stream, settings=None): settings = {} else: settings = settings.copy() - try: encode = writer.ENCODE except AttributeError: @@ -538,6 +555,21 @@ def write_embroidery(writer, pattern, stream, settings=None): settings["thread_change_command"] = writer.THREAD_CHANGE_COMMAND except AttributeError: pass + if not ("translate" in settings): + try: + settings["translate"] = writer.TRANSLATE + except AttributeError: + pass + if not ("scale" in settings): + try: + settings["scale"] = writer.SCALE + except AttributeError: + pass + if not ("rotate" in settings): + try: + settings["rotate"] = writer.ROTATE + except AttributeError: + pass pattern = pattern.get_normalized_pattern(settings) if isinstance(stream, str): @@ -592,6 +624,12 @@ def write_txt(pattern, stream, settings=None): write_embroidery(TxtWriter, pattern, stream, settings) +def write_gcode(pattern, stream, settings=None): + """Writes fileobject as Gcode file""" + write_embroidery(GcodeWriter, pattern, stream, settings) + + + def write_svg(pattern, stream, settings=None): """Writes fileobject as DST file""" write_embroidery(SvgWriter, pattern, stream, settings) diff --git a/pyembroidery/Vp3Writer.py b/pyembroidery/Vp3Writer.py index 6c4d195..96bd341 100644 --- a/pyembroidery/Vp3Writer.py +++ b/pyembroidery/Vp3Writer.py @@ -211,11 +211,7 @@ def vp3_write_thread(f, thread): def write_stitches_block(f, stitches, first_pos_x, first_pos_y): - # Embroidermodder code has - # vp3_write_string(f, "\x00"); # The 0, x, 0 bytes come before placeholders - # Given this consistency, it's doubtful this is a string. - # Those aren't f.write(b'\x00\x01\x00') placeholder_distance_to_end_of_stitches_block_010 = f.tell() write_int_32be(f, 0) # placeholder diff --git a/test/test_convert_gcode.py b/test/test_convert_gcode.py new file mode 100644 index 0000000..35718d7 --- /dev/null +++ b/test/test_convert_gcode.py @@ -0,0 +1,139 @@ +from __future__ import print_function + +import unittest + +from pattern_for_tests import * + + +class TestConverts(unittest.TestCase): + + def position_equals(self, stitches, j, k): + self.assertEqual(stitches[j][:1], stitches[k][:1]) + + def test_convert_gcode_to_u01(self): + file1 = "convert_u01.gcode" + file2 = "converted_gcode.u01" + write_gcode(get_big_pattern(), file1) + f_pattern = read_gcode(file1) + write_u01(f_pattern, file2) + t_pattern = read_u01(file2) + + self.assertIsNotNone(t_pattern) + self.assertEqual(t_pattern.count_stitch_commands(NEEDLE_SET), 16) + self.assertEqual(t_pattern.count_stitch_commands(STITCH), 16 * 5) + self.position_equals(t_pattern.stitches, 0, -1) + print("gcode->u01: ", t_pattern.stitches) + self.addCleanup(os.remove, file1) + self.addCleanup(os.remove, file2) + + def test_convert_gcode_to_csv(self): + file1 = "convert_csv.gcode" + file2 = "converted_gcode.csv" + write_gcode(get_big_pattern(), file1, {"encode": True}) + f_pattern = read_gcode(file1) + write_csv(f_pattern, file2) + t_pattern = read_csv(file2) + + self.assertIsNotNone(t_pattern) + self.assertEqual(t_pattern.count_stitch_commands(COLOR_CHANGE), 15) + self.assertEqual(t_pattern.count_stitch_commands(STITCH), 16 * 5) + self.position_equals(t_pattern.stitches, 0, -1) + print("gcode->csv: ", t_pattern.stitches) + self.addCleanup(os.remove, file1) + self.addCleanup(os.remove, file2) + + def test_convert_gcode_to_exp(self): + file1 = "convert_exp.gcode" + file2 = "converted_gcode.exp" + write_gcode(get_big_pattern(), file1) + f_pattern = read_gcode(file1) + write_exp(f_pattern, file2) + t_pattern = read_exp(file2) + + self.assertIsNotNone(t_pattern) + self.assertEqual(t_pattern.count_stitch_commands(COLOR_CHANGE), 15) + self.assertEqual(t_pattern.count_stitch_commands(STITCH), 16 * 5) + self.position_equals(t_pattern.stitches, 0, -1) + print("gcode->exp: ", t_pattern.stitches) + self.addCleanup(os.remove, file1) + self.addCleanup(os.remove, file2) + + def test_convert_gcode_to_pes(self): + file1 = "convert_pes.gcode" + file2 = "converted_gcode.pes" + write_gcode(get_big_pattern(), file1) + f_pattern = read_gcode(file1) + write_pes(f_pattern, file2) + t_pattern = read_pes(file2) + + self.assertIsNotNone(t_pattern) + self.assertEqual(t_pattern.count_stitch_commands(COLOR_CHANGE), 15) + self.assertEqual(t_pattern.count_stitch_commands(STITCH), 16 * 5) + self.position_equals(t_pattern.stitches, 0, -1) + print("gcode->pes: ", t_pattern.stitches) + self.addCleanup(os.remove, file1) + self.addCleanup(os.remove, file2) + + def test_convert_gcode_to_jef(self): + file1 = "convert_jef.gcode" + file2 = "converted_gcode.jef" + write_gcode(get_big_pattern(), file1) + f_pattern = read_gcode(file1) + write_jef(f_pattern, file2) + t_pattern = read_jef(file2) + + self.assertIsNotNone(t_pattern) + self.assertEqual(t_pattern.count_stitch_commands(COLOR_CHANGE), 15) + self.assertEqual(t_pattern.count_stitch_commands(STITCH), 16 * 5) + self.position_equals(t_pattern.stitches, 0, -1) + print("gcode->jef: ", t_pattern.stitches) + self.addCleanup(os.remove, file1) + self.addCleanup(os.remove, file2) + + def test_convert_gcode_to_pec(self): + file1 = "convert_pec.gcode" + file2 = "converted_gcode.pec" + write_gcode(get_big_pattern(), file1) + f_pattern = read_gcode(file1) + write_pec(f_pattern, file2) + t_pattern = read_pec(file2) + + self.assertIsNotNone(t_pattern) + self.assertEqual(t_pattern.count_stitch_commands(COLOR_CHANGE), 15) + self.assertEqual(t_pattern.count_stitch_commands(STITCH), 16 * 5) + self.position_equals(t_pattern.stitches, 0, -1) + print("gcode->pec: ", t_pattern.stitches) + self.addCleanup(os.remove, file1) + self.addCleanup(os.remove, file2) + + def test_convert_gcode_to_vp3(self): + file1 = "convert_vp3.gcode" + file2 = "converted_gcode.vp3" + write_gcode(get_big_pattern(), file1) + f_pattern = read_gcode(file1) + write_vp3(f_pattern, file2) + t_pattern = read_vp3(file2) + + self.assertIsNotNone(t_pattern) + self.assertEqual(t_pattern.count_stitch_commands(COLOR_CHANGE), 15) + self.assertEqual(t_pattern.count_stitch_commands(STITCH), 16 * 5) + self.position_equals(t_pattern.stitches, 0, -1) + print("gcode->vp3: ", t_pattern.stitches) + self.addCleanup(os.remove, file1) + self.addCleanup(os.remove, file2) + + def test_convert_gcode_to_dst(self): + file1 = "convert_dst.gcode" + file2 = "converted_gcode.dst" + write_gcode(get_big_pattern(), file1) + f_pattern = read_gcode(file1) + write_dst(f_pattern, file2) + t_pattern = read_dst(file2) + + self.assertIsNotNone(t_pattern) + self.assertEqual(t_pattern.count_stitch_commands(COLOR_CHANGE), 15) + self.assertEqual(t_pattern.count_stitch_commands(STITCH), 16 * 5) + self.position_equals(t_pattern.stitches, 0, -1) + print("gcode->dst: ", t_pattern.stitches) + self.addCleanup(os.remove, file1) + self.addCleanup(os.remove, file2) diff --git a/test/test_writes.py b/test/test_writes.py index 413bf3e..250336b 100644 --- a/test/test_writes.py +++ b/test/test_writes.py @@ -104,6 +104,17 @@ def test_write_csv_read_csv(self): print("csv: ", csv_pattern.stitches) self.addCleanup(os.remove, file1) + def test_write_gcode_read_gcode(self): + file1 = "file.gcode" + write_gcode(get_big_pattern(), file1) + gcode_pattern = read_gcode(file1) + self.assertIsNotNone(gcode_pattern) + self.assertEqual(gcode_pattern.count_stitch_commands(COLOR_CHANGE), 15) + self.assertEqual(gcode_pattern.count_stitch_commands(STITCH), 5 * 16) + self.position_equals(gcode_pattern.stitches, 0, -1) + write_txt(get_big_pattern(), file1, {"mimic": True}) + self.addCleanup(os.remove, file1) + def test_write_txt(self): file1 = "file.txt" write_txt(get_big_pattern(), file1) From 60afd80751b9d27ea6530970bafcc4d9537bb43c Mon Sep 17 00:00:00 2001 From: tatarize Date: Mon, 29 Oct 2018 08:10:49 -0700 Subject: [PATCH 2/2] 3.6 compat tweak --- pyembroidery/GcodeReader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyembroidery/GcodeReader.py b/pyembroidery/GcodeReader.py index 3a43286..f214704 100644 --- a/pyembroidery/GcodeReader.py +++ b/pyembroidery/GcodeReader.py @@ -34,7 +34,7 @@ def parse(f): or byte == b'+' \ or byte == b'-' \ or byte == b'.': - value += str(byte) + value += byte.decode('utf8') continue if ord_A <= b <= ord_Z: