Skip to content

Commit

Permalink
Add support for cura and prusa slicer thumbnails for octoprint plugin…
Browse files Browse the repository at this point in the history
…s by jneilliii.
  • Loading branch information
FormerLurker committed Oct 16, 2020
1 parent 87d9367 commit 103ad7d
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 49 deletions.
140 changes: 122 additions & 18 deletions octoprint_arc_welder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@
from __future__ import unicode_literals

import time
import datetime
import uuid
import threading
from distutils.version import LooseVersion
from six import string_types
from flask import request, jsonify
import os
import sys
import octoprint.plugin
import tornado
from shutil import copyfile
from octoprint.server.util.tornado import LargeResponseHandler
from octoprint.server import util, app
from octoprint.filemanager import FileDestinations
Expand Down Expand Up @@ -210,10 +213,17 @@ def process_request(self):
if self._enabled:
request_values = request.get_json()
path = request_values["path"]
origin = request_values["origin"]
# decode the path
path = urllibparse.unquote(path)
self.add_file_to_preprocessor_queue(path)
return jsonify({"success": True})
# get the metadata for the file
metadata = self._file_manager.get_metadata(origin, path)
if "arc_welder" not in metadata:
# Extract only the supported metadata from the added file
additional_metadata = self.get_additional_metadata(metadata)
# add the file and metadata to the processor queue
self.add_file_to_preprocessor_queue(path, additional_metadata)
return jsonify({"success": True})
return jsonify({"success": False, "message": "Arc Welder is Disabled."})

@octoprint.plugin.BlueprintPlugin.route("/restoreDefaultSettings", methods=["POST"])
Expand Down Expand Up @@ -409,7 +419,7 @@ def get_preprocessor_arguments(self, source_path_on_disk):
"log_level": self._gcode_conversion_log_level
}

def save_preprocessed_file(self, path, preprocessor_args, results):
def save_preprocessed_file(self, path, preprocessor_args, results, additional_metadata):
# get the file name and path
new_path, new_name = self.get_storage_path_and_name(
path, not self._overwrite_source_file
Expand Down Expand Up @@ -453,6 +463,7 @@ def save_preprocessed_file(self, path, preprocessor_args, results):
"target_filename": new_name,
"preprocessing_job_guid": self.preprocessing_job_guid
}

self._file_manager.set_additional_metadata(
FileDestinations.LOCAL,
new_path,
Expand All @@ -462,8 +473,91 @@ def save_preprocessed_file(self, path, preprocessor_args, results):
merge=False
)

# Add compatibility for ultimaker thumbnail package
has_ultimaker_format_package_thumbnail = (
"thumbnail" in additional_metadata
and isinstance(additional_metadata['thumbnail'], string_types)
and additional_metadata['thumbnail'].startswith('plugin/UltimakerFormatPackage/thumbnail/')
)
# Add compatibility for PrusaSlicer thumbnail package
has_prusa_slicer_thumbnail = (
"thumbnail" in additional_metadata
and isinstance(additional_metadata['thumbnail'], string_types)
and additional_metadata['thumbnail'].startswith('plugin/prusaslicerthumbnails/thumbnail/')
)

# delete the thumbnail src element if it exists, we will add it later if necessary
if "thumbnail_src" in additional_metadata:
del additional_metadata["thumbnail_src"]

if has_ultimaker_format_package_thumbnail and not "thumbnail_src" in additional_metadata:
additional_metadata["thumbnail_src"] = "UltimakerFormatPackage"
elif has_prusa_slicer_thumbnail and not "thumbnail_src" in additional_metadata:
additional_metadata["thumbnail_src"] = "prusaslicerthumbnails"

# add the additional metadata
if "thumbnail" in additional_metadata:
current_path = additional_metadata["thumbnail"]
thumbnail_src = None
thumbnail = None
if has_ultimaker_format_package_thumbnail:
thumbnail_src = "UltimakerFormatPackage"
elif has_prusa_slicer_thumbnail:
thumbnail_src = "prusaslicerthumbnails"

if thumbnail_src is not None:
thumbnail = self.copy_thumbnail(thumbnail_src, current_path, new_name)
# set the thumbnail path and src. It will not be copied to the final metadata if the value is none
additional_metadata["thumbnail"] = thumbnail
additional_metadata["thumbnail_src"] = thumbnail_src

# add all the metadata items
for key, value in additional_metadata.items():
if value is not None:
self._file_manager.set_additional_metadata(
FileDestinations.LOCAL,
new_path,
key,
value,
overwrite=True,
merge=False
)

return new_path, new_name, metadata

def copy_thumbnail(self, thumbnail_src, thumbnail_path, gcode_filename):
# get the plugin implementation
plugin_implementation = self._plugin_manager.get_plugin_info(thumbnail_src, True)
if plugin_implementation:
thumbnail_uri_root = 'plugin/' + thumbnail_src + '/thumbnail/'
data_folder = plugin_implementation.implementation.get_plugin_data_folder()
# extract the file name from the path
path = thumbnail_path.replace(thumbnail_uri_root, '')
querystring_index = path.rfind('?')
if querystring_index > -1:
path = path[0: querystring_index]

path = os.path.join(data_folder, path)
# see if the thumbnail exists
if os.path.isfile(path):
# create a new path
pre, ext = os.path.splitext(gcode_filename)
new_thumb_name = pre + ".png"
new_path = os.path.join(data_folder, new_thumb_name)
new_metadata = (
thumbnail_uri_root + new_thumb_name + "?" + "{:%Y%m%d%H%M%S}".format(
datetime.datetime.now()
)
)
if path != new_path:
try:
copyfile(path, new_path)
except (IOError, OSError) as e:
logger.exception("An error occurred copying thumbnail from '%s' to '%s'", path, new_path)
new_metadata = None
return new_metadata
return None

def preprocessing_started(self, path, preprocessor_args):
new_path, new_name = self.get_storage_path_and_name(
path, not self._overwrite_source_file
Expand Down Expand Up @@ -539,11 +633,11 @@ def preprocessing_cancelled(self, path, preprocessor_args):
}
self._plugin_manager.send_plugin_message(self._identifier, data)

def preprocessing_success(self, results, path, preprocessor_args):
def preprocessing_success(self, results, path, preprocessor_args, additional_metadata):
# save the newly created file. This must be done before
# exiting this callback because the target file isn't
# guaranteed to exist later.
new_path, new_name, metadata = self.save_preprocessed_file(path, preprocessor_args, results)
new_path, new_name, metadata = self.save_preprocessed_file(path, preprocessor_args, results, additional_metadata)
if self._show_completed_notification:
data = {
"message_type": "preprocessing-success",
Expand Down Expand Up @@ -575,14 +669,14 @@ def preprocessing_failed(self, message):

def on_event(self, event, payload):

if event == Events.UPLOAD:
# Need to use file added event to catch uploads and other non-upload methods of adding a file.
if event == Events.FILE_ADDED:
if not self._enabled or not self._auto_pre_processing_enabled:
return

#storage = payload["storage"]
# Note, 'target' is the key for FILE_UPLOADED, but 'storage' is the key for FILE_ADDED
target = payload["storage"]
path = payload["path"]
name = payload["name"]
target = payload["target"]

if path == self.preprocessing_job_source_file_path or name == self.preprocessing_job_target_file_name:
return
Expand All @@ -598,28 +692,38 @@ def on_event(self, event, payload):
metadata = self._file_manager.get_metadata(target, path)
if "arc_welder" in metadata:
return

self.add_file_to_preprocessor_queue(path)

def add_file_to_preprocessor_queue(self, path):
# Extract only the supported metadata from the added file
additional_metadata = self.get_additional_metadata(metadata)
# Add this file to the processor queue.
self.add_file_to_preprocessor_queue(path, additional_metadata)

def get_additional_metadata(self, metadata):
# list of supported metadata
supported_metadata_keys = ['thumbnail', 'thumbnail_src']
additional_metadata = {}
# Create the additional metadata from the supported keys
for key in supported_metadata_keys:
if key in metadata:
additional_metadata[key] = metadata[key]
return additional_metadata

def add_file_to_preprocessor_queue(self, path, additional_metadata):
# get the file by path
# file = self._file_manager.get_file(FileDestinations.LOCAL, path)
if self._printer.is_printing():
self.send_notification_toast(
"error", "Arc-Welder: Unable to Process",
"warning", "Arc-Welder: Unable to Process",
"Cannot preprocess gcode while a print is in progress because print quality may be affected. The "
"gcode will be processed as soon as the print has completed.",
False,
True,
key="unable_to_process", close_keys=["unable_to_process"]
)
return

logger.info("Received a new gcode file for processing. FileName: %s.", path)

#self.is_cancelled = False
path_on_disk = self._file_manager.path_on_disk(FileDestinations.LOCAL, path)
preprocessor_args = self.get_preprocessor_arguments(path_on_disk)
self._processing_queue.put((path, preprocessor_args))
self._processing_queue.put((path, preprocessor_args, additional_metadata))

def register_custom_routes(self, server_routes, *args, **kwargs):
# version specific permission validator
Expand Down
13 changes: 9 additions & 4 deletions octoprint_arc_welder/preprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,18 @@ def run(self):
try:
# see if there are any rendering tasks.
time.sleep(self._idle_sleep_seconds)

if not self._task_queue.empty():
# add an additional sleep in case this file was uploaded
# from cura to give the printer state a chance to catch up.
time.sleep(0.1)
if self._is_printing_callback():
continue

path, processor_args = self._task_queue.get(False)
path, processor_args, additional_metadata = self._task_queue.get(False)
success = False
try:
self._process(path, processor_args)
self._process(path, processor_args, additional_metadata)
except Exception as e:
logger.exception("An unhandled exception occurred while preprocessing the gcode file.")
message = "An error occurred while preprocessing {0}. Check plugin_arc_welder.log for details.".\
Expand All @@ -108,7 +113,7 @@ def run(self):
except queue.Empty:
pass

def _process(self, path, processor_args):
def _process(self, path, processor_args, additional_metadata):
self._start_callback(path, processor_args)
logger.info(
"Copying source gcode file at %s to %s for processing.", processor_args["path"], self._source_file_path
Expand Down Expand Up @@ -158,7 +163,7 @@ def _process(self, path, processor_args):
elif encoded_results["success"]:
logger.info("Preprocessing of %s completed.", processor_args["path"])
# Save the produced gcode file
self._success_callback(encoded_results, path, processor_args)
self._success_callback(encoded_results, path, processor_args, additional_metadata)
else:
self._failed_callback(encoded_results["message"])

Expand Down
54 changes: 27 additions & 27 deletions octoprint_arc_welder/static/js/arc_welder.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,33 +453,33 @@ $(function () {
file_name_html + "<div><strong>Time:</strong> " + ArcWelder.ToTimer(seconds_elapsed) + "</div><div class='row-fluid'><span class='span5'><strong>Arcs Created</strong><br/>" + arcs_created.toString() + "</span>"
+ "<span class='span7'><strong>Points Compressed</strong><br/>" + points_compressed.toString() + "</span></div>"
+ "<div class='row-fluid'><div class='span5'><strong>Compression</strong><br/> Ratio: " + compression_ratio.toFixed(1) + " - " + compression_percent.toFixed(1) + "%</div><div class='span7'><strong>Space</strong><br/>"+ source_size_string + " - " + space_saved_string + " = " + target_size_string + "</div></div>";
var options = {
title: "Arc Welder Preprocessing Complete",
text: progress_text,
type: "success",
hide: false,
addclass: "arc-welder",
var options = {
title: "Arc Welder Preprocessing Complete",
text: progress_text,
type: "success",
hide: true,
addclass: "arc-welder",

};
PNotifyExtensions.displayPopupForKey(options, ArcWelder.PopupKey("preprocessing-success"));
progress_text =
"Preprocessing completed in " + ArcWelder.ToTimer(seconds_elapsed) + " seconds. " + arcs_created.toString() + " arcs were created and "
+ points_compressed.toString() + " points were compressed.";
options = {
title: "Arc Welder Preprocessing Complete",
text: "Preprocessing Completed",
type: "success",
hide: false,
desktop: {
desktop: true,
fallback: false
}
};
PNotifyExtensions.displayPopupForKey(
options,
ArcWelder.PopupKey("preprocessing-success-desktop"),
[]
);
};
PNotifyExtensions.displayPopupForKey(options, ArcWelder.PopupKey("preprocessing-success"));
progress_text =
"Preprocessing completed in " + ArcWelder.ToTimer(seconds_elapsed) + " seconds. " + arcs_created.toString() + " arcs were created and "
+ points_compressed.toString() + " points were compressed.";
options = {
title: "Arc Welder Preprocessing Complete",
text: "Preprocessing Completed",
type: "success",
hide: false,
desktop: {
desktop: true,
fallback: false
}
};
PNotifyExtensions.displayPopupForKey(
options,
ArcWelder.PopupKey("preprocessing-success-desktop"),
[]
);
break;
case "preprocessing-complete":
self.closePreprocessingPopup();
Expand Down Expand Up @@ -727,7 +727,7 @@ $(function () {
// disable the element
$(event.target).addClass("disabled");
// Request that the file be processed
var data = { "path": encodeURI(file_data.path)};
var data = { "path": encodeURI(file_data.path), "origin": file_data.origin};
$.ajax({
url: ArcWelder.APIURL("process"),
type: "POST",
Expand Down

0 comments on commit 103ad7d

Please sign in to comment.