diff --git a/Constants.py b/Constants.py index 4b17bd6..0e15e9f 100644 --- a/Constants.py +++ b/Constants.py @@ -12,6 +12,7 @@ SCREENSHOT_INDEX = "mks_screenshot_index" SIMAGE = "mks_simage" GIMAGE = "mks_gimage" +IS_PREVIEW_ENCODED = "mks_is_preview_encoded" # Errors EXCEPTION_MESSAGE = "An exception occurred in network connection: %s" diff --git a/MKSPreview.py b/MKSPreview.py index 93e75ad..c9cb1eb 100644 --- a/MKSPreview.py +++ b/MKSPreview.py @@ -1,11 +1,14 @@ # Copyright (c) 2021 # MKS Plugin is released under the terms of the AGPLv3 or higher. +from array import array from UM.Application import Application from cura.Snapshot import Snapshot from PyQt6 import QtCore from UM.Logger import Logger + from . import Constants +from .encoders import ColPicEncoder def add_leading_zeros(rgb): str_hex = "%x" % rgb @@ -18,19 +21,21 @@ def add_leading_zeros(rgb): str_hex = '000' + str_hex[0:1] return str_hex -def add_screenshot_str(img, width, height, img_type): - result = "" - b_image = img.scaled(width, height, QtCore.Qt.AspectRatioMode.KeepAspectRatio) - img_size = b_image.size() - result += img_type +def convert_to_rgb(image, height_pixel, width_pixel): + pixel_color = image.pixelColor(width_pixel, height_pixel) + r = pixel_color.red() >> 3 + g = pixel_color.green() >> 2 + b = pixel_color.blue() >> 3 + rgb = (r << 11) | (g << 5) | b + return rgb + + +def default_encode(scaled_image, img_type, img_size): + result = img_type datasize = 0 for i in range(img_size.height()): for j in range(img_size.width()): - pixel_color = b_image.pixelColor(j, i) - r = pixel_color.red() >> 3 - g = pixel_color.green() >> 2 - b = pixel_color.blue() >> 3 - rgb = (r << 11) | (g << 5) | b + rgb = convert_to_rgb(scaled_image, i, j) str_hex = add_leading_zeros(rgb) if str_hex[2:4] != '': result += str_hex[2:4] @@ -40,15 +45,91 @@ def add_screenshot_str(img, width, height, img_type): datasize += 2 if datasize >= 50: datasize = 0 - # if i != img_size.height() - 1: result += '\rM10086 ;' if i == img_size.height() - 1: result += "\r" return result +def custom_encode(scaled_image, img_type, img_size): + result = "" + color16 = array('H') + for i in range(img_size.height()): + for j in range(img_size.width()): + rgb = convert_to_rgb(scaled_image, i, j) + color16.append(rgb) + max_size = img_size.height()*img_size.width()*10 + output_data = bytearray(max_size) + resultInt = ColPicEncoder.ColPic_EncodeStr( + color16, + img_size.height(), + img_size.width(), + output_data, + max_size, + 1024 + ) + # legacy code, don't try to understand + # in short - add img_type and new lines where its needed + data_without_zeros = str(output_data).replace('\\x00', '') + data = data_without_zeros[2:len(data_without_zeros) - 2] + each_line_max = 1024 - 8 - 1 + max_lines = int(len(data)/each_line_max) + length_to_append = each_line_max - 3 - int(len(data)%each_line_max)+10 + j = 0 + for i in range(len(output_data)): + if (output_data[i] != 0): + if j == max_lines*each_line_max: + result += '\r;' + img_type + chr(output_data[i]) + elif j == 0: + result += img_type + chr(output_data[i]) + elif j%each_line_max == 0: + result += '\r' + img_type + chr(output_data[i]) + else: + result += chr(output_data[i]) + j += 1 + result += '\r;' + # add zeros to the end + for m in range(length_to_append): + result += '0' + return result + + +def add_screenshot_str(img, width, height, img_type, encoded): + result = "" + scaled_image = img.scaled(width, height, QtCore.Qt.AspectRatioMode.KeepAspectRatio) + img_size = scaled_image.size() + try: + if encoded: + result = custom_encode(scaled_image, img_type, img_size) + else: + result = default_encode(scaled_image, img_type, img_size) + except Exception as e: + Logger.log("d", "Unable to encode screenshot: " + str(e)) + return result + def take_screenshot(): - cut_image = Snapshot.snapshot(width = 900, height = 900) - return cut_image + # param width: width of the aspect ratio default 300 + # param height: height of the aspect ratio default 300 + # return: None when there is no model on the build plate otherwise it will return an image + return Snapshot.snapshot(width = 900, height = 900) + +def generate_preview(global_container_stack, image): + screenshot_string = "" + meta_data = global_container_stack.getMetaData() + Logger.log("d", "Get current preview settings.") + encoded = False + if Constants.IS_PREVIEW_ENCODED in meta_data: + encoded = True + if Constants.SIMAGE in meta_data: + simage = int(global_container_stack.getMetaDataEntry(Constants.SIMAGE)) + Logger.log("d", "mks_simage value: " + str(simage)) + screenshot_string += add_screenshot_str(image, simage, simage, ";simage:",encoded) + if Constants.GIMAGE in meta_data: + gimage = int(global_container_stack.getMetaDataEntry(Constants.GIMAGE)) + Logger.log("d", "mks_gimage value: " + str(gimage)) + # ;; - needed for correct colors. do not remove them. + screenshot_string += add_screenshot_str(image, gimage, gimage, ";;gimage:",encoded) + screenshot_string += "\r" + return simage,gimage,screenshot_string def add_preview(self): application = Application.getInstance() @@ -75,18 +156,7 @@ def add_preview(self): gimage = 0 if image: - meta_data = global_container_stack.getMetaData() - Logger.log("d", "Get current preview settings.") - if Constants.SIMAGE in meta_data: - simage = int(global_container_stack.getMetaDataEntry(Constants.SIMAGE)) - Logger.log("d", "mks_simage value: " + str(simage)) - screenshot_string += add_screenshot_str(image, simage, simage, ";simage:") - if Constants.GIMAGE in meta_data: - gimage = int(global_container_stack.getMetaDataEntry(Constants.GIMAGE)) - Logger.log("d", "mks_gimage value: " + str(gimage)) - # ;; - needed for correct colors. do not remove them. - screenshot_string += add_screenshot_str(image, gimage, gimage, ";;gimage:") - screenshot_string += "\r" + simage, gimage, screenshot_string = generate_preview(global_container_stack, image) else: Logger.log("d", "Skipping adding screenshot") return @@ -112,3 +182,4 @@ def add_preview(self): if dict_changed: setattr(scene, "gcode_dict", gcode_dict) + diff --git a/MachineConfig.py b/MachineConfig.py index 0ef5fc6..5a0af51 100644 --- a/MachineConfig.py +++ b/MachineConfig.py @@ -177,6 +177,8 @@ def pluginDisable(self): global_container_stack.removeMetaDataEntry(Constants.SIMAGE) global_container_stack.setMetaDataEntry(Constants.GIMAGE, None) global_container_stack.removeMetaDataEntry(Constants.GIMAGE) + global_container_stack.setMetaDataEntry(Constants.IS_PREVIEW_ENCODED, None) + global_container_stack.removeMetaDataEntry(Constants.IS_PREVIEW_ENCODED) global_container_stack.setMetaDataEntry(Constants.CURRENT_IP, None) global_container_stack.removeMetaDataEntry(Constants.CURRENT_IP) global_container_stack.setMetaDataEntry(Constants.IP_LIST, None) @@ -276,6 +278,26 @@ def supportScreenshot(self): return True return False + @pyqtSlot(result=bool) + def isPreviewEncoded(self): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + meta_data = global_container_stack.getMetaData() + if Constants.IS_PREVIEW_ENCODED in meta_data: + return True + return False + + @pyqtSlot(str) + def setPreviewEncodeSettings(self, is_preview_encoded): + global_container_stack = Application.getInstance().getGlobalContainerStack() + if global_container_stack: + Logger.log("d", "Is preview encoded: "+ str(is_preview_encoded)) + if is_preview_encoded == "true": + global_container_stack.setMetaDataEntry(Constants.IS_PREVIEW_ENCODED, is_preview_encoded) + else: + global_container_stack.setMetaDataEntry(Constants.IS_PREVIEW_ENCODED, None) + global_container_stack.removeMetaDataEntry(Constants.IS_PREVIEW_ENCODED) + @pyqtSlot(result="QVariantList") def getScreenshotOptions(self): Logger.log("d", "Trying to get screenshot options") @@ -290,13 +312,14 @@ def getScreenshotOptions(self): @pyqtSlot(str, result="QVariant") def getScreenshotSettings(self, label): Logger.log("d", "Get screenshot settings for: "+ label) - result = {"simage": "", "gimage": ''} + result = {"simage": "", "gimage": '', "encoded": False} options = sorted(self.screenshot_info, key=lambda k: k['index']) for option in options: value = option["label"] if value == label: result["simage"] = option["simage"] result["gimage"] = option["gimage"] + result["encoded"] = option["encoded"] return result @pyqtSlot(str) diff --git a/config/screenshot.json b/config/screenshot.json index c94a46a..f7645eb 100644 --- a/config/screenshot.json +++ b/config/screenshot.json @@ -1,7 +1,10 @@ [ - { "index": 1, "label": "Default", "simage": "100", "gimage": "200" }, - { "index": 2, "label": "FLSUN QQ-S", "simage": "", "gimage": "" }, - { "index": 3, "label": "Flying Bear Ghost 4S/5", "simage": "50", "gimage": "200" }, - { "index": 4, "label": "Two Trees Sapphire", "simage": "100", "gimage": "200" }, - { "index": 5, "label": "Wanhao D12", "simage": "100", "gimage": "200" } + { "index": 1, "label": "Default", "simage": "100", "gimage": "200", "encoded": false }, + { "index": 2, "label": "FLSUN QQ-S", "simage": "", "gimage": "" , "encoded": false }, + { "index": 3, "label": "Flying Bear Ghost 4S/5", "simage": "50", "gimage": "200", "encoded": false }, + { "index": 4, "label": "Two Trees Sapphire", "simage": "100", "gimage": "200" , "encoded": false }, + { "index": 5, "label": "Wanhao D12", "simage": "100", "gimage": "200" , "encoded": false }, + { "index": 6, "label": "Artillery Sidewinder X3", "simage": "85", "gimage": "230", "mimage": "170", "encoded": true }, + { "index": 7, "label": "Elegoo Neptune2 | NeptureX", "simage": "100", "gimage": "200", "encoded": true }, + { "index": 8, "label": "Elegoo Neptune3+", "simage": "160", "gimage": "200", "encoded": true } ] \ No newline at end of file diff --git a/encoders/ColPicEncoder.py b/encoders/ColPicEncoder.py new file mode 100644 index 0000000..2185e58 --- /dev/null +++ b/encoders/ColPicEncoder.py @@ -0,0 +1,272 @@ +# Copyright (c) 2024 +# MKS Plugin is released under the terms of the AGPLv3 or higher. + + +def ColPic_EncodeStr(from_color16, image_width, image_height, output_data: bytearray, output_max_t_size, colors_max): + qty = 0 + temp = 0 + str_index = 0 + hex_index = 0 + temp_byte_array = bytearray(4) + qty = ColPicEncode(from_color16, image_width, image_height, output_data, output_max_t_size, colors_max) + if qty == 0: + return 0 + temp = 3 - qty % 3 + while temp > 0 and qty < output_max_t_size: + output_data[qty] = 0 + qty += 1 + temp -= 1 + + if qty * 4 / 3 >= output_max_t_size: + return 0 + hex_index = qty + str_index = qty * 4 / 3 + while hex_index > 0: + hex_index -= 3 + str_index -= 4 + temp_byte_array[0] = output_data[hex_index] >> 2 + temp_byte_array[1] = output_data[hex_index] & 3 + temp_byte_array[1] <<= 4 + temp_byte_array[1] += output_data[hex_index + 1] >> 4 + temp_byte_array[2] = output_data[hex_index + 1] & 15 + temp_byte_array[2] <<= 2 + temp_byte_array[2] += output_data[hex_index + 2] >> 6 + temp_byte_array[3] = output_data[hex_index + 2] & 63 + temp_byte_array[0] += 48 + if chr(temp_byte_array[0]) == '\\': + temp_byte_array[0] = 126 + temp_byte_array[1] += 48 + if chr(temp_byte_array[1]) == '\\': + temp_byte_array[1] = 126 + temp_byte_array[2] += 48 + if chr(temp_byte_array[2]) == '\\': + temp_byte_array[2] = 126 + temp_byte_array[3] += 48 + if chr(temp_byte_array[3]) == '\\': + temp_byte_array[3] = 126 + output_data[int(str_index)] = temp_byte_array[0] + output_data[int(str_index) + 1] = temp_byte_array[1] + output_data[int(str_index) + 2] = temp_byte_array[2] + output_data[int(str_index) + 3] = temp_byte_array[3] + + qty = qty * 4 / 3 + output_data[int(qty)] = 0 + return qty + + +def ColPicEncode(from_color16, image_width, image_height, output_data: bytearray, output_max_t_size, colors_max): + l0 = U16HEAD() + Head0 = ColPicHead3() + list_u16 = [] + for i in range(1024): + list_u16.append(U16HEAD()) + + ListQty = 0 + enqty = 0 + dotsqty = image_width * image_height + if colors_max > 1024: + colors_max = 1024 + for i in range(dotsqty): + ListQty = ADList0(from_color16[i], list_u16, ListQty, 1024) + + for index in range(1, ListQty): + l0 = list_u16[index] + for i in range(index): + if l0.qty >= list_u16[i].qty: + alist_u16 = blist_u16 = list_u16.copy() + for j in range(index - i): + list_u16[i + j + 1] = alist_u16[i + j] + + list_u16[i] = l0 + break + + while ListQty > colors_max: + l0 = list_u16[ListQty - 1] + minval = 255 + fid = -1 + for i in range(colors_max): + cha0 = list_u16[i].A0 - l0.A0 + if cha0 < 0: + cha0 = 0 - cha0 + cha1 = list_u16[i].A1 - l0.A1 + if cha1 < 0: + cha1 = 0 - cha1 + cha2 = list_u16[i].A2 - l0.A2 + if cha2 < 0: + cha2 = 0 - cha2 + chall = cha0 + cha1 + cha2 + if chall < minval: + minval = chall + fid = i + + for i in range(dotsqty): + if from_color16[i] == l0.colo16: + from_color16[i] = list_u16[fid].colo16 + + ListQty = ListQty - 1 + + for n in range(len(output_data)): + output_data[n] = 0 + + Head0.encodever = 3 + Head0.oncelistqty = 0 + Head0.mark = 98419516 + Head0.list_data_size = ListQty * 2 + output_data[0] = 3 + output_data[12] = 60 + output_data[13] = 195 + output_data[14] = 221 + output_data[15] = 5 + output_data[16] = ListQty * 2 & 255 + output_data[17] = (ListQty * 2 & 65280) >> 8 + output_data[18] = (ListQty * 2 & 16711680) >> 16 + output_data[19] = (ListQty * 2 & 4278190080) >> 24 + size_of_ColPicHead3 = 32 + for i in range(ListQty): + output_data[size_of_ColPicHead3 + i * 2 + 1] = (list_u16[i].colo16 & 65280) >> 8 + output_data[size_of_ColPicHead3 + i * 2 + 0] = list_u16[i].colo16 & 255 + + enqty = Byte8bitEncode( + from_color16, + size_of_ColPicHead3, + Head0.list_data_size >> 1, + dotsqty, + output_data, + size_of_ColPicHead3 + Head0.list_data_size, + output_max_t_size - size_of_ColPicHead3 - Head0.list_data_size + ) + Head0.color_data_size = enqty + Head0.image_width = image_width + Head0.image_height = image_height + output_data[4] = image_width & 255 + output_data[5] = (image_width & 65280) >> 8 + output_data[6] = (image_width & 16711680) >> 16 + output_data[7] = (image_width & 4278190080) >> 24 + output_data[8] = image_height & 255 + output_data[9] = (image_height & 65280) >> 8 + output_data[10] = (image_height & 16711680) >> 16 + output_data[11] = (image_height & 4278190080) >> 24 + output_data[20] = enqty & 255 + output_data[21] = (enqty & 65280) >> 8 + output_data[22] = (enqty & 16711680) >> 16 + output_data[23] = (enqty & 4278190080) >> 24 + return size_of_ColPicHead3 + Head0.list_data_size + Head0.color_data_size + + +def ADList0(val, list_u16, ListQty, maxqty): + qty = ListQty + if qty >= maxqty: + return ListQty + for i in range(qty): + if list_u16[i].colo16 == val: + list_u16[i].qty += 1 + return ListQty + + A0 = val >> 11 & 31 + A1 = (val & 2016) >> 5 + A2 = val & 31 + list_u16[qty].colo16 = val + list_u16[qty].A0 = A0 + list_u16[qty].A1 = A1 + list_u16[qty].A2 = A2 + list_u16[qty].qty = 1 + ListQty = qty + 1 + return ListQty + + +def Byte8bitEncode(from_color16, list_u16Index, listqty, dotsqty, output_data: bytearray, output_dataIndex, decMaxBytesize): + list_u16 = output_data + dots = 0 + src_index = 0 + dec_index = 0 + last_id = 0 + temp = 0 + while dotsqty > 0: + dots = 1 + for i in range(dotsqty - 1): + if from_color16[src_index + i] != from_color16[src_index + i + 1]: + break + dots += 1 + if dots == 255: + break + + temp = 0 + for i in range(listqty): + aa = list_u16[i * 2 + 1 + list_u16Index] << 8 + aa |= list_u16[i * 2 + 0 + list_u16Index] + if aa == from_color16[src_index]: + temp = i + break + + tid = int(temp % 32) + if tid > 255: + tid = 255 + sid = int(temp / 32) + if sid > 255: + sid = 255 + if last_id != sid: + if dec_index >= decMaxBytesize: + dotsqty = 0 + break + output_data[dec_index + output_dataIndex] = 7 + output_data[dec_index + output_dataIndex] <<= 5 + output_data[dec_index + output_dataIndex] += sid + dec_index += 1 + last_id = sid + if dots <= 6: + if dec_index >= decMaxBytesize: + dotsqty = 0 + break + aa = dots + if aa > 255: + aa = 255 + output_data[dec_index + output_dataIndex] = aa + output_data[dec_index + output_dataIndex] <<= 5 + output_data[dec_index + output_dataIndex] += tid + dec_index += 1 + else: + if dec_index >= decMaxBytesize: + dotsqty = 0 + break + output_data[dec_index + output_dataIndex] = 0 + output_data[dec_index + output_dataIndex] += tid + dec_index += 1 + if dec_index >= decMaxBytesize: + dotsqty = 0 + break + aa = dots + if aa > 255: + aa = 255 + output_data[dec_index + output_dataIndex] = aa + dec_index += 1 + src_index += dots + dotsqty -= dots + + return dec_index + + +class U16HEAD: + + def __init__(self): + self.colo16 = 0 + self.A0 = 0 + self.A1 = 0 + self.A2 = 0 + self.res0 = 0 + self.res1 = 0 + self.qty = 0 + + +class ColPicHead3: + + def __init__(self): + self.encodever = 0 + self.res0 = 0 + self.oncelistqty = 0 + self.image_width = 0 + self.image_height = 0 + self.mark = 0 + self.list_data_size = 0 + self.color_data_size = 0 + self.res1 = 0 + self.res2 = 0 \ No newline at end of file diff --git a/qml/MachineConfig.qml b/qml/MachineConfig.qml index e65aa4a..9ad2558 100644 --- a/qml/MachineConfig.qml +++ b/qml/MachineConfig.qml @@ -378,6 +378,7 @@ Cura.MachineAction { var settings = manager.getScreenshotSettings(currentValue) manager.setSimage(settings.simage) manager.setGimage(settings.gimage) + manager.setPreviewEncodeSettings(settings.encoded) } simageTextInput.text = manager.getSimage() gimageTextInput.text = manager.getGimage()