def printerSdCommand(): if not settings().getBoolean(["feature", "sdSupport"]): return make_response("SD support is disabled", 404) if not printer.is_operational() or printer.is_printing() or printer.is_paused(): return make_response("Printer is not operational or currently busy", 409) valid_commands = { "init": [], "refresh": [], "release": [] } command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response tags = {"source:api", "api:printer.sd"} if command == "init": printer.init_sd_card(tags=tags) elif command == "refresh": printer.refresh_sd_files(tags=tags) elif command == "release": printer.release_sd_card(tags=tags) return NO_CONTENT
def controlJob(): if not printer.is_operational(): return make_response("Printer is not operational", 409) valid_commands = { "start": [], "restart": [], "pause": [], "cancel": [] } command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response activePrintjob = printer.is_printing() or printer.is_paused() if command == "start": if activePrintjob: return make_response("Printer already has an active print job, did you mean 'restart'?", 409) printer.start_print() elif command == "restart": if not printer.is_paused(): return make_response("Printer does not have an active print job or is not paused", 409) printer.start_print() elif command == "pause": if not activePrintjob: return make_response("Printer is neither printing nor paused, 'pause' command cannot be performed", 409) printer.toggle_pause_print() elif command == "cancel": if not activePrintjob: return make_response("Printer is neither printing nor paused, 'cancel' command cannot be performed", 409) printer.cancel_print() return NO_CONTENT
def controlTeste(): if not printer.is_operational(): return make_response("Printer is not operational", 409) activePrintjob = printer.is_printing() or printer.is_paused() printer.cancel_teste()
def deleteGcodeFile(filename, target): if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) if not _verifyFileExists(target, filename): return make_response("File not found on '%s': %s" % (target, filename), 404) # prohibit deleting files that are currently in use currentOrigin, currentFilename = _getCurrentFile() if currentFilename == filename and currentOrigin == target and (printer.is_printing() or printer.is_paused()): make_response("Trying to delete file that is currently being printed: %s" % filename, 409) if (target, filename) in fileManager.get_busy_files(): make_response("Trying to delete a file that is currently in use: %s" % filename, 409) # deselect the file if it's currently selected if currentFilename is not None and filename == currentFilename: printer.unselect_file() # delete it if target == FileDestinations.SDCARD: printer.delete_sd_file(filename) else: fileManager.remove_file(target, filename) return NO_CONTENT
def deleteGcodeFile(filename, target): if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) if not _verifyFileExists(target, filename): return make_response("File not found on '%s': %s" % (target, filename), 404) # prohibit deleting files that are currently in use currentOrigin, currentFilename = _getCurrentFile() if currentFilename == filename and currentOrigin == target and ( printer.is_printing() or printer.is_paused()): make_response( "Trying to delete file that is currently being printed: %s" % filename, 409) if (target, filename) in fileManager.get_busy_files(): make_response( "Trying to delete a file that is currently in use: %s" % filename, 409) # deselect the file if it's currently selected if currentFilename is not None and filename == currentFilename: printer.unselect_file() # delete it if target == FileDestinations.SDCARD: printer.delete_sd_file(filename) else: fileManager.remove_file(target, filename) return NO_CONTENT
def controlJob(): if not printer.is_operational(): abort(409, description="Printer is not operational") valid_commands = {"start": [], "restart": [], "pause": [], "cancel": []} command, data, response = get_json_command_from_request( request, valid_commands) if response is not None: return response activePrintjob = printer.is_printing() or printer.is_paused() tags = {"source:api", "api:job"} user = current_user.get_name() with Permissions.PRINT.require(403): if command == "start": if activePrintjob: abort( 409, description= "Printer already has an active print job, did you mean 'restart'?", ) printer.start_print(tags=tags, user=user) elif command == "restart": if not printer.is_paused(): abort( 409, description= "Printer does not have an active print job or is not paused", ) printer.start_print(tags=tags, user=user) elif command == "pause": if not activePrintjob: abort( 409, description= "Printer is neither printing nor paused, 'pause' command cannot be performed", ) action = data.get("action", "toggle") if action == "toggle": printer.toggle_pause_print(tags=tags, user=user) elif action == "pause": printer.pause_print(tags=tags, user=user) elif action == "resume": printer.resume_print(tags=tags, user=user) else: abort(400, description="Unknown action") elif command == "cancel": if not activePrintjob: abort( 409, description= "Printer is neither printing nor paused, 'cancel' command cannot be performed", ) printer.cancel_print(tags=tags, user=user) return NO_CONTENT
def printerPrintheadCommand(): valid_commands = {"jog": [], "home": ["axes"], "feedrate": ["factor"]} command, data, response = get_json_command_from_request( request, valid_commands) if response is not None: return response if not printer.is_operational() or (printer.is_printing() and command != "feedrate"): # do not jog when a print job is running or we don't have a connection return make_response( "Printer is not operational or currently printing", 409) valid_axes = ["x", "y", "z"] ##~~ jog command if command == "jog": # validate all jog instructions, make sure that the values are numbers validated_values = {} for axis in valid_axes: if axis in data: value = data[axis] if not isinstance(value, (int, float)): return make_response( "Not a number for axis %s: %r" % (axis, value), 400) validated_values[axis] = value absolute = "absolute" in data and data[ "absolute"] in valid_boolean_trues speed = data.get("speed", None) # execute the jog commands printer.jog(validated_values, relative=not absolute, speed=speed) ##~~ home command elif command == "home": validated_values = [] axes = data["axes"] for axis in axes: if not axis in valid_axes: return make_response("Invalid axis: %s" % axis, 400) validated_values.append(axis) # execute the home command printer.home(validated_values) elif command == "feedrate": factor = data["factor"] if not isinstance(factor, (int, float)): return make_response("Not a number for feed rate: %r" % factor, 400) try: printer.feed_rate(factor) except ValueError as e: return make_response("Invalid value for feed rate: %s" % str(e), 400) return NO_CONTENT
def controlJob(): if not printer.is_operational(): return make_response("Printer is not operational", 409) valid_commands = {"start": [], "restart": [], "pause": [], "cancel": []} command, data, response = get_json_command_from_request( request, valid_commands) if response is not None: return response activePrintjob = printer.is_printing() or printer.is_paused() tags = {"source:api", "api:job"} user = current_user.get_name() with Permissions.PRINT.require(403): if command == "start": if activePrintjob: return make_response( "Printer already has an active print job, did you mean 'restart'?", 409) printer.start_print(tags=tags, user=user) elif command == "restart": if not printer.is_paused(): return make_response( "Printer does not have an active print job or is not paused", 409) printer.start_print(tags=tags, user=user) elif command == "pause": if not activePrintjob: return make_response( "Printer is neither printing nor paused, 'pause' command cannot be performed", 409) action = data.get("action", "toggle") if action == "toggle": printer.toggle_pause_print(tags=tags, user=user) elif action == "pause": printer.pause_print(tags=tags, user=user) elif action == "resume": printer.resume_print(tags=tags, user=user) else: return make_response( "Unknown action '{}', allowed values for action parameter are 'pause', 'resume' and 'toggle'" .format(action), 400) # Safety first, everyone can cancel a print, but log who canceled the print # to prevent fraudulent use if command == "cancel": if not activePrintjob: return make_response( "Printer is neither printing nor paused, 'cancel' command cannot be performed", 409) printer.cancel_print(tags=tags, user=user) return NO_CONTENT
def printerPrintheadCommand(): valid_commands = { "jog": [], "home": ["axes"], "feedrate": ["factor"] } command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response if not printer.is_operational() or (printer.is_printing() and command != "feedrate"): # do not jog when a print job is running or we don't have a connection return make_response("Printer is not operational or currently printing", 409) tags = {"source:api", "api:printer.printhead"} valid_axes = ["x", "y", "z"] ##~~ jog command if command == "jog": # validate all jog instructions, make sure that the values are numbers validated_values = {} for axis in valid_axes: if axis in data: value = data[axis] if not isinstance(value, (int, long, float)): return make_response("Not a number for axis %s: %r" % (axis, value), 400) validated_values[axis] = value absolute = "absolute" in data and data["absolute"] in valid_boolean_trues speed = data.get("speed", None) # execute the jog commands printer.jog(validated_values, relative=not absolute, speed=speed, tags=tags) ##~~ home command elif command == "home": validated_values = [] axes = data["axes"] for axis in axes: if not axis in valid_axes: return make_response("Invalid axis: %s" % axis, 400) validated_values.append(axis) # execute the home command printer.home(validated_values, tags=tags) elif command == "feedrate": factor = data["factor"] if not isinstance(factor, (int, long, float)): return make_response("Not a number for feed rate: %r" % factor, 400) try: printer.feed_rate(factor, tags=tags) except ValueError as e: return make_response("Invalid value for feed rate: %s" % str(e), 400) return NO_CONTENT
def _isBusy(target, path): currentOrigin, currentPath = _getCurrentFile() if currentPath is not None and currentOrigin == target and fileManager.file_in_path( FileDestinations.LOCAL, path, currentPath) and (printer.is_printing() or printer.is_paused()): return True return any(target == x[0] and fileManager.file_in_path(FileDestinations.LOCAL, path, x[1]) for x in fileManager.get_busy_files())
def printerPrintheadCommand(): valid_commands = {"jog": [], "home": ["axes"], "feedrate": ["factor"]} command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response if not printer.is_operational() or (printer.is_printing() and command != "feedrate"): # do not jog when a print job is running or we don't have a connection abort(409, description="Printer is not operational or currently printing") tags = {"source:api", "api:printer.printhead"} valid_axes = ["x", "y", "z"] ##~~ jog command if command == "jog": # validate all jog instructions, make sure that the values are numbers validated_values = {} for axis in valid_axes: if axis in data: value = data[axis] if not isinstance(value, (int, long, float)): abort(400, description="axis value is invalid") validated_values[axis] = value absolute = "absolute" in data and data["absolute"] in valid_boolean_trues speed = data.get("speed", None) # execute the jog commands printer.jog(validated_values, relative=not absolute, speed=speed, tags=tags) ##~~ home command elif command == "home": validated_values = [] axes = data["axes"] for axis in axes: if axis not in valid_axes: abort(400, description="axis is invalid") validated_values.append(axis) # execute the home command printer.home(validated_values, tags=tags) elif command == "feedrate": factor = data["factor"] if not isinstance(factor, (int, long, float)): abort(400, description="factor is invalid") try: printer.feed_rate(factor, tags=tags) except ValueError: abort(400, description="factor is invalid") return NO_CONTENT
def controlJob(): if not printer.is_operational(): return make_response("Printer is not operational", 409) valid_commands = { "start": [], "restart": [], "pause": [], "cancel": [], "shutdown": [] } command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response activePrintjob = printer.is_printing() or printer.is_paused() or printer.is_preparing_print() or printer.is_shutdown() if command == "start": if activePrintjob: return make_response("Printer already has an active print job, did you mean 'restart'?", 409) printer.start_print() elif command == "restart": if not printer.is_paused(): return make_response("Printer does not have an active print job or is not paused", 409) printer.start_print() elif command == "pause": if not activePrintjob: return make_response("Printer is neither printing nor paused, 'pause' command cannot be performed", 409) action = data.get("action", "toggle") if action == "toggle": printer.toggle_pause_print() elif action == "pause": printer.pause_print() elif action == "resume": printer.resume_print() else: return make_response("Unknown action '{}', allowed values for action parameter are 'pause', 'resume' and 'toggle'".format(action), 400) elif command == "cancel": if not activePrintjob: printer.unselect_file() else: printer.cancel_print() elif command == "shutdown": if not printer.is_paused(): return make_response("Printer does not have an active print job or is not paused", 409) printer.enter_shutdown_mode() return NO_CONTENT
def controlJob(): if not printer.is_operational(): return make_response("Printer is not operational", 409) valid_commands = {"start": [], "restart": [], "pause": [], "cancel": []} command, data, response = get_json_command_from_request( request, valid_commands) if response is not None: return response activePrintjob = printer.is_printing() or printer.is_paused() if command == "start": if activePrintjob: return make_response( "Printer already has an active print job, did you mean 'restart'?", 409) printer.start_print() elif command == "restart": if not printer.is_paused(): return make_response( "Printer does not have an active print job or is not paused", 409) printer.start_print() elif command == "pause": if not activePrintjob: return make_response( "Printer is neither printing nor paused, 'pause' command cannot be performed", 409) action = data.get("action", "toggle") if action == "toggle": printer.toggle_pause_print() elif action == "pause": printer.pause_print() elif action == "resume": printer.resume_print() else: return make_response( "Unknown action '{}', allowed values for action parameter are 'pause', 'resume' and 'toggle'" .format(action), 400) elif command == "cancel": if not activePrintjob: return make_response( "Printer is neither printing nor paused, 'cancel' command cannot be performed", 409) printer.cancel_print() return NO_CONTENT
def processUnrenderedTimelapseCommand(name): # valid file commands, dict mapping command name to mandatory parameters valid_commands = { "render": [] } command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response if command == "render": if printer.is_printing() or printer.is_paused(): return make_response("Printer is currently printing, cannot render timelapse", 409) octoprint.timelapse.render_unrendered_timelapse(name) return NO_CONTENT
def processUnrenderedTimelapseCommand(name): # valid file commands, dict mapping command name to mandatory parameters valid_commands = {"render": []} command, data, response = get_json_command_from_request( request, valid_commands) if response is not None: return response if command == "render": if printer.is_printing() or printer.is_paused(): return make_response( "Printer is currently printing, cannot render timelapse", 409) octoprint.timelapse.render_unrendered_timelapse(name) return NO_CONTENT
def _isBusy(target, path): currentOrigin, currentPath = _getCurrentFile() if currentPath is not None and currentOrigin == target and fileManager.file_in_path(FileDestinations.LOCAL, path, currentPath) and (printer.is_printing() or printer.is_paused()): return True return any(target == x[0] and fileManager.file_in_path(FileDestinations.LOCAL, path, x[1]) for x in fileManager.get_busy_files())
def gcodeFileCommand(filename, target): if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) # valid file commands, dict mapping command name to mandatory parameters valid_commands = { "select": [], "slice": [], "analyse": [], "copy": ["destination"], "move": ["destination"] } command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response user = current_user.get_name() if command == "select": if not _verifyFileExists(target, filename): return make_response("File not found on '%s': %s" % (target, filename), 404) # selects/loads a file if not octoprint.filemanager.valid_file_type(filename, type="machinecode"): return make_response("Cannot select {filename} for printing, not a machinecode file".format(**locals()), 415) printAfterLoading = False if "print" in data.keys() and data["print"] in valid_boolean_trues: if not printer.is_operational(): return make_response("Printer is not operational, cannot directly start printing", 409) printAfterLoading = True sd = False if target == FileDestinations.SDCARD: filenameToSelect = filename sd = True else: filenameToSelect = fileManager.path_on_disk(target, filename) printer.select_file(filenameToSelect, sd, printAfterLoading, user) elif command == "slice": if not _verifyFileExists(target, filename): return make_response("File not found on '%s': %s" % (target, filename), 404) try: if "slicer" in data: slicer = data["slicer"] del data["slicer"] slicer_instance = slicingManager.get_slicer(slicer) elif "cura" in slicingManager.registered_slicers: slicer = "cura" slicer_instance = slicingManager.get_slicer("cura") else: return make_response("Cannot slice {filename}, no slicer available".format(**locals()), 415) except octoprint.slicing.UnknownSlicer as e: return make_response("Slicer {slicer} is not available".format(slicer=e.slicer), 400) if not any([octoprint.filemanager.valid_file_type(filename, type=source_file_type) for source_file_type in slicer_instance.get_slicer_properties().get("source_file_types", ["model"])]): return make_response("Cannot slice {filename}, not a model file".format(**locals()), 415) if slicer_instance.get_slicer_properties().get("same_device", True) and (printer.is_printing() or printer.is_paused()): # slicer runs on same device as OctoPrint, slicing while printing is hence disabled return make_response("Cannot slice on {slicer} while printing due to performance reasons".format(**locals()), 409) if "destination" in data and data["destination"]: destination = data["destination"] del data["destination"] elif "gcode" in data and data["gcode"]: destination = data["gcode"] del data["gcode"] else: import os name, _ = os.path.splitext(filename) destination = name + "." + slicer_instance.get_slicer_properties().get("destination_extensions", ["gco", "gcode", "g"])[0] full_path = destination if "path" in data and data["path"]: full_path = fileManager.join_path(target, data["path"], destination) else: path, _ = fileManager.split_path(target, filename) if path: full_path = fileManager.join_path(target, path, destination) canon_path, canon_name = fileManager.canonicalize(target, full_path) sanitized_name = fileManager.sanitize_name(target, canon_name) if canon_path: full_path = fileManager.join_path(target, canon_path, sanitized_name) else: full_path = sanitized_name # prohibit overwriting the file that is currently being printed currentOrigin, currentFilename = _getCurrentFile() if currentFilename == full_path and currentOrigin == target and (printer.is_printing() or printer.is_paused()): make_response("Trying to slice into file that is currently being printed: %s" % full_path, 409) if "profile" in data.keys() and data["profile"]: profile = data["profile"] del data["profile"] else: profile = None if "printerProfile" in data.keys() and data["printerProfile"]: printerProfile = data["printerProfile"] del data["printerProfile"] else: printerProfile = None if "position" in data.keys() and data["position"] and isinstance(data["position"], dict) and "x" in data["position"] and "y" in data["position"]: position = data["position"] del data["position"] else: position = None select_after_slicing = False if "select" in data.keys() and data["select"] in valid_boolean_trues: if not printer.is_operational(): return make_response("Printer is not operational, cannot directly select for printing", 409) select_after_slicing = True print_after_slicing = False if "print" in data.keys() and data["print"] in valid_boolean_trues: if not printer.is_operational(): return make_response("Printer is not operational, cannot directly start printing", 409) select_after_slicing = print_after_slicing = True override_keys = [k for k in data if k.startswith("profile.") and data[k] is not None] overrides = dict() for key in override_keys: overrides[key[len("profile."):]] = data[key] def slicing_done(target, path, select_after_slicing, print_after_slicing): if select_after_slicing or print_after_slicing: sd = False if target == FileDestinations.SDCARD: filenameToSelect = path sd = True else: filenameToSelect = fileManager.path_on_disk(target, path) printer.select_file(filenameToSelect, sd, print_after_slicing, user) try: fileManager.slice(slicer, target, filename, target, full_path, profile=profile, printer_profile_id=printerProfile, position=position, overrides=overrides, display=canon_name, callback=slicing_done, callback_args=(target, full_path, select_after_slicing, print_after_slicing)) except octoprint.slicing.UnknownProfile: return make_response("Profile {profile} doesn't exist".format(**locals()), 400) files = {} location = url_for(".readGcodeFile", target=target, filename=full_path, _external=True) result = { "name": sanitized_name, "path": full_path, "display": canon_name, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + target + "/" + full_path } } r = make_response(jsonify(result), 202) r.headers["Location"] = location return r elif command == "analyse": if not _verifyFileExists(target, filename): return make_response("File not found on '%s': %s" % (target, filename), 404) printer_profile = None if "printerProfile" in data and data["printerProfile"]: printer_profile = data["printerProfile"] if not fileManager.analyse(target, filename, printer_profile_id=printer_profile): return make_response("No analysis possible for {} on {}".format(filename, target), 400) elif command == "copy" or command == "move": # Copy and move are only possible on local storage if not target in [FileDestinations.LOCAL]: return make_response("Unsupported target for {}: {}".format(command, target), 400) if not _verifyFileExists(target, filename) and not _verifyFolderExists(target, filename): return make_response("File or folder not found on {}: {}".format(target, filename), 404) path, name = fileManager.split_path(target, filename) destination = data["destination"] dst_path, dst_name = fileManager.split_path(target, destination) sanitized_destination = fileManager.join_path(target, dst_path, fileManager.sanitize_name(target, dst_name)) if _verifyFolderExists(target, destination) and sanitized_destination != filename: # destination is an existing folder and not ourselves (= display rename), we'll assume we are supposed # to move filename to this folder under the same name destination = fileManager.join_path(target, destination, name) is_file = fileManager.file_exists(target, filename) is_folder = fileManager.folder_exists(target, filename) if not (is_file or is_folder): return make_response("{} on {} is neither file or folder, can't {}".format(filename, target, command), 400) if command == "copy": # destination already there? error... if _verifyFileExists(target, destination) or _verifyFolderExists(target, destination): return make_response("File or folder does already exist on {}: {}".format(target, destination), 409) if is_file: fileManager.copy_file(target, filename, destination) else: fileManager.copy_folder(target, filename, destination) elif command == "move": if _isBusy(target, filename): return make_response("Trying to move a file or folder that is currently in use: {}".format(filename), 409) # destination already there AND not ourselves (= display rename)? error... if (_verifyFileExists(target, destination) or _verifyFolderExists(target, destination)) \ and sanitized_destination != filename: return make_response("File or folder does already exist on {}: {}".format(target, destination), 409) # deselect the file if it's currently selected currentOrigin, currentFilename = _getCurrentFile() if currentFilename is not None and filename == currentFilename: printer.unselect_file() if is_file: fileManager.move_file(target, filename, destination) else: fileManager.move_folder(target, filename, destination) location = url_for(".readGcodeFile", target=target, filename=destination, _external=True) result = { "name": name, "path": destination, "origin": FileDestinations.LOCAL, "refs": { "resource": location } } if is_file: result["refs"]["download"] = url_for("index", _external=True) + "downloads/files/" + target + "/" + destination r = make_response(jsonify(result), 201) r.headers["Location"] = location return r return NO_CONTENT
def uploadGcodeFile(target): input_name = "file" input_upload_name = input_name + "." + settings().get(["server", "uploads", "nameSuffix"]) input_upload_path = input_name + "." + settings().get(["server", "uploads", "pathSuffix"]) if input_upload_name in request.values and input_upload_path in request.values: if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) upload = octoprint.filemanager.util.DiskFileWrapper(request.values[input_upload_name], request.values[input_upload_path]) # Store any additional user data the caller may have passed. userdata = None if "userdata" in request.values: import json try: userdata = json.loads(request.values["userdata"]) except: return make_response("userdata contains invalid JSON", 400) if target == FileDestinations.SDCARD and not settings().getBoolean(["feature", "sdSupport"]): return make_response("SD card support is disabled", 404) sd = target == FileDestinations.SDCARD selectAfterUpload = "select" in request.values.keys() and request.values["select"] in valid_boolean_trues printAfterSelect = "print" in request.values.keys() and request.values["print"] in valid_boolean_trues if sd: # validate that all preconditions for SD upload are met before attempting it if not (printer.is_operational() and not (printer.is_printing() or printer.is_paused())): return make_response("Can not upload to SD card, printer is either not operational or already busy", 409) if not printer.is_sd_ready(): return make_response("Can not upload to SD card, not yet initialized", 409) # determine future filename of file to be uploaded, abort if it can't be uploaded try: # FileDestinations.LOCAL = should normally be target, but can't because SDCard handling isn't implemented yet canonPath, canonFilename = fileManager.canonicalize(FileDestinations.LOCAL, upload.filename) futurePath = fileManager.sanitize_path(FileDestinations.LOCAL, canonPath) futureFilename = fileManager.sanitize_name(FileDestinations.LOCAL, canonFilename) except: canonFilename = None futurePath = None futureFilename = None if futureFilename is None: return make_response("Can not upload file %s, wrong format?" % upload.filename, 415) if "path" in request.values and request.values["path"]: # we currently only support uploads to sdcard via local, so first target is local instead of "target" futurePath = fileManager.sanitize_path(FileDestinations.LOCAL, request.values["path"]) # prohibit overwriting currently selected file while it's being printed futureFullPath = fileManager.join_path(FileDestinations.LOCAL, futurePath, futureFilename) futureFullPathInStorage = fileManager.path_in_storage(FileDestinations.LOCAL, futureFullPath) if not printer.can_modify_file(futureFullPathInStorage, sd): return make_response("Trying to overwrite file that is currently being printed: %s" % futureFullPath, 409) reselect = printer.is_current_file(futureFullPathInStorage, sd) user = current_user.get_name() def fileProcessingFinished(filename, absFilename, destination): """ Callback for when the file processing (upload, optional slicing, addition to analysis queue) has finished. Depending on the file's destination triggers either streaming to SD card or directly calls selectAndOrPrint. """ if destination == FileDestinations.SDCARD and octoprint.filemanager.valid_file_type(filename, "machinecode"): return filename, printer.add_sd_file(filename, absFilename, selectAndOrPrint, tags={"source:api", "api:files.sd"}) else: selectAndOrPrint(filename, absFilename, destination) return filename def selectAndOrPrint(filename, absFilename, destination): """ Callback for when the file is ready to be selected and optionally printed. For SD file uploads this is only the case after they have finished streaming to the printer, which is why this callback is also used for the corresponding call to addSdFile. Selects the just uploaded file if either selectAfterUpload or printAfterSelect are True, or if the exact file is already selected, such reloading it. """ if octoprint.filemanager.valid_file_type(added_file, "gcode") and (selectAfterUpload or printAfterSelect or reselect): printer.select_file(absFilename, destination == FileDestinations.SDCARD, printAfterSelect, user) try: added_file = fileManager.add_file(FileDestinations.LOCAL, futureFullPathInStorage, upload, allow_overwrite=True, display=canonFilename) except octoprint.filemanager.storage.StorageError as e: if e.code == octoprint.filemanager.storage.StorageError.INVALID_FILE: return make_response("Could not upload the file \"{}\", invalid type".format(upload.filename), 400) else: return make_response("Could not upload the file \"{}\"".format(upload.filename), 500) if octoprint.filemanager.valid_file_type(added_file, "stl"): filename = added_file done = True else: filename = fileProcessingFinished(added_file, fileManager.path_on_disk(FileDestinations.LOCAL, added_file), target) done = not sd if userdata is not None: # upload included userdata, add this now to the metadata fileManager.set_additional_metadata(FileDestinations.LOCAL, added_file, "userdata", userdata) sdFilename = None if isinstance(filename, tuple): filename, sdFilename = filename eventManager.fire(Events.UPLOAD, {"name": futureFilename, "path": filename, "target": target, # TODO deprecated, remove in 1.4.0 "file": filename}) files = {} location = url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=filename, _external=True) files.update({ FileDestinations.LOCAL: { "name": futureFilename, "path": filename, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + FileDestinations.LOCAL + "/" + filename } } }) if sd and sdFilename: location = url_for(".readGcodeFile", target=FileDestinations.SDCARD, filename=sdFilename, _external=True) files.update({ FileDestinations.SDCARD: { "name": sdFilename, "path": sdFilename, "origin": FileDestinations.SDCARD, "refs": { "resource": location } } }) r = make_response(jsonify(files=files, done=done), 201) r.headers["Location"] = location return r elif "foldername" in request.values: foldername = request.values["foldername"] if not target in [FileDestinations.LOCAL]: return make_response("Unknown target: %s" % target, 400) canonPath, canonName = fileManager.canonicalize(target, foldername) futurePath = fileManager.sanitize_path(target, canonPath) futureName = fileManager.sanitize_name(target, canonName) if not futureName or not futurePath: return make_response("Can't create a folder with an empty name", 400) if "path" in request.values and request.values["path"]: futurePath = fileManager.sanitize_path(FileDestinations.LOCAL, request.values["path"]) futureFullPath = fileManager.join_path(target, futurePath, futureName) if octoprint.filemanager.valid_file_type(futureName): return make_response("Can't create a folder named %s, please try another name" % futureName, 409) try: added_folder = fileManager.add_folder(target, futureFullPath, display=canonName) except octoprint.filemanager.storage.StorageError as e: if e.code == octoprint.filemanager.storage.StorageError.INVALID_DIRECTORY: return make_response("Could not create folder {}, invalid directory".format(futureName)) else: return make_response("Could not create folder {}".format(futureName)) location = url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=added_folder, _external=True) folder = dict(name=futureName, path=added_folder, origin=target, refs=dict(resource=location)) r = make_response(jsonify(folder=folder, done=True), 201) r.headers["Location"] = location return r else: return make_response("No file to upload and no folder to create", 400)
def printerToolCommand(): if not printer.is_operational(): return make_response("Printer is not operational", 409) valid_commands = { "select": ["tool"], "target": ["targets"], "offset": ["offsets"], "extrude": ["amount"], "flowrate": ["factor"] } command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response validation_regex = re.compile("tool\d+") ##~~ tool selection if command == "select": tool = data["tool"] if re.match(validation_regex, tool) is None: return make_response("Invalid tool: %s" % tool, 400) if not tool.startswith("tool"): return make_response("Invalid tool for selection: %s" % tool, 400) printer.change_tool(tool) ##~~ temperature elif command == "target": targets = data["targets"] # make sure the targets are valid and the values are numbers validated_values = {} for tool, value in targets.iteritems(): if re.match(validation_regex, tool) is None: return make_response("Invalid target for setting temperature: %s" % tool, 400) if not isinstance(value, (int, long, float)): return make_response("Not a number for %s: %r" % (tool, value), 400) validated_values[tool] = value # perform the actual temperature commands for tool in validated_values.keys(): printer.set_temperature(tool, validated_values[tool]) ##~~ temperature offset elif command == "offset": offsets = data["offsets"] # make sure the targets are valid, the values are numbers and in the range [-50, 50] validated_values = {} for tool, value in offsets.iteritems(): if re.match(validation_regex, tool) is None: return make_response("Invalid target for setting temperature: %s" % tool, 400) if not isinstance(value, (int, long, float)): return make_response("Not a number for %s: %r" % (tool, value), 400) if not -50 <= value <= 50: return make_response("Offset %s not in range [-50, 50]: %f" % (tool, value), 400) validated_values[tool] = value # set the offsets printer.set_temperature_offset(validated_values) ##~~ extrusion elif command == "extrude": if printer.is_printing(): # do not extrude when a print job is running return make_response("Printer is currently printing", 409) amount = data["amount"] if not isinstance(amount, (int, long, float)): return make_response("Not a number for extrusion amount: %r" % amount, 400) printer.extrude(amount) elif command == "flowrate": factor = data["factor"] if not isinstance(factor, (int, long, float)): return make_response("Not a number for flow rate: %r" % factor, 400) try: printer.flow_rate(factor) except ValueError as e: return make_response("Invalid value for flow rate: %s" % str(e), 400) return NO_CONTENT
def uploadGcodeFile(target): if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) input_name = "file" input_upload_name = input_name + "." + settings().get( ["server", "uploads", "nameSuffix"]) input_upload_path = input_name + "." + settings().get( ["server", "uploads", "pathSuffix"]) if input_upload_name in request.values and input_upload_path in request.values: upload = octoprint.filemanager.util.DiskFileWrapper( request.values[input_upload_name], request.values[input_upload_path]) else: return make_response("No file included", 400) # Store any additional user data the caller may have passed. userdata = None if "userdata" in request.values: import json try: userdata = json.loads(request.values["userdata"]) except: return make_response("userdata contains invalid JSON", 400) if target == FileDestinations.SDCARD and not settings().getBoolean( ["feature", "sdSupport"]): return make_response("SD card support is disabled", 404) sd = target == FileDestinations.SDCARD selectAfterUpload = "select" in request.values.keys( ) and request.values["select"] in valid_boolean_trues printAfterSelect = "print" in request.values.keys( ) and request.values["print"] in valid_boolean_trues if sd: # validate that all preconditions for SD upload are met before attempting it if not (printer.is_operational() and not (printer.is_printing() or printer.is_paused())): return make_response( "Can not upload to SD card, printer is either not operational or already busy", 409) if not printer.is_sd_ready(): return make_response( "Can not upload to SD card, not yet initialized", 409) # determine current job currentFilename = None currentOrigin = None currentJob = printer.get_current_job() if currentJob is not None and "file" in currentJob.keys(): currentJobFile = currentJob["file"] if "name" in currentJobFile.keys() and "origin" in currentJobFile.keys( ): currentFilename = currentJobFile["name"] currentOrigin = currentJobFile["origin"] # determine future filename of file to be uploaded, abort if it can't be uploaded try: futureFilename = fileManager.sanitize_name(FileDestinations.LOCAL, upload.filename) except: futureFilename = None if futureFilename is None: return make_response( "Can not upload file %s, wrong format?" % upload.filename, 415) # prohibit overwriting currently selected file while it's being printed if futureFilename == currentFilename and target == currentOrigin and printer.is_printing( ) or printer.is_paused(): return make_response( "Trying to overwrite file that is currently being printed: %s" % currentFilename, 409) def fileProcessingFinished(filename, absFilename, destination): """ Callback for when the file processing (upload, optional slicing, addition to analysis queue) has finished. Depending on the file's destination triggers either streaming to SD card or directly calls selectAndOrPrint. """ if destination == FileDestinations.SDCARD and octoprint.filemanager.valid_file_type( filename, "gcode"): return filename, printer.add_sd_file(filename, absFilename, selectAndOrPrint) else: selectAndOrPrint(filename, absFilename, destination) return filename def selectAndOrPrint(filename, absFilename, destination): """ Callback for when the file is ready to be selected and optionally printed. For SD file uploads this is only the case after they have finished streaming to the printer, which is why this callback is also used for the corresponding call to addSdFile. Selects the just uploaded file if either selectAfterUpload or printAfterSelect are True, or if the exact file is already selected, such reloading it. """ if octoprint.filemanager.valid_file_type(added_file, "gcode") and ( selectAfterUpload or printAfterSelect or (currentFilename == filename and currentOrigin == destination)): printer.select_file(absFilename, destination == FileDestinations.SDCARD, printAfterSelect) added_file = fileManager.add_file(FileDestinations.LOCAL, upload.filename, upload, allow_overwrite=True) if added_file is None: return make_response("Could not upload the file %s" % upload.filename, 500) if octoprint.filemanager.valid_file_type(added_file, "stl"): filename = added_file done = True else: filename = fileProcessingFinished( added_file, fileManager.path_on_disk(FileDestinations.LOCAL, added_file), target) done = True if userdata is not None: # upload included userdata, add this now to the metadata fileManager.set_additional_metadata(FileDestinations.LOCAL, added_file, "userdata", userdata) sdFilename = None if isinstance(filename, tuple): filename, sdFilename = filename eventManager.fire(Events.UPLOAD, {"file": filename, "target": target}) files = {} location = url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=filename, _external=True) files.update({ FileDestinations.LOCAL: { "name": filename, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + FileDestinations.LOCAL + "/" + filename } } }) if sd and sdFilename: location = url_for(".readGcodeFile", target=FileDestinations.SDCARD, filename=sdFilename, _external=True) files.update({ FileDestinations.SDCARD: { "name": sdFilename, "origin": FileDestinations.SDCARD, "refs": { "resource": location } } }) r = make_response(jsonify(files=files, done=done), 201) r.headers["Location"] = location return r
def gcodeFileCommand(filename, target): if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) if not _verifyFileExists(target, filename): return make_response("File not found on '%s': %s" % (target, filename), 404) # valid file commands, dict mapping command name to mandatory parameters valid_commands = { "select": [], "slice": [] } command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response if command == "select": # selects/loads a file printAfterLoading = False if "print" in data.keys() and data["print"] in valid_boolean_trues: if not printer.is_operational(): return make_response("Printer is not operational, cannot directly start printing", 409) printAfterLoading = True sd = False if target == FileDestinations.SDCARD: filenameToSelect = filename sd = True else: filenameToSelect = fileManager.path_on_disk(target, filename) printer.select_file(filenameToSelect, sd, printAfterLoading) elif command == "slice": try: if "slicer" in data: slicer = data["slicer"] del data["slicer"] slicer_instance = slicingManager.get_slicer(slicer) elif "cura" in slicingManager.registered_slicers: slicer = "cura" slicer_instance = slicingManager.get_slicer("cura") else: return make_response("Cannot slice {filename}, no slicer available".format(**locals()), 415) except octoprint.slicing.UnknownSlicer as e: return make_response("Slicer {slicer} is not available".format(slicer=e.slicer), 400) if not any([octoprint.filemanager.valid_file_type(filename, type=source_file_type) for source_file_type in slicer_instance.get_slicer_properties().get("source_file_types", ["model"])]): return make_response("Cannot slice {filename}, not a model file".format(**locals()), 415) if slicer_instance.get_slicer_properties().get("same_device", True) and (printer.is_printing() or printer.is_paused()): # slicer runs on same device as OctoPrint, slicing while printing is hence disabled return make_response("Cannot slice on {slicer} while printing due to performance reasons".format(**locals()), 409) if "destination" in data and data["destination"]: destination = data["destination"] del data["destination"] elif "gcode" in data and data["gcode"]: destination = data["gcode"] del data["gcode"] else: import os name, _ = os.path.splitext(filename) destination = name + "." + slicer_instance.get_slicer_properties().get("destination_extensions", ["gco", "gcode", "g"])[0] # prohibit overwriting the file that is currently being printed currentOrigin, currentFilename = _getCurrentFile() if currentFilename == destination and currentOrigin == target and (printer.is_printing() or printer.is_paused()): make_response("Trying to slice into file that is currently being printed: %s" % destination, 409) if "profile" in data.keys() and data["profile"]: profile = data["profile"] del data["profile"] else: profile = None if "printerProfile" in data.keys() and data["printerProfile"]: printerProfile = data["printerProfile"] del data["printerProfile"] else: printerProfile = None if "position" in data.keys() and data["position"] and isinstance(data["position"], dict) and "x" in data["position"] and "y" in data["position"]: position = data["position"] del data["position"] else: position = None select_after_slicing = False if "select" in data.keys() and data["select"] in valid_boolean_trues: if not printer.is_operational(): return make_response("Printer is not operational, cannot directly select for printing", 409) select_after_slicing = True print_after_slicing = False if "print" in data.keys() and data["print"] in valid_boolean_trues: if not printer.is_operational(): return make_response("Printer is not operational, cannot directly start printing", 409) select_after_slicing = print_after_slicing = True override_keys = [k for k in data if k.startswith("profile.") and data[k] is not None] overrides = dict() for key in override_keys: overrides[key[len("profile."):]] = data[key] def slicing_done(target, gcode_name, select_after_slicing, print_after_slicing): if select_after_slicing or print_after_slicing: sd = False if target == FileDestinations.SDCARD: filenameToSelect = gcode_name sd = True else: filenameToSelect = fileManager.path_on_disk(target, gcode_name) printer.select_file(filenameToSelect, sd, print_after_slicing) try: fileManager.slice(slicer, target, filename, target, destination, profile=profile, printer_profile_id=printerProfile, position=position, overrides=overrides, callback=slicing_done, callback_args=(target, destination, select_after_slicing, print_after_slicing)) except octoprint.slicing.UnknownProfile: return make_response("Profile {profile} doesn't exist".format(**locals()), 400) files = {} location = url_for(".readGcodeFile", target=target, filename=destination, _external=True) result = { "name": destination, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + target + "/" + destination } } r = make_response(jsonify(result), 202) r.headers["Location"] = location return r return NO_CONTENT
def uploadGcodeFile(target): if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) input_name = "file" input_upload_name = input_name + "." + settings().get(["server", "uploads", "nameSuffix"]) input_upload_path = input_name + "." + settings().get(["server", "uploads", "pathSuffix"]) if input_upload_name in request.values and input_upload_path in request.values: upload = octoprint.filemanager.util.DiskFileWrapper(request.values[input_upload_name], request.values[input_upload_path]) else: return make_response("No file included", 400) # Store any additional user data the caller may have passed. userdata = None if "userdata" in request.values: import json try: userdata = json.loads(request.values["userdata"]) except: return make_response("userdata contains invalid JSON", 400) if target == FileDestinations.SDCARD and not settings().getBoolean(["feature", "sdSupport"]): return make_response("SD card support is disabled", 404) sd = target == FileDestinations.SDCARD selectAfterUpload = "select" in request.values.keys() and request.values["select"] in valid_boolean_trues printAfterSelect = "print" in request.values.keys() and request.values["print"] in valid_boolean_trues if sd: # validate that all preconditions for SD upload are met before attempting it if not (printer.is_operational() and not (printer.is_printing() or printer.is_paused())): return make_response("Can not upload to SD card, printer is either not operational or already busy", 409) if not printer.is_sd_ready(): return make_response("Can not upload to SD card, not yet initialized", 409) # determine current job currentFilename = None currentOrigin = None currentJob = printer.get_current_job() if currentJob is not None and "file" in currentJob.keys(): currentJobFile = currentJob["file"] if "name" in currentJobFile.keys() and "origin" in currentJobFile.keys(): currentFilename = currentJobFile["name"] currentOrigin = currentJobFile["origin"] # determine future filename of file to be uploaded, abort if it can't be uploaded try: futureFilename = fileManager.sanitize_name(FileDestinations.LOCAL, upload.filename) except: futureFilename = None if futureFilename is None: return make_response("Can not upload file %s, wrong format?" % upload.filename, 415) # prohibit overwriting currently selected file while it's being printed if futureFilename == currentFilename and target == currentOrigin and printer.is_printing() or printer.is_paused(): return make_response("Trying to overwrite file that is currently being printed: %s" % currentFilename, 409) def fileProcessingFinished(filename, absFilename, destination): """ Callback for when the file processing (upload, optional slicing, addition to analysis queue) has finished. Depending on the file's destination triggers either streaming to SD card or directly calls selectAndOrPrint. """ if destination == FileDestinations.SDCARD and octoprint.filemanager.valid_file_type(filename, "gcode"): return filename, printer.add_sd_file(filename, absFilename, selectAndOrPrint) else: selectAndOrPrint(filename, absFilename, destination) return filename def selectAndOrPrint(filename, absFilename, destination): """ Callback for when the file is ready to be selected and optionally printed. For SD file uploads this is only the case after they have finished streaming to the printer, which is why this callback is also used for the corresponding call to addSdFile. Selects the just uploaded file if either selectAfterUpload or printAfterSelect are True, or if the exact file is already selected, such reloading it. """ if octoprint.filemanager.valid_file_type(added_file, "gcode") and (selectAfterUpload or printAfterSelect or (currentFilename == filename and currentOrigin == destination)): printer.select_file(absFilename, destination == FileDestinations.SDCARD, printAfterSelect) added_file = fileManager.add_file(FileDestinations.LOCAL, upload.filename, upload, allow_overwrite=True) if added_file is None: return make_response("Could not upload the file %s" % upload.filename, 500) if octoprint.filemanager.valid_file_type(added_file, "stl"): filename = added_file done = True else: filename = fileProcessingFinished(added_file, fileManager.path_on_disk(FileDestinations.LOCAL, added_file), target) done = not sd if userdata is not None: # upload included userdata, add this now to the metadata fileManager.set_additional_metadata(FileDestinations.LOCAL, added_file, "userdata", userdata) sdFilename = None if isinstance(filename, tuple): filename, sdFilename = filename eventManager.fire(Events.UPLOAD, {"file": filename, "target": target}) files = {} location = url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=filename, _external=True) files.update({ FileDestinations.LOCAL: { "name": filename, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + FileDestinations.LOCAL + "/" + filename } } }) if sd and sdFilename: location = url_for(".readGcodeFile", target=FileDestinations.SDCARD, filename=sdFilename, _external=True) files.update({ FileDestinations.SDCARD: { "name": sdFilename, "origin": FileDestinations.SDCARD, "refs": { "resource": location } } }) r = make_response(jsonify(files=files, done=done), 201) r.headers["Location"] = location return r
def gcodeFileCommand(filename, target): if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) if not _verifyFileExists(target, filename): return make_response("File not found on '%s': %s" % (target, filename), 404) # valid file commands, dict mapping command name to mandatory parameters valid_commands = {"select": [], "slice": []} command, data, response = get_json_command_from_request( request, valid_commands) if response is not None: return response if command == "select": # selects/loads a file printAfterLoading = False if "print" in data.keys() and data["print"] in valid_boolean_trues: if not printer.is_operational(): return make_response( "Printer is not operational, cannot directly start printing", 409) printAfterLoading = True sd = False if target == FileDestinations.SDCARD: filenameToSelect = filename sd = True else: filenameToSelect = fileManager.path_on_disk(target, filename) printer.select_file(filenameToSelect, sd, printAfterLoading) elif command == "slice": try: if "slicer" in data: slicer = data["slicer"] del data["slicer"] slicer_instance = slicingManager.get_slicer(slicer) elif "cura" in slicingManager.registered_slicers: slicer = "cura" slicer_instance = slicingManager.get_slicer("cura") else: return make_response( "Cannot slice {filename}, no slicer available".format( **locals()), 415) except octoprint.slicing.UnknownSlicer as e: return make_response( "Slicer {slicer} is not available".format(slicer=e.slicer), 400) if not octoprint.filemanager.valid_file_type(filename, type="stl"): return make_response( "Cannot slice {filename}, not an STL file".format(**locals()), 415) if slicer_instance.get_slicer_properties()["same_device"] and ( printer.is_printing() or printer.is_paused()): # slicer runs on same device as OctoPrint, slicing while printing is hence disabled return make_response( "Cannot slice on {slicer} while printing due to performance reasons" .format(**locals()), 409) if "gcode" in data and data["gcode"]: gcode_name = data["gcode"] del data["gcode"] else: import os name, _ = os.path.splitext(filename) gcode_name = name + ".gco" # prohibit overwriting the file that is currently being printed currentOrigin, currentFilename = _getCurrentFile() if currentFilename == gcode_name and currentOrigin == target and ( printer.is_printing() or printer.is_paused()): make_response( "Trying to slice into file that is currently being printed: %s" % gcode_name, 409) if "profile" in data.keys() and data["profile"]: profile = data["profile"] del data["profile"] else: profile = None ## if "printerProfile" in data.keys() and data["printerProfile"]: printerProfile = data["printerProfile"] del data["printerProfile"] else: printerProfile = None if "position" in data.keys() and data["position"] and isinstance( data["position"], dict) and "x" in data["position"] and "y" in data["position"]: position = data["position"] del data["position"] else: position = None select_after_slicing = False if "select" in data.keys() and data["select"] in valid_boolean_trues: if not printer.is_operational(): return make_response( "Printer is not operational, cannot directly select for printing", 409) select_after_slicing = True print_after_slicing = False if "print" in data.keys() and data["print"] in valid_boolean_trues: if not printer.is_operational(): return make_response( "Printer is not operational, cannot directly start printing", 409) select_after_slicing = print_after_slicing = True override_keys = [ k for k in data if k.startswith("profile.") and data[k] is not None ] overrides = dict() for key in override_keys: overrides[key[len("profile."):]] = data[key] def slicing_done(target, gcode_name, select_after_slicing, print_after_slicing): if select_after_slicing or print_after_slicing: sd = False if target == FileDestinations.SDCARD: filenameToSelect = gcode_name sd = True else: filenameToSelect = fileManager.path_on_disk( target, gcode_name) printer.select_file(filenameToSelect, sd, print_after_slicing) try: fileManager.slice(slicer, target, filename, target, gcode_name, profile=profile, printer_profile_id=printerProfile, position=position, overrides=overrides, callback=slicing_done, callback_args=(target, gcode_name, select_after_slicing, print_after_slicing)) except octoprint.slicing.UnknownProfile: return make_response( "Profile {profile} doesn't exist".format(**locals()), 400) files = {} location = url_for(".readGcodeFile", target=target, filename=gcode_name, _external=True) result = { "name": gcode_name, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + target + "/" + gcode_name } } r = make_response(jsonify(result), 202) r.headers["Location"] = location return r return NO_CONTENT
def printerPrintheadCommand(): valid_commands = { "jog": [], "home": ["axes"], "feedrate": ["factor"], "position": ["x", "y"] } command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response if not (printer.is_operational() or printer.is_locked()) or (printer.is_printing() and command != "feedrate"): # do not jog when a print job is running or we don't have a connection return make_response("Printer is not operational or currently printing", 409) valid_axes = ["x", "y", "z"] ##~~ jog command if command == "jog": # validate all jog instructions, make sure that the values are numbers validated_values = {} for axis in valid_axes: if axis in data: value = data[axis] if not isinstance(value, (int, long, float)): return make_response("Not a number for axis %s: %r" % (axis, value), 400) validated_values[axis] = value # execute the jog commands for axis, value in validated_values.iteritems(): printer.jog(axis, value) ##~~ position command if command == "position": # validate all jog instructions, make sure that the values are numbers validated_values = {} x = data["x"] y = data["y"] if isinstance(x, (int, long, float)) and isinstance(y, (int, long, float)) : printer.position(x, y) else: return make_response("Not a number for axis %s: %r" % (axis, value), 400) ##~~ home command elif command == "home": validated_values = [] axes = data["axes"] for axis in axes: if not axis in valid_axes: return make_response("Invalid axis: %s" % axis, 400) validated_values.append(axis) # execute the home command printer.home(validated_values) elif command == "feedrate": factor = data["factor"] if not isinstance(factor, (int, long, float)): return make_response("Not a number for feed rate: %r" % factor, 400) try: printer.feed_rate(factor) except ValueError as e: return make_response("Invalid value for feed rate: %s" % str(e), 400) return NO_CONTENT
def printerToolCommand(): if not printer.is_operational(): abort(409, description="Printer is not operational") valid_commands = { "select": ["tool"], "target": ["targets"], "offset": ["offsets"], "extrude": ["amount"], "flowrate": ["factor"], } command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response validation_regex = re.compile(r"tool\d+") tags = {"source:api", "api:printer.tool"} ##~~ tool selection if command == "select": tool = data["tool"] if not isinstance(tool, basestring) or re.match(validation_regex, tool) is None: abort(400, description="tool is invalid") printer.change_tool(tool, tags=tags) ##~~ temperature elif command == "target": targets = data["targets"] # make sure the targets are valid and the values are numbers validated_values = {} for tool, value in targets.items(): if re.match(validation_regex, tool) is None: abort(400, description="targets contains invalid tool") if not isinstance(value, (int, long, float)): abort(400, description="targets contains invalid value") validated_values[tool] = value # perform the actual temperature commands for tool in validated_values.keys(): printer.set_temperature(tool, validated_values[tool], tags=tags) ##~~ temperature offset elif command == "offset": offsets = data["offsets"] # make sure the targets are valid, the values are numbers and in the range [-50, 50] validated_values = {} for tool, value in offsets.items(): if re.match(validation_regex, tool) is None: abort(400, description="offsets contains invalid tool") if not isinstance(value, (int, long, float)) or not -50 <= value <= 50: abort(400, description="offsets contains invalid value") validated_values[tool] = value # set the offsets printer.set_temperature_offset(validated_values) ##~~ extrusion elif command == "extrude": if printer.is_printing(): # do not extrude when a print job is running abort(409, description="Printer is currently printing") amount = data["amount"] speed = data.get("speed", None) if not isinstance(amount, (int, long, float)): abort(400, description="amount is invalid") printer.extrude(amount, speed=speed, tags=tags) elif command == "flowrate": factor = data["factor"] if not isinstance(factor, (int, long, float)): abort(400, description="factor is invalid") try: printer.flow_rate(factor, tags=tags) except ValueError: abort(400, description="factor is invalid") return NO_CONTENT
def gcodeConvertCommand(): target = FileDestinations.LOCAL # valid file commands, dict mapping command name to mandatory parameters valid_commands = {"convert": []} command, data, response = get_json_command_from_request( request, valid_commands) if response is not None: return response appendGcodeFiles = data['gcodeFilesToAppend'] del data['gcodeFilesToAppend'] # def appendCallback(location, path, sources, **kwargs): # if '_error' in kwargs: # result = kwargs['_error'] # return make_response("Could not slice: {result}".format(result=result), 500) # else: # output_path = fileManager.path_on_disk(location, path) # # append additioal gcodes # with open(output_path,'ab') as wfd: # for f in sources: # path = fileManager.path_on_disk(f['origin'], f['name']) # wfd.write( "\n; "+ f['name'] + "\n") # # with open(path,'rb') as fd: # shutil.copyfileobj(fd, wfd, 1024*1024*10) # # wfd.write( "\nM05\n") # ensure that the laser is off. # #files = {} # #location = url_for(".readGcodeFile", target=target, filename=gcode_name, _external=True) # #result = { ## "name": gcode_name, ## "origin": FileDestinations.LOCAL, ## "refs": { ## "resource": location, ## "download": url_for("index", _external=True) + "downloads/files/" + target + "/" + gcode_name ## } ## } # # #r = make_response(jsonify(result), 202) # #r.headers["Location"] = location # #return r if command == "convert": # TODO stripping non-ascii is a hack - svg contains lots of non-ascii in <text> tags. Fix this! import re svg = ''.join(i for i in data['svg'] if ord(i) < 128) # strip non-ascii chars like € del data['svg'] import os name, _ = os.path.splitext(data['gcode']) filename = target + "/temp.svg" class Wrapper(object): def __init__(self, filename, content): self.filename = filename self.content = content def save(self, absolute_dest_path): with open(absolute_dest_path, "w") as d: d.write(self.content) d.close() fileObj = Wrapper(filename, svg) fileManager.add_file(target, filename, fileObj, links=None, allow_overwrite=True) slicer = "svgtogcode" slicer_instance = slicingManager.get_slicer(slicer) if slicer_instance.get_slicer_properties()["same_device"] and ( printer.is_printing() or printer.is_paused()): # slicer runs on same device as OctoPrint, slicing while printing is hence disabled return make_response( "Cannot convert while lasering due to performance reasons". format(**locals()), 409) if "gcode" in data.keys() and data["gcode"]: gcode_name = data["gcode"] del data["gcode"] else: import os name, _ = os.path.splitext(filename) gcode_name = name + ".gco" # append number if file exists name, ext = os.path.splitext(gcode_name) i = 1 while (fileManager.file_exists(target, gcode_name)): gcode_name = name + '.' + str(i) + ext i += 1 # prohibit overwriting the file that is currently being printed currentOrigin, currentFilename = _getCurrentFile() if currentFilename == gcode_name and currentOrigin == target and ( printer.is_printing() or printer.is_paused()): make_response( "Trying to slice into file that is currently being printed: %s" % gcode_name, 409) if "profile" in data.keys() and data["profile"]: profile = data["profile"] del data["profile"] else: profile = None ## if "printerProfile" in data.keys() and data["printerProfile"]: printerProfile = data["printerProfile"] del data["printerProfile"] else: printerProfile = None if "position" in data.keys() and data["position"] and isinstance( data["position"], dict) and "x" in data["position"] and "y" in data["position"]: position = data["position"] del data["position"] else: position = None select_after_slicing = False if "select" in data.keys() and data["select"] in valid_boolean_trues: if not printer.is_operational(): return make_response( "Printer is not operational, cannot directly select for printing", 409) select_after_slicing = True print_after_slicing = False if "print" in data.keys() and data["print"] in valid_boolean_trues: if not printer.is_operational(): return make_response( "Printer is not operational, cannot directly start printing", 409) select_after_slicing = print_after_slicing = True override_keys = [ k for k in data if k.startswith("profile.") and data[k] is not None ] overrides = dict() for key in override_keys: overrides[key[len("profile."):]] = data[key] def slicing_done(target, gcode_name, select_after_slicing, print_after_slicing, append_these_files): # append additioal gcodes output_path = fileManager.path_on_disk(target, gcode_name) with open(output_path, 'ab') as wfd: for f in append_these_files: path = fileManager.path_on_disk(f['origin'], f['name']) wfd.write("\n; " + f['name'] + "\n") with open(path, 'rb') as fd: shutil.copyfileobj(fd, wfd, 1024 * 1024 * 10) wfd.write("\nM05\n") # ensure that the laser is off. if select_after_slicing or print_after_slicing: sd = False filenameToSelect = fileManager.path_on_disk(target, gcode_name) printer.select_file(filenameToSelect, sd, True) try: fileManager.slice(slicer, target, filename, target, gcode_name, profile=profile, printer_profile_id=printerProfile, position=position, overrides=overrides, callback=slicing_done, callback_args=[ target, gcode_name, select_after_slicing, print_after_slicing, appendGcodeFiles ]) except octoprint.slicing.UnknownProfile: return make_response( "Profile {profile} doesn't exist".format(**locals()), 400) files = {} location = url_for(".readGcodeFile", target=target, filename=gcode_name, _external=True) result = { "name": gcode_name, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + target + "/" + gcode_name } } r = make_response(jsonify(result), 202) r.headers["Location"] = location return r return NO_CONTENT
def gcodeFileCommand(filename, target): if target not in [FileDestinations.LOCAL, FileDestinations.SDCARD]: abort(404) if not _validate(target, filename): abort(404) # valid file commands, dict mapping command name to mandatory parameters valid_commands = { "select": [], "unselect": [], "slice": [], "analyse": [], "copy": ["destination"], "move": ["destination"], } command, data, response = get_json_command_from_request( request, valid_commands) if response is not None: return response user = current_user.get_name() if command == "select": with Permissions.FILES_SELECT.require(403): if not _verifyFileExists(target, filename): abort(404) # selects/loads a file if not octoprint.filemanager.valid_file_type(filename, type="machinecode"): abort( 415, description= "Cannot select file for printing, not a machinecode file", ) if not printer.is_ready(): abort( 409, description= "Printer is already printing, cannot select a new file", ) printAfterLoading = False if "print" in data and data["print"] in valid_boolean_trues: with Permissions.PRINT.require(403): if not printer.is_operational(): abort( 409, description= "Printer is not operational, cannot directly start printing", ) printAfterLoading = True sd = False if target == FileDestinations.SDCARD: filenameToSelect = filename sd = True else: filenameToSelect = fileManager.path_on_disk(target, filename) printer.select_file(filenameToSelect, sd, printAfterLoading, user) elif command == "unselect": with Permissions.FILES_SELECT.require(403): if not printer.is_ready(): return make_response( "Printer is already printing, cannot unselect current file", 409) _, currentFilename = _getCurrentFile() if currentFilename is None: return make_response( "Cannot unselect current file when there is no file selected", 409) if filename != currentFilename and filename != "current": return make_response( "Only the currently selected file can be unselected", 400) printer.unselect_file() elif command == "slice": with Permissions.SLICE.require(403): if not _verifyFileExists(target, filename): abort(404) try: if "slicer" in data: slicer = data["slicer"] del data["slicer"] slicer_instance = slicingManager.get_slicer(slicer) elif "cura" in slicingManager.registered_slicers: slicer = "cura" slicer_instance = slicingManager.get_slicer("cura") else: abort(415, description="Cannot slice file, no slicer available") except octoprint.slicing.UnknownSlicer: abort(404) if not any([ octoprint.filemanager.valid_file_type( filename, type=source_file_type) for source_file_type in slicer_instance. get_slicer_properties().get("source_file_types", ["model"]) ]): abort(415, description="Cannot slice file, not a model file") cores = psutil.cpu_count() if (slicer_instance.get_slicer_properties().get( "same_device", True) and (printer.is_printing() or printer.is_paused()) and (cores is None or cores < 2)): # slicer runs on same device as OctoPrint, slicing while printing is hence disabled abort( 409, description= "Cannot slice on this slicer while printing on single core systems or systems of unknown core count due to performance reasons", ) if "destination" in data and data["destination"]: destination = data["destination"] del data["destination"] elif "gcode" in data and data["gcode"]: destination = data["gcode"] del data["gcode"] else: import os name, _ = os.path.splitext(filename) destination = ( name + "." + slicer_instance.get_slicer_properties().get( "destination_extensions", ["gco", "gcode", "g"])[0]) full_path = destination if "path" in data and data["path"]: full_path = fileManager.join_path(target, data["path"], destination) else: path, _ = fileManager.split_path(target, filename) if path: full_path = fileManager.join_path(target, path, destination) canon_path, canon_name = fileManager.canonicalize( target, full_path) sanitized_name = fileManager.sanitize_name(target, canon_name) if canon_path: full_path = fileManager.join_path(target, canon_path, sanitized_name) else: full_path = sanitized_name # prohibit overwriting the file that is currently being printed currentOrigin, currentFilename = _getCurrentFile() if (currentFilename == full_path and currentOrigin == target and (printer.is_printing() or printer.is_paused())): abort( 409, description= "Trying to slice into file that is currently being printed", ) if "profile" in data and data["profile"]: profile = data["profile"] del data["profile"] else: profile = None if "printerProfile" in data and data["printerProfile"]: printerProfile = data["printerProfile"] del data["printerProfile"] else: printerProfile = None if ("position" in data and data["position"] and isinstance(data["position"], dict) and "x" in data["position"] and "y" in data["position"]): position = data["position"] del data["position"] else: position = None select_after_slicing = False if "select" in data and data["select"] in valid_boolean_trues: if not printer.is_operational(): abort( 409, description= "Printer is not operational, cannot directly select for printing", ) select_after_slicing = True print_after_slicing = False if "print" in data and data["print"] in valid_boolean_trues: if not printer.is_operational(): abort( 409, description= "Printer is not operational, cannot directly start printing", ) select_after_slicing = print_after_slicing = True override_keys = [ k for k in data if k.startswith("profile.") and data[k] is not None ] overrides = {} for key in override_keys: overrides[key[len("profile."):]] = data[key] def slicing_done(target, path, select_after_slicing, print_after_slicing): if select_after_slicing or print_after_slicing: sd = False if target == FileDestinations.SDCARD: filenameToSelect = path sd = True else: filenameToSelect = fileManager.path_on_disk( target, path) printer.select_file(filenameToSelect, sd, print_after_slicing, user) try: fileManager.slice( slicer, target, filename, target, full_path, profile=profile, printer_profile_id=printerProfile, position=position, overrides=overrides, display=canon_name, callback=slicing_done, callback_args=( target, full_path, select_after_slicing, print_after_slicing, ), ) except octoprint.slicing.UnknownProfile: abort(404, description="Unknown profile") location = url_for(".readGcodeFile", target=target, filename=full_path, _external=True) result = { "name": destination, "path": full_path, "display": canon_name, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + target + "/" + full_path, }, } r = make_response(jsonify(result), 202) r.headers["Location"] = location return r elif command == "analyse": with Permissions.FILES_UPLOAD.require(403): if not _verifyFileExists(target, filename): abort(404) printer_profile = None if "printerProfile" in data and data["printerProfile"]: printer_profile = data["printerProfile"] if not fileManager.analyse( target, filename, printer_profile_id=printer_profile): abort(400, description="No analysis possible") elif command == "copy" or command == "move": with Permissions.FILES_UPLOAD.require(403): # Copy and move are only possible on local storage if target not in [FileDestinations.LOCAL]: abort(400, description="Unsupported target for {}".format(command)) if not _verifyFileExists(target, filename) and not _verifyFolderExists( target, filename): abort(404) path, name = fileManager.split_path(target, filename) destination = data["destination"] dst_path, dst_name = fileManager.split_path(target, destination) sanitized_destination = fileManager.join_path( target, dst_path, fileManager.sanitize_name(target, dst_name)) # Check for exception thrown by _verifyFolderExists, if outside the root directory try: if (_verifyFolderExists(target, destination) and sanitized_destination != filename): # destination is an existing folder and not ourselves (= display rename), we'll assume we are supposed # to move filename to this folder under the same name destination = fileManager.join_path( target, destination, name) if _verifyFileExists(target, destination) or _verifyFolderExists( target, destination): abort(409, description="File or folder does already exist") except Exception: abort(409, description= "Exception thrown by storage, bad folder/file name?") is_file = fileManager.file_exists(target, filename) is_folder = fileManager.folder_exists(target, filename) if not (is_file or is_folder): abort(400, description="Neither file nor folder, can't {}".format( command)) if command == "copy": # destination already there? error... if _verifyFileExists(target, destination) or _verifyFolderExists( target, destination): abort(409, description="File or folder does already exist") if is_file: fileManager.copy_file(target, filename, destination) else: fileManager.copy_folder(target, filename, destination) elif command == "move": with Permissions.FILES_DELETE.require(403): if _isBusy(target, filename): abort( 409, description= "Trying to move a file or folder that is currently in use", ) # destination already there AND not ourselves (= display rename)? error... if (_verifyFileExists(target, destination) or _verifyFolderExists(target, destination) ) and sanitized_destination != filename: abort(409, description="File or folder does already exist") # deselect the file if it's currently selected currentOrigin, currentFilename = _getCurrentFile() if currentFilename is not None and filename == currentFilename: printer.unselect_file() if is_file: fileManager.move_file(target, filename, destination) else: fileManager.move_folder(target, filename, destination) location = url_for(".readGcodeFile", target=target, filename=destination, _external=True) result = { "name": name, "path": destination, "origin": FileDestinations.LOCAL, "refs": { "resource": location }, } if is_file: result["refs"]["download"] = ( url_for("index", _external=True) + "downloads/files/" + target + "/" + destination) r = make_response(jsonify(result), 201) r.headers["Location"] = location return r return NO_CONTENT
def uploadGcodeFile(target): input_name = "file" input_upload_name = input_name + "." + settings().get(["server", "uploads", "nameSuffix"]) input_upload_path = input_name + "." + settings().get(["server", "uploads", "pathSuffix"]) if input_upload_name in request.values and input_upload_path in request.values: if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) upload = octoprint.filemanager.util.DiskFileWrapper(request.values[input_upload_name], request.values[input_upload_path]) # Store any additional user data the caller may have passed. userdata = None if "userdata" in request.values: import json try: userdata = json.loads(request.values["userdata"]) except: return make_response("userdata contains invalid JSON", 400) if target == FileDestinations.SDCARD and not settings().getBoolean(["feature", "sdSupport"]): return make_response("SD card support is disabled", 404) sd = target == FileDestinations.SDCARD selectAfterUpload = "select" in request.values.keys() and request.values["select"] in valid_boolean_trues printAfterSelect = "print" in request.values.keys() and request.values["print"] in valid_boolean_trues if sd: # validate that all preconditions for SD upload are met before attempting it if not (printer.is_operational() and not (printer.is_printing() or printer.is_paused())): return make_response("Can not upload to SD card, printer is either not operational or already busy", 409) if not printer.is_sd_ready(): return make_response("Can not upload to SD card, not yet initialized", 409) # determine future filename of file to be uploaded, abort if it can't be uploaded try: # FileDestinations.LOCAL = should normally be target, but can't because SDCard handling isn't implemented yet futurePath, futureFilename = fileManager.sanitize(FileDestinations.LOCAL, upload.filename) except: futurePath = None futureFilename = None if futureFilename is None: return make_response("Can not upload file %s, wrong format?" % upload.filename, 415) if "path" in request.values and request.values["path"]: # we currently only support uploads to sdcard via local, so first target is local instead of "target" futurePath = fileManager.sanitize_path(FileDestinations.LOCAL, request.values["path"]) # prohibit overwriting currently selected file while it's being printed futureFullPath = fileManager.join_path(FileDestinations.LOCAL, futurePath, futureFilename) futureFullPathInStorage = fileManager.path_in_storage(FileDestinations.LOCAL, futureFullPath) if not printer.can_modify_file(futureFullPathInStorage, sd): return make_response("Trying to overwrite file that is currently being printed: %s" % futureFullPath, 409) reselect = printer.is_current_file(futureFullPathInStorage, sd) def fileProcessingFinished(filename, absFilename, destination): """ Callback for when the file processing (upload, optional slicing, addition to analysis queue) has finished. Depending on the file's destination triggers either streaming to SD card or directly calls selectAndOrPrint. """ if destination == FileDestinations.SDCARD and octoprint.filemanager.valid_file_type(filename, "gcode"): return filename, printer.add_sd_file(filename, absFilename, selectAndOrPrint) else: selectAndOrPrint(filename, absFilename, destination) return filename def selectAndOrPrint(filename, absFilename, destination): """ Callback for when the file is ready to be selected and optionally printed. For SD file uploads this is only the case after they have finished streaming to the printer, which is why this callback is also used for the corresponding call to addSdFile. Selects the just uploaded file if either selectAfterUpload or printAfterSelect are True, or if the exact file is already selected, such reloading it. """ if octoprint.filemanager.valid_file_type(added_file, "gcode") and (selectAfterUpload or printAfterSelect or reselect): printer.select_file(absFilename, destination == FileDestinations.SDCARD, printAfterSelect) try: added_file = fileManager.add_file(FileDestinations.LOCAL, futureFullPathInStorage, upload, allow_overwrite=True) except octoprint.filemanager.storage.StorageError as e: if e.code == octoprint.filemanager.storage.StorageError.INVALID_FILE: return make_response("Could not upload the file \"{}\", invalid type".format(upload.filename), 400) else: return make_response("Could not upload the file \"{}\"".format(upload.filename), 500) if octoprint.filemanager.valid_file_type(added_file, "stl"): filename = added_file done = True else: filename = fileProcessingFinished(added_file, fileManager.path_on_disk(FileDestinations.LOCAL, added_file), target) done = not sd if userdata is not None: # upload included userdata, add this now to the metadata fileManager.set_additional_metadata(FileDestinations.LOCAL, added_file, "userdata", userdata) sdFilename = None if isinstance(filename, tuple): filename, sdFilename = filename eventManager.fire(Events.UPLOAD, {"name": futureFilename, "path": filename, "target": target, # TODO deprecated, remove in 1.4.0 "file": filename}) files = {} location = url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=filename, _external=True) files.update({ FileDestinations.LOCAL: { "name": futureFilename, "path": filename, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + FileDestinations.LOCAL + "/" + filename } } }) if sd and sdFilename: location = url_for(".readGcodeFile", target=FileDestinations.SDCARD, filename=sdFilename, _external=True) files.update({ FileDestinations.SDCARD: { "name": sdFilename, "path": sdFilename, "origin": FileDestinations.SDCARD, "refs": { "resource": location } } }) r = make_response(jsonify(files=files, done=done), 201) r.headers["Location"] = location return r elif "foldername" in request.values: foldername = request.values["foldername"] if not target in [FileDestinations.LOCAL]: return make_response("Unknown target: %s" % target, 400) futurePath, futureName = fileManager.sanitize(target, foldername) if not futureName or not futurePath: return make_response("Can't create a folder with an empty name", 400) if "path" in request.values and request.values["path"]: futurePath = fileManager.sanitize_path(FileDestinations.LOCAL, request.values["path"]) futureFullPath = fileManager.join_path(target, futurePath, futureName) if octoprint.filemanager.valid_file_type(futureName): return make_response("Can't create a folder named %s, please try another name" % futureName, 409) try: added_folder = fileManager.add_folder(target, futureFullPath) except octoprint.filemanager.storage.StorageError as e: if e.code == octoprint.filemanager.storage.StorageError.INVALID_DIRECTORY: return make_response("Could not create folder {}, invalid directory".format(futureName)) else: return make_response("Could not create folder {}".format(futureName)) location = url_for(".readGcodeFile", target=FileDestinations.LOCAL, filename=added_folder, _external=True) folder = dict(name=futureName, path=added_folder, origin=target, refs=dict(resource=location)) r = make_response(jsonify(folder=folder, done=True), 201) r.headers["Location"] = location return r else: return make_response("No file to upload and no folder to create", 400)
def gcodeFileCommand(filename, target): if not target in [FileDestinations.LOCAL, FileDestinations.SDCARD]: return make_response("Unknown target: %s" % target, 404) # valid file commands, dict mapping command name to mandatory parameters valid_commands = { "select": [], "slice": [], "copy": ["destination"], "move": ["destination"] } command, data, response = get_json_command_from_request(request, valid_commands) if response is not None: return response if command == "select": if not _verifyFileExists(target, filename): return make_response("File not found on '%s': %s" % (target, filename), 404) # selects/loads a file if not octoprint.filemanager.valid_file_type(filename, type="machinecode"): return make_response("Cannot select {filename} for printing, not a machinecode file".format(**locals()), 415) printAfterLoading = False if "print" in data.keys() and data["print"] in valid_boolean_trues: if not printer.is_operational(): return make_response("Printer is not operational, cannot directly start printing", 409) printAfterLoading = True sd = False if target == FileDestinations.SDCARD: filenameToSelect = filename sd = True else: filenameToSelect = fileManager.path_on_disk(target, filename) printer.select_file(filenameToSelect, sd, printAfterLoading) elif command == "slice": if not _verifyFileExists(target, filename): return make_response("File not found on '%s': %s" % (target, filename), 404) try: if "slicer" in data: slicer = data["slicer"] del data["slicer"] slicer_instance = slicingManager.get_slicer(slicer) elif "cura" in slicingManager.registered_slicers: slicer = "cura" slicer_instance = slicingManager.get_slicer("cura") else: return make_response("Cannot slice {filename}, no slicer available".format(**locals()), 415) except octoprint.slicing.UnknownSlicer as e: return make_response("Slicer {slicer} is not available".format(slicer=e.slicer), 400) if not any([octoprint.filemanager.valid_file_type(filename, type=source_file_type) for source_file_type in slicer_instance.get_slicer_properties().get("source_file_types", ["model"])]): return make_response("Cannot slice {filename}, not a model file".format(**locals()), 415) if slicer_instance.get_slicer_properties().get("same_device", True) and (printer.is_printing() or printer.is_paused()): # slicer runs on same device as OctoPrint, slicing while printing is hence disabled return make_response("Cannot slice on {slicer} while printing due to performance reasons".format(**locals()), 409) if "destination" in data and data["destination"]: destination = data["destination"] del data["destination"] elif "gcode" in data and data["gcode"]: destination = data["gcode"] del data["gcode"] else: import os name, _ = os.path.splitext(filename) destination = name + "." + slicer_instance.get_slicer_properties().get("destination_extensions", ["gco", "gcode", "g"])[0] full_path = destination if "path" in data and data["path"]: full_path = fileManager.join_path(target, data["path"], destination) else: path, _ = fileManager.split_path(target, filename) if path: full_path = fileManager.join_path(target, path, destination) # prohibit overwriting the file that is currently being printed currentOrigin, currentFilename = _getCurrentFile() if currentFilename == full_path and currentOrigin == target and (printer.is_printing() or printer.is_paused()): make_response("Trying to slice into file that is currently being printed: %s" % full_path, 409) if "profile" in data.keys() and data["profile"]: profile = data["profile"] del data["profile"] else: profile = None if "printerProfile" in data.keys() and data["printerProfile"]: printerProfile = data["printerProfile"] del data["printerProfile"] else: printerProfile = None if "position" in data.keys() and data["position"] and isinstance(data["position"], dict) and "x" in data["position"] and "y" in data["position"]: position = data["position"] del data["position"] else: position = None select_after_slicing = False if "select" in data.keys() and data["select"] in valid_boolean_trues: if not printer.is_operational(): return make_response("Printer is not operational, cannot directly select for printing", 409) select_after_slicing = True print_after_slicing = False if "print" in data.keys() and data["print"] in valid_boolean_trues: if not printer.is_operational(): return make_response("Printer is not operational, cannot directly start printing", 409) select_after_slicing = print_after_slicing = True override_keys = [k for k in data if k.startswith("profile.") and data[k] is not None] overrides = dict() for key in override_keys: overrides[key[len("profile."):]] = data[key] def slicing_done(target, path, select_after_slicing, print_after_slicing): if select_after_slicing or print_after_slicing: sd = False if target == FileDestinations.SDCARD: filenameToSelect = path sd = True else: filenameToSelect = fileManager.path_on_disk(target, path) printer.select_file(filenameToSelect, sd, print_after_slicing) try: fileManager.slice(slicer, target, filename, target, full_path, profile=profile, printer_profile_id=printerProfile, position=position, overrides=overrides, callback=slicing_done, callback_args=(target, full_path, select_after_slicing, print_after_slicing)) except octoprint.slicing.UnknownProfile: return make_response("Profile {profile} doesn't exist".format(**locals()), 400) files = {} location = url_for(".readGcodeFile", target=target, filename=full_path, _external=True) result = { "name": destination, "path": full_path, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + target + "/" + full_path } } r = make_response(jsonify(result), 202) r.headers["Location"] = location return r elif command == "copy" or command == "move": # Copy and move are only possible on local storage if not target in [FileDestinations.LOCAL]: return make_response("Unsupported target for {}: {}".format(command, target), 400) if not _verifyFileExists(target, filename) and not _verifyFolderExists(target, filename): return make_response("File or folder not found on {}: {}".format(target, filename), 404) path, name = fileManager.split_path(target, filename) destination = data["destination"] if _verifyFolderExists(target, destination): # destination is an existing folder, we'll assume we are supposed to move filename to this # folder under the same name destination = fileManager.join_path(target, destination, name) if _verifyFileExists(target, destination) or _verifyFolderExists(target, destination): return make_response("File or folder does already exist on {}: {}".format(target, destination), 409) is_file = fileManager.file_exists(target, filename) is_folder = fileManager.folder_exists(target, filename) if not (is_file or is_folder): return make_response("{} on {} is neither file or folder, can't {}".format(filename, target, command), 400) if command == "copy": if is_file: fileManager.copy_file(target, filename, destination) else: fileManager.copy_folder(target, filename, destination) elif command == "move": if _isBusy(target, filename): return make_response("Trying to move a file or folder that is currently in use: {}".format(filename), 409) # deselect the file if it's currently selected currentOrigin, currentFilename = _getCurrentFile() if currentFilename is not None and filename == currentFilename: printer.unselect_file() if is_file: fileManager.move_file(target, filename, destination) else: fileManager.move_folder(target, filename, destination) location = url_for(".readGcodeFile", target=target, filename=destination, _external=True) result = { "name": name, "path": destination, "origin": FileDestinations.LOCAL, "refs": { "resource": location } } if is_file: result["refs"]["download"] = url_for("index", _external=True) + "downloads/files/" + target + "/" + destination r = make_response(jsonify(result), 201) r.headers["Location"] = location return r return NO_CONTENT
def uploadGcodeFile(target): input_name = "file" input_upload_name = (input_name + "." + settings().get(["server", "uploads", "nameSuffix"])) input_upload_path = (input_name + "." + settings().get(["server", "uploads", "pathSuffix"])) if input_upload_name in request.values and input_upload_path in request.values: if target not in [FileDestinations.LOCAL, FileDestinations.SDCARD]: abort(404) upload = octoprint.filemanager.util.DiskFileWrapper( request.values[input_upload_name], request.values[input_upload_path]) # Store any additional user data the caller may have passed. userdata = None if "userdata" in request.values: import json try: userdata = json.loads(request.values["userdata"]) except Exception: abort(400, description="userdata contains invalid JSON") if target == FileDestinations.SDCARD and not settings().getBoolean( ["feature", "sdSupport"]): abort(404) sd = target == FileDestinations.SDCARD selectAfterUpload = ("select" in request.values and request.values["select"] in valid_boolean_trues and Permissions.FILES_SELECT.can()) printAfterSelect = ("print" in request.values and request.values["print"] in valid_boolean_trues and Permissions.PRINT.can()) if sd: # validate that all preconditions for SD upload are met before attempting it if not (printer.is_operational() and not (printer.is_printing() or printer.is_paused())): abort( 409, description= "Can not upload to SD card, printer is either not operational or already busy", ) if not printer.is_sd_ready(): abort( 409, description="Can not upload to SD card, not yet initialized" ) # determine future filename of file to be uploaded, abort if it can't be uploaded try: # FileDestinations.LOCAL = should normally be target, but can't because SDCard handling isn't implemented yet canonPath, canonFilename = fileManager.canonicalize( FileDestinations.LOCAL, upload.filename) if request.values.get("path"): canonPath = request.values.get("path") if request.values.get("filename"): canonFilename = request.values.get("filename") futurePath = fileManager.sanitize_path(FileDestinations.LOCAL, canonPath) futureFilename = fileManager.sanitize_name(FileDestinations.LOCAL, canonFilename) except Exception: canonFilename = None futurePath = None futureFilename = None if futureFilename is None: abort(415, description="Can not upload file, wrong format?") # prohibit overwriting currently selected file while it's being printed futureFullPath = fileManager.join_path(FileDestinations.LOCAL, futurePath, futureFilename) futureFullPathInStorage = fileManager.path_in_storage( FileDestinations.LOCAL, futureFullPath) if not printer.can_modify_file(futureFullPathInStorage, sd): abort( 409, description= "Trying to overwrite file that is currently being printed", ) if (fileManager.file_exists(FileDestinations.LOCAL, futureFullPathInStorage) and request.values.get("noOverwrite") in valid_boolean_trues): abort(409, description="File already exists and noOverwrite was set") reselect = printer.is_current_file(futureFullPathInStorage, sd) user = current_user.get_name() def fileProcessingFinished(filename, absFilename, destination): """ Callback for when the file processing (upload, optional slicing, addition to analysis queue) has finished. Depending on the file's destination triggers either streaming to SD card or directly calls selectAndOrPrint. """ if (destination == FileDestinations.SDCARD and octoprint.filemanager.valid_file_type( filename, "machinecode")): return filename, printer.add_sd_file( filename, absFilename, on_success=selectAndOrPrint, tags={"source:api", "api:files.sd"}, ) else: selectAndOrPrint(filename, absFilename, destination) return filename def selectAndOrPrint(filename, absFilename, destination): """ Callback for when the file is ready to be selected and optionally printed. For SD file uploads this is only the case after they have finished streaming to the printer, which is why this callback is also used for the corresponding call to addSdFile. Selects the just uploaded file if either selectAfterUpload or printAfterSelect are True, or if the exact file is already selected, such reloading it. """ if octoprint.filemanager.valid_file_type( added_file, "gcode") and (selectAfterUpload or printAfterSelect or reselect): printer.select_file( absFilename, destination == FileDestinations.SDCARD, printAfterSelect, user, ) try: added_file = fileManager.add_file( FileDestinations.LOCAL, futureFullPathInStorage, upload, allow_overwrite=True, display=canonFilename, ) except octoprint.filemanager.storage.StorageError as e: if e.code == octoprint.filemanager.storage.StorageError.INVALID_FILE: abort(400, description="Could not upload file, invalid type") else: abort(500, description="Could not upload file") if octoprint.filemanager.valid_file_type(added_file, "stl"): filename = added_file done = True else: filename = fileProcessingFinished( added_file, fileManager.path_on_disk(FileDestinations.LOCAL, added_file), target, ) done = not sd if userdata is not None: # upload included userdata, add this now to the metadata fileManager.set_additional_metadata(FileDestinations.LOCAL, added_file, "userdata", userdata) sdFilename = None if isinstance(filename, tuple): filename, sdFilename = filename payload = { "name": futureFilename, "path": filename, "target": target, "select": selectAfterUpload, "print": printAfterSelect, } if userdata is not None: payload["userdata"] = userdata eventManager.fire(Events.UPLOAD, payload) files = {} location = url_for( ".readGcodeFile", target=FileDestinations.LOCAL, filename=filename, _external=True, ) files.update({ FileDestinations.LOCAL: { "name": futureFilename, "path": filename, "origin": FileDestinations.LOCAL, "refs": { "resource": location, "download": url_for("index", _external=True) + "downloads/files/" + FileDestinations.LOCAL + "/" + urlquote(filename), }, } }) if sd and sdFilename: location = url_for( ".readGcodeFile", target=FileDestinations.SDCARD, filename=sdFilename, _external=True, ) files.update({ FileDestinations.SDCARD: { "name": sdFilename, "path": sdFilename, "origin": FileDestinations.SDCARD, "refs": { "resource": location }, } }) r = make_response(jsonify(files=files, done=done), 201) r.headers["Location"] = location return r elif "foldername" in request.values: foldername = request.values["foldername"] if target not in [FileDestinations.LOCAL]: abort(400, description="target is invalid") canonPath, canonName = fileManager.canonicalize(target, foldername) futurePath = fileManager.sanitize_path(target, canonPath) futureName = fileManager.sanitize_name(target, canonName) if not futureName or not futurePath: abort(400, description="folder name is empty") if "path" in request.values and request.values["path"]: futurePath = fileManager.sanitize_path(FileDestinations.LOCAL, request.values["path"]) futureFullPath = fileManager.join_path(target, futurePath, futureName) if octoprint.filemanager.valid_file_type(futureName): abort(409, description="Can't create folder, please try another name") try: added_folder = fileManager.add_folder(target, futureFullPath, display=canonName) except octoprint.filemanager.storage.StorageError as e: if e.code == octoprint.filemanager.storage.StorageError.INVALID_DIRECTORY: abort(400, description="Could not create folder, invalid directory") else: abort(500, description="Could not create folder") location = url_for( ".readGcodeFile", target=FileDestinations.LOCAL, filename=added_folder, _external=True, ) folder = { "name": futureName, "path": added_folder, "origin": target, "refs": { "resource": location }, } r = make_response(jsonify(folder=folder, done=True), 201) r.headers["Location"] = location return r else: abort(400, description="No file to upload and no folder to create")