I just picked up a Sindoh 3DWOX DP200 running the AQ firmware. I got sick of the error message when printing from S3D. I wrote a VERY rough python script that removes the error and provides the default functionality such as time, build size, thumbnail image and percent bar progress. It also removes the error when you start printing.
Copy and paste the following code into a text file with a .py extension.
Install python
At command prompt >>python3 code.py
Good luck!
######################################
# SIMPLIFY3D to 3DWOX
# Carl Madson
#
[email protected]
# December 30, 2023
# In S3D under Edit Process Settings, Output Tab, check Include Thumbnail Images
# Thumbnail Encoding: Octoprint, Klipper....
# Default size of 300x300 seems to work fine
# My apologies for this being rough and inefficient, not my most elegant code
# December 31, 2023
# Cleaned up the code A LOT, used objects, added raft layers
# Also need to clean up find first int/float functions
######################################
import base64
import math
import re
import tkinter as tk
from tkinter import filedialog
TK_SILENCE_DEPRECATION=1
def pick_file():
root = tk.Tk()
root.withdraw() # Hide the main Tkinter window
# Show the file picker dialog
file_path = filedialog.askopenfilename() # Returns the selected file path or None
root.destroy() # Close the Tkinter window
return file_path
file_path_in = pick_file()
tmp = file_path_in.split('.')
file_path_in_noext = tmp[0]
file_path_out = file_path_in_noext + "_3DWOX.gcode"
S3D_in = open(file_path_in, "r")
W3D_out = open(file_path_out, "w")
class printObject:
tsl = 80 #thumbnail string length
header = ";3DWOX Desktop Version : 1.4.1157"
gcode = []
ss = []
bs = []
thumbnails = []
ssdict = {}
name = ""
material = ""
raft_layers = 0
xyadj = 0.0
hours = 0
minutes = 0
length = 0.0 #mm
volume = 0.0 #mm^3
weight = 0.0 #grams
cost = 0.0 #unitless
po = printObject()
######################################
# FIND_FIRST_INT
######################################
def find_first_int(string):
match = re.search(r'\d+', string)
if match:
return int(match.group())
else:
return None
######################################
# FIND_SECOND_INT
######################################
def find_second_int(text):
matches = re.findall(r'\d+', text)
if len(matches) >= 2:
return matches[1] # Return the second match
else:
return None
######################################
# FIND_FIRST_FLOAT
######################################
def find_first_float(text):
# Regular expression for matching a floating point number
match = re.search(r'\b\d+(\.\d+)?([eE][+-]?\d+)?\b', text)
if match:
return match.group()
else:
return None
######################################
# DETERMINE_BUILD_SIZE
######################################
def determine_build_size(po):
min_x = min_y = float('inf')
max_x = max_y = float('-inf')
is_part_started = False
for line in po.gcode:
if '; feature outer perimeter' in line: # Placeholder for part start marker
is_part_started = True
if is_part_started:
if line.startswith('; layer ') and not line.endswith('end'):
po.layers = find_first_int(line)
tmp = line.split(',')
po.zmax = round(float(find_first_float(tmp[1])),1)
if line.startswith(('G0 ', 'G1 ')) and 'E' in line: # Movement with extrusion
parts = line.split()
for part in parts:
if part.startswith('X'):
x = float(part[1:])
min_x, max_x = min(min_x, x), max(max_x, x)
elif part.startswith('Y'):
y = float(part[1:])
min_y, max_y = min(min_y, y), max(max_y, y)
# Calculating dimensions
po.width = round(max_x - min_x,0)
po.depth = round(max_y - min_y,0)
return po
######################################
# GET_BUILD_SETTINGS
######################################
def get_build_settings(po):
for line in po.bs:
if line.startswith('; Build Time:'):
po.hours = int(find_first_int(line))
po.minutes = int(find_second_int(line))
if line.startswith('; Material Length:'): #mm
po.length = float(find_first_float(line))
if line.startswith('; Material Volume:'): #mm^3
po.volume = float(find_first_float(line))
if line.startswith('; Material Weight:'): #g
po.weight = float(find_first_float(line))
if line.startswith('; Material Cost:'):
po.cost = float(find_first_float(line))
return po
######################################
# BUILD_SYSTEM_SETTINGS_DICT
######################################
def build_system_settings_dict(po):
for line in po.ss:
pos = re.search(r'\w', line)
if re.match(r';\s+\w+,\w+', line):
key, value = line[pos.start():].split(',', maxsplit = 1)
if value.isdigit():
value = int(value)
elif re.match(r'^-?\d+(\.\d+)?$', value):
value = float(value)
po.ssdict[key] = value
return po
######################################
# DETERMINE_SYSTEM_SETTINGS
######################################
def determine_system_settings(po):
po.material = po.ssdict['autoConfigureMaterial']
po.name = po.ssdict['targetModels']
tmp1 = 0
tmp2 = 0
if po.ssdict['useSkirt']:
tmp1 = po.ssdict['skirtOutlines'] * (po.ssdict['firstLayerWidthAbsolute'] * (po.ssdict['firstLayerWidthPercentage'] / 100)) + 0*po.ssdict['skirtOffset']
if po.ssdict['useRaft']:
po.raftLayers = po.ssdict['raftBaseLayers'] + po.ssdict['raftTopLayers']
tmp2 = po.ssdict['raftOffset'] + po.ssdict['raftSeperationDistance']
po.xyadj = 2 * max(tmp1, tmp2)
return po
######################################
# EXTRACT_DATA
######################################
def extract_data(po, file_handle):
is_thumbnail_section = False
thumbnail_data = ""
is_gcode_section = False
is_bs_section = False
is_ss_section = False
for line in file_handle:
# THUMBNAIL start
if line.startswith('; thumbnail begin'):
is_thumbnail_section = True
thumbnail_data = ""
continue
if line.startswith('; thumbnail end'):
is_thumbnail_section = False
# Decode and store the thumbnail data
try:
decoded_data = base64.b64decode(thumbnail_data)
po.thumbnails.append(decoded_data)
except base64.binascii.Error:
print("Error decoding base64 data")
continue
if is_thumbnail_section:
newline = line[1:]
thumbnail_data += newline.strip()
continue
# GCODE start
if line.startswith('G90'):
is_gcode_section = True
if is_gcode_section:
po.gcode.append(line.strip())
if line.startswith('M84') and is_gcode_section:
is_gcode_section = False
# BS Start
if line.startswith('; Build Time:'):
is_bs_section = True
if is_bs_section:
po.bs.append(line.strip())
if line.startswith('; Material Cost:'):
is_bs_section = False
# SS Start
if line.startswith('; G-Code generated by Simplify3D'):
is_ss_section = True
if line.startswith('G90'):
is_ss_section = False
if is_ss_section:
po.ss.append(line.strip())
return po
######################################
# WRITE_GCODE
######################################
def write_gcode(po, file_handle):
for line in po.gcode:
file_handle.write(f"{line}\n")
######################################
# WRITE_SS
######################################
def write_ss(po, file_handle):
for line in po.ss:
file_handle.write(f"{line}\n")
file_handle.write(f"\n")
######################################
# WRITE_HEADER
######################################
def write_header(po, file_handle):
file_handle.write(f"\n{po.header}\n\n")
######################################
# WRITE_THUMBNAILS
######################################
def write_thumbnails(po, file_handle):
for index, thumbnail in enumerate(po.thumbnails):
encoded_thumbnail = base64.b64encode(thumbnail)
decoded_string = encoded_thumbnail.decode("utf-8")
rows = math.ceil(len(decoded_string) / po.tsl)
for i in range(1, rows+1):
write_string = decoded_string[(i-1)*po.tsl:i*po.tsl]
file_handle.write(f";IMAGE[{i}/{rows}] {write_string}\n")
######################################
# WRITE_SINDOH
######################################
def write_sindoh(po, file_handle):
filename = "f{po.name}"
encoded_filename = base64.b64encode(filename.encode())
file_handle.write(f"\n;FILENAME[1/1] {encoded_filename.decode('utf-8')}\n\n")
file_handle.write(f";PRINTER_MODEL: [DP200]\n")
file_handle.write(f";ESTIMATION_TIME: [{po.hours}:{po.minutes}:00]\n\n")
file_handle.write(f";DIMENSION: [{po.width}:{po.depth}:{po.zmax}]\n")
file_handle.write(f";LOCATION: [89:84]\n")
file_handle.write(f";OPERATINGZONE: [31:32:5]\n\n")
file_handle.write(f";CARTRIDGE_COUNT_TOTAL: [1]\n")
file_handle.write(f";USEDNOZZLE: [1]\n")
file_handle.write(f";CARTRIDGE_USED_STATE: [T]\n\n")
file_handle.write(f";ESTIMATION_FILAMENT_CARTRIDGE_0: [{po.length}]\n")
file_handle.write(f";MASS_CARTRIDGE_0: [{po.weight}]\n")
file_handle.write(f";MATERIAL_CARTRIDGE_0: [{po.material}]\n")
file_handle.write(f";ESTIMATION_FILAMENT: [{po.length}]\n")
file_handle.write(f";MASS: [{po.weight}]\n")
file_handle.write(f";MATERIAL: [{po.material}]\n\n")
file_handle.write(f";TOTAL_LAYER: [{po.layers}]\n")
file_handle.write(f";TOTAL_RAFTLAYER: [{po.raft_layers}]\n")
file_handle.write(f";TOTAL_MASS: [{po.weight}]\n\n")
######################################
# MAIN
######################################
po = extract_data(po, S3D_in)
po = build_system_settings_dict(po)
po = determine_system_settings(po)
po = determine_build_size(po)
po = get_build_settings(po)
write_header(po, W3D_out)
write_ss(po, W3D_out)
write_thumbnails(po, W3D_out)
write_sindoh(po, W3D_out)
write_gcode(po, W3D_out)
S3D_in.close()
W3D_out.close()