13
13
from PIL import Image
14
14
import re
15
15
import base64
16
+ import imghdr
16
17
17
18
from flask_babel import gettext
18
19
from octoprint .access import ADMIN_GROUP
@@ -50,7 +51,7 @@ def get_settings_defaults(self):
50
51
'inline_thumbnail_scale_value' : "50" , 'inline_thumbnail_position_left' : False ,
51
52
'align_inline_thumbnail' : False , 'inline_thumbnail_align_value' : "left" , 'state_panel_thumbnail' : True ,
52
53
'state_panel_thumbnail_scale_value' : "100" , 'resize_filelist' : False , 'filelist_height' : "306" ,
53
- 'scale_inline_thumbnail_position' : False , 'sync_on_refresh' : False }
54
+ 'scale_inline_thumbnail_position' : False , 'sync_on_refresh' : False , 'use_uploads_folder' : False }
54
55
55
56
# ~~ AssetPlugin mixin
56
57
@@ -65,16 +66,19 @@ def get_template_configs(self):
65
66
]
66
67
67
68
def _extract_thumbnail (self , gcode_filename , thumbnail_filename ):
68
- regex = r"(?:^; thumbnail begin \d+[x ]\d+ \d+)(?:\n|\r\n?)((?:.+(?:\n|\r\n?))+?)(?:^; thumbnail end)"
69
+ regex = r"(?:^; thumbnail(?:_JPG)* begin \d+[x ]\d+ \d+)(?:\n|\r\n?)((?:.+(?:\n|\r\n?))+?)(?:^; thumbnail(?:_JPG)* end)"
69
70
regex_mks = re .compile ('(?:;(?:simage|;gimage):).*?M10086 ;[\r \n ]' , re .DOTALL )
70
71
regex_weedo = re .compile ('W221[\r \n ](.*)[\r \n ]W222' , re .DOTALL )
71
72
regex_luban = re .compile (';thumbnail: data:image/png;base64,(.*)[\r \n ]' , re .DOTALL )
72
73
regex_qidi = re .compile ('M4010.*\' (.*)\' ' , re .DOTALL )
74
+ regex_creality = r"(?:^; jpg begin .*)(?:\n|\r\n?)((?:.+(?:\n|\r\n?))+?)(?:^; jpg end)"
73
75
lineNum = 0
74
76
collectedString = ""
75
77
use_mks = False
76
78
use_weedo = False
77
79
use_qidi = False
80
+ use_flashprint = False
81
+ use_creality = False
78
82
with open (gcode_filename , "rb" ) as gcode_file :
79
83
for line in gcode_file :
80
84
lineNum += 1
@@ -109,6 +113,19 @@ def _extract_thumbnail(self, gcode_filename, thumbnail_filename):
109
113
if len (matches ) > 0 :
110
114
self ._logger .debug ("Found qidi thumbnail." )
111
115
use_qidi = True
116
+ if len (matches ) == 0 : # FlashPrint fallback
117
+ with open (gcode_filename , "rb" ) as gcode_file :
118
+ gcode_file .seek (58 )
119
+ thumbbytes = gcode_file .read (14454 )
120
+ if imghdr .what (file = None , h = thumbbytes ) == 'bmp' :
121
+ self ._logger .debug ("Found flashprint thumbnail." )
122
+ matches = [thumbbytes ]
123
+ use_flashprint = True
124
+ if len (matches ) == 0 : # Creality Neo fallback
125
+ matches = re .findall (regex_creality , test_str , re .MULTILINE )
126
+ if len (matches ) > 0 :
127
+ self ._logger .debug ("Found creality thumbnail." )
128
+ use_creality = True
112
129
if len (matches ) > 0 :
113
130
maxlen = 0
114
131
choosen = - 1
@@ -125,11 +142,38 @@ def _extract_thumbnail(self, gcode_filename, thumbnail_filename):
125
142
png_file .write (self ._extract_mks_thumbnail (matches ))
126
143
elif use_weedo :
127
144
png_file .write (self ._extract_weedo_thumbnail (matches ))
145
+ elif use_creality :
146
+ png_file .write (self ._extract_creality_thumbnail (matches [choosen ]))
128
147
elif use_qidi :
129
148
self ._logger .debug (matches )
149
+ elif use_flashprint :
150
+ png_file .write (self ._extract_flashprint_thumbnail (matches ))
130
151
else :
131
152
png_file .write (base64 .b64decode (matches [choosen ].replace ("; " , "" ).encode ()))
132
153
154
+ # Extracts a thumbnail from hex binary data usd by FlashPrint slicer
155
+ def _extract_flashprint_thumbnail (self , gcode_encoded_images ):
156
+ encoded_image = gcode_encoded_images [0 ]
157
+
158
+ image = Image .open (io .BytesIO (encoded_image )).resize ((160 ,120 ))
159
+ rgba = image .convert ("RGBA" )
160
+ pixels = rgba .getdata ()
161
+ newData = []
162
+
163
+ alphamaxvalue = 35
164
+ for pixel in pixels :
165
+ 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
166
+ newData .append ((255 , 255 , 255 , 0 )) # storing a transparent value when we find a black/dark colour
167
+ else :
168
+ newData .append (pixel ) # other colours remain unchanged
169
+
170
+ rgba .putdata (newData )
171
+
172
+ with io .BytesIO () as png_bytes :
173
+ rgba .save (png_bytes , "PNG" )
174
+ png_bytes_string = png_bytes .getvalue ()
175
+ return png_bytes_string
176
+
133
177
# Extracts a thumbnail from hex binary data usd by Qidi slicer
134
178
def _extract_qidi_thumbnail (self , gcode_encoded_images ):
135
179
encoded_image = gcode_encoded_images [0 ].replace ('W220 ' , '' ).replace ('\n ' , '' ).replace ('\r ' , '' ).replace (' ' , '' )
@@ -160,7 +204,16 @@ def _extract_mks_thumbnail(self, gcode_encoded_images):
160
204
161
205
# Load pixel data
162
206
image = Image .frombytes ('RGB' , encoded_image_dimensions , encoded_image , 'raw' , 'BGR;16' , 0 , 1 )
207
+ return self ._imageToPng (image )
208
+
209
+ # Extracts a thumbnail from hex binary data usd by Qidi slicer
210
+ def _extract_creality_thumbnail (self , match ):
211
+ encoded_jpg = base64 .b64decode (match .replace ("; " , "" ).encode ())
212
+ with io .BytesIO (encoded_jpg ) as jpg_bytes :
213
+ image = Image .open (jpg_bytes )
214
+ return self ._imageToPng (image )
163
215
216
+ def _imageToPng (self , image ):
164
217
# Save image as png
165
218
with io .BytesIO () as png_bytes :
166
219
image .save (png_bytes , "PNG" )
@@ -205,20 +258,23 @@ def on_event(self, event, payload):
205
258
"type" ] and payload .get ("name" , False ):
206
259
thumbnail_name = self .regex_extension .sub (".png" , payload ["name" ])
207
260
thumbnail_path = self .regex_extension .sub (".png" , payload ["path" ])
208
- thumbnail_filename = "{}/{}" .format (self .get_plugin_data_folder (), thumbnail_path )
261
+ if not self ._settings .get_boolean (["use_uploads_folder" ]):
262
+ thumbnail_filename = "{}/{}" .format (self .get_plugin_data_folder (), thumbnail_path )
263
+ else :
264
+ thumbnail_filename = self ._file_manager .path_on_disk ("local" , thumbnail_path )
209
265
210
266
if os .path .exists (thumbnail_filename ):
211
267
os .remove (thumbnail_filename )
212
268
if event == "FileAdded" :
213
269
gcode_filename = self ._file_manager .path_on_disk ("local" , payload ["path" ])
214
270
self ._extract_thumbnail (gcode_filename , thumbnail_filename )
215
271
if os .path .exists (thumbnail_filename ):
216
- thumbnail_url = "plugin/prusaslicerthumbnails/thumbnail/{}?{:%Y%m%d%H%M%S}" . format (
217
- thumbnail_path .replace (thumbnail_name , quote (thumbnail_name )), datetime .datetime .now ())
218
- self . _file_manager . set_additional_metadata ( "local" , payload [ "path" ], "thumbnail" ,
219
- thumbnail_url . replace ( "//" , "/" ), overwrite = True )
220
- self ._file_manager .set_additional_metadata ("local" , payload ["path" ], "thumbnail_src" ,
221
- self ._identifier , overwrite = True )
272
+ if not self . _settings . get_boolean ([ "use_uploads_folder" ]):
273
+ thumbnail_url = "plugin/prusaslicerthumbnails/thumbnail/{}?{:%Y%m%d%H%M%S}" . format ( thumbnail_path .replace (thumbnail_name , quote (thumbnail_name )), datetime .datetime .now ())
274
+ else :
275
+ thumbnail_url = "downloads/files/local/{}?{:%Y%m%d%H%M%S}" . format ( thumbnail_path . replace ( thumbnail_name , quote ( thumbnail_name )), datetime . datetime . now () )
276
+ self ._file_manager .set_additional_metadata ("local" , payload ["path" ], "thumbnail" , thumbnail_url . replace ( "//" , "/" ), overwrite = True )
277
+ self . _file_manager . set_additional_metadata ( "local" , payload [ "path" ], "thumbnail_src" , self ._identifier , overwrite = True )
222
278
223
279
# ~~ SimpleApiPlugin mixin
224
280
@@ -258,7 +314,7 @@ def on_api_command(self, command, data):
258
314
def scan_files (self ):
259
315
self ._logger .debug ("Crawling Files" )
260
316
file_list = self ._file_manager .list_files (recursive = True )
261
- self ._logger .info (file_list )
317
+ self ._logger .debug (file_list )
262
318
local_files = file_list ["local" ]
263
319
results = dict (no_thumbnail = [], no_thumbnail_src = [])
264
320
for key , file in local_files .items ():
0 commit comments