Skip to content

Commit

Permalink
Merge pull request #102 from jneilliii/rc
Browse files Browse the repository at this point in the history
  • Loading branch information
jneilliii authored Mar 11, 2023
2 parents 1563137 + 999f1f1 commit d4bc5ca
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

![GitHub Downloads](https://badgen.net/github/assets-dl/jneilliii/OctoPrint-PrusaSlicerThumbnails/)

This plugin will extract embedded thumbnails from gcode files created from [PrusaSlicer](#PrusaSlicer), [SuperSlicer](#SuperSlicer), [Cura](#Cura), or [Simplify3D](#Simplify3D).
This plugin will extract embedded thumbnails from gcode files created from [PrusaSlicer](#PrusaSlicer), [SuperSlicer](#SuperSlicer), [Cura](#Cura), [Simplify3D](#Simplify3D), [IdeaMaker](#IdeaMaker), or FlashPrint (FlashForge printers).

The preview thumbnail can be shown in OctoPrint from the files list by clicking the newly added image button.

Expand Down
76 changes: 66 additions & 10 deletions octoprint_prusaslicerthumbnails/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from PIL import Image
import re
import base64
import imghdr

from flask_babel import gettext
from octoprint.access import ADMIN_GROUP
Expand Down Expand Up @@ -50,7 +51,7 @@ def get_settings_defaults(self):
'inline_thumbnail_scale_value': "50", 'inline_thumbnail_position_left': False,
'align_inline_thumbnail': False, 'inline_thumbnail_align_value': "left", 'state_panel_thumbnail': True,
'state_panel_thumbnail_scale_value': "100", 'resize_filelist': False, 'filelist_height': "306",
'scale_inline_thumbnail_position': False, 'sync_on_refresh': False}
'scale_inline_thumbnail_position': False, 'sync_on_refresh': False, 'use_uploads_folder': False}

# ~~ AssetPlugin mixin

Expand All @@ -65,16 +66,19 @@ def get_template_configs(self):
]

def _extract_thumbnail(self, gcode_filename, thumbnail_filename):
regex = r"(?:^; thumbnail begin \d+[x ]\d+ \d+)(?:\n|\r\n?)((?:.+(?:\n|\r\n?))+?)(?:^; thumbnail end)"
regex = r"(?:^; thumbnail(?:_JPG)* begin \d+[x ]\d+ \d+)(?:\n|\r\n?)((?:.+(?:\n|\r\n?))+?)(?:^; thumbnail(?:_JPG)* end)"
regex_mks = re.compile('(?:;(?:simage|;gimage):).*?M10086 ;[\r\n]', re.DOTALL)
regex_weedo = re.compile('W221[\r\n](.*)[\r\n]W222', re.DOTALL)
regex_luban = re.compile(';thumbnail: data:image/png;base64,(.*)[\r\n]', re.DOTALL)
regex_qidi = re.compile('M4010.*\'(.*)\'', re.DOTALL)
regex_creality = r"(?:^; jpg begin .*)(?:\n|\r\n?)((?:.+(?:\n|\r\n?))+?)(?:^; jpg end)"
lineNum = 0
collectedString = ""
use_mks = False
use_weedo = False
use_qidi = False
use_flashprint = False
use_creality = False
with open(gcode_filename, "rb") as gcode_file:
for line in gcode_file:
lineNum += 1
Expand Down Expand Up @@ -109,6 +113,19 @@ def _extract_thumbnail(self, gcode_filename, thumbnail_filename):
if len(matches) > 0:
self._logger.debug("Found qidi thumbnail.")
use_qidi = True
if len(matches) == 0: # FlashPrint fallback
with open(gcode_filename, "rb") as gcode_file:
gcode_file.seek(58)
thumbbytes = gcode_file.read(14454)
if imghdr.what(file=None, h=thumbbytes) == 'bmp':
self._logger.debug("Found flashprint thumbnail.")
matches = [thumbbytes]
use_flashprint = True
if len(matches) == 0: # Creality Neo fallback
matches = re.findall(regex_creality, test_str, re.MULTILINE)
if len(matches) > 0:
self._logger.debug("Found creality thumbnail.")
use_creality = True
if len(matches) > 0:
maxlen=0
choosen=-1
Expand All @@ -125,11 +142,38 @@ def _extract_thumbnail(self, gcode_filename, thumbnail_filename):
png_file.write(self._extract_mks_thumbnail(matches))
elif use_weedo:
png_file.write(self._extract_weedo_thumbnail(matches))
elif use_creality:
png_file.write(self._extract_creality_thumbnail(matches[choosen]))
elif use_qidi:
self._logger.debug(matches)
elif use_flashprint:
png_file.write(self._extract_flashprint_thumbnail(matches))
else:
png_file.write(base64.b64decode(matches[choosen].replace("; ", "").encode()))

# Extracts a thumbnail from hex binary data usd by FlashPrint slicer
def _extract_flashprint_thumbnail(self, gcode_encoded_images):
encoded_image = gcode_encoded_images[0]

image = Image.open(io.BytesIO(encoded_image)).resize((160,120))
rgba = image.convert("RGBA")
pixels = rgba.getdata()
newData = []

alphamaxvalue = 35
for pixel in pixels:
if pixel[0] >= 0 and pixel[0] <= alphamaxvalue and pixel[1] >= 0 and pixel[1] <= alphamaxvalue and pixel[2] >= 0 and pixel[2] <= alphamaxvalue : # finding black colour by its RGB value
newData.append((255, 255, 255, 0)) # storing a transparent value when we find a black/dark colour
else:
newData.append(pixel) # other colours remain unchanged

rgba.putdata(newData)

with io.BytesIO() as png_bytes:
rgba.save(png_bytes, "PNG")
png_bytes_string = png_bytes.getvalue()
return png_bytes_string

# Extracts a thumbnail from hex binary data usd by Qidi slicer
def _extract_qidi_thumbnail(self, gcode_encoded_images):
encoded_image = gcode_encoded_images[0].replace('W220 ', '').replace('\n', '').replace('\r', '').replace(' ', '')
Expand Down Expand Up @@ -160,7 +204,16 @@ def _extract_mks_thumbnail(self, gcode_encoded_images):

# Load pixel data
image = Image.frombytes('RGB', encoded_image_dimensions, encoded_image, 'raw', 'BGR;16', 0, 1)
return self._imageToPng(image)

# Extracts a thumbnail from hex binary data usd by Qidi slicer
def _extract_creality_thumbnail(self, match):
encoded_jpg = base64.b64decode(match.replace("; ", "").encode())
with io.BytesIO(encoded_jpg) as jpg_bytes:
image = Image.open(jpg_bytes)
return self._imageToPng(image)

def _imageToPng(self, image):
# Save image as png
with io.BytesIO() as png_bytes:
image.save(png_bytes, "PNG")
Expand Down Expand Up @@ -205,20 +258,23 @@ def on_event(self, event, payload):
"type"] and payload.get("name", False):
thumbnail_name = self.regex_extension.sub(".png", payload["name"])
thumbnail_path = self.regex_extension.sub(".png", payload["path"])
thumbnail_filename = "{}/{}".format(self.get_plugin_data_folder(), thumbnail_path)
if not self._settings.get_boolean(["use_uploads_folder"]):
thumbnail_filename = "{}/{}".format(self.get_plugin_data_folder(), thumbnail_path)
else:
thumbnail_filename = self._file_manager.path_on_disk("local", thumbnail_path)

if os.path.exists(thumbnail_filename):
os.remove(thumbnail_filename)
if event == "FileAdded":
gcode_filename = self._file_manager.path_on_disk("local", payload["path"])
self._extract_thumbnail(gcode_filename, thumbnail_filename)
if os.path.exists(thumbnail_filename):
thumbnail_url = "plugin/prusaslicerthumbnails/thumbnail/{}?{:%Y%m%d%H%M%S}".format(
thumbnail_path.replace(thumbnail_name, quote(thumbnail_name)), datetime.datetime.now())
self._file_manager.set_additional_metadata("local", payload["path"], "thumbnail",
thumbnail_url.replace("//", "/"), overwrite=True)
self._file_manager.set_additional_metadata("local", payload["path"], "thumbnail_src",
self._identifier, overwrite=True)
if not self._settings.get_boolean(["use_uploads_folder"]):
thumbnail_url = "plugin/prusaslicerthumbnails/thumbnail/{}?{:%Y%m%d%H%M%S}".format(thumbnail_path.replace(thumbnail_name, quote(thumbnail_name)), datetime.datetime.now())
else:
thumbnail_url = "downloads/files/local/{}?{:%Y%m%d%H%M%S}".format(thumbnail_path.replace(thumbnail_name, quote(thumbnail_name)), datetime.datetime.now())
self._file_manager.set_additional_metadata("local", payload["path"], "thumbnail", thumbnail_url.replace("//", "/"), overwrite=True)
self._file_manager.set_additional_metadata("local", payload["path"], "thumbnail_src", self._identifier, overwrite=True)

# ~~ SimpleApiPlugin mixin

Expand Down Expand Up @@ -258,7 +314,7 @@ def on_api_command(self, command, data):
def scan_files(self):
self._logger.debug("Crawling Files")
file_list = self._file_manager.list_files(recursive=True)
self._logger.info(file_list)
self._logger.debug(file_list)
local_files = file_list["local"]
results = dict(no_thumbnail=[], no_thumbnail_src=[])
for key, file in local_files.items():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
<input type="checkbox" data-bind="checked: settings.plugins.prusaslicerthumbnails.sync_on_refresh"> Rescan all files when pressing refresh in file list.
</label>
</div>
<div class="controls">
<label class="checkbox">
<input type="checkbox" data-bind="checked: settings.plugins.prusaslicerthumbnails.use_uploads_folder"> Extract images into uploads folder.
</label>
</div>
</div>
<div class="row-fluid" data-bind="allowBindings: false">
<div class="row-fluid" data-bind="allowBindings: true">
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
plugin_name = "Slicer Thumbnails"

# The plugin's version. Can be overwritten within OctoPrint's internal data via __plugin_version__ in the plugin module
plugin_version = "1.0.1"
plugin_version = "1.0.2"

# The plugin's description. Can be overwritten within OctoPrint's internal data via __plugin_description__ in the plugin
# module
Expand Down

0 comments on commit d4bc5ca

Please sign in to comment.