diff --git a/src/octoprint/plugins/svgtogcode/__init__.py b/src/octoprint/plugins/svgtogcode/__init__.py index 123e8630..28d9cdda 100644 --- a/src/octoprint/plugins/svgtogcode/__init__.py +++ b/src/octoprint/plugins/svgtogcode/__init__.py @@ -235,7 +235,7 @@ class SvgToGcodePlugin(octoprint.plugin.SlicerPlugin, def get_assets(self): return dict( - js=[ "js/convert.js", "js/working_area.js", "js/gcode_parser.js", "js/lib/snap.svg-min.js", "js/lib/photobooth_min.js", "js/matrix_oven.js", "js/render_fills.js", "js/drag_scale_rotate.js"], + js=[ "js/convert.js", "js/working_area.js", "js/gcode_parser.js", "js/lib/snap.svg-min.js", "js/lib/photobooth_min.js", "js/matrix_oven.js", "js/render_fills.js", "js/drag_scale_rotate.js", "js/svg2gcode.js"], less=["less/svgtogcode.less"], css=["css/svgtogcode.css", "css/mrbeam.css"] ) @@ -344,7 +344,7 @@ class SvgToGcodePlugin(octoprint.plugin.SlicerPlugin, if not machinecode_path: path, _ = os.path.splitext(model_path) machinecode_path = path + ".gco" - + self._svgtogcode_logger.info("### Slicing %s to %s using profile stored at %s" % (model_path, machinecode_path, profile_path)) ## direct call diff --git a/src/octoprint/plugins/svgtogcode/static/js/convert.js b/src/octoprint/plugins/svgtogcode/static/js/convert.js index fe37227f..e2ba8a41 100644 --- a/src/octoprint/plugins/svgtogcode/static/js/convert.js +++ b/src/octoprint/plugins/svgtogcode/static/js/convert.js @@ -22,7 +22,7 @@ $(function(){ self.profiles = ko.observableArray(); self.defaultSlicer = undefined; self.defaultProfile = undefined; - + // expert settings self.showHints = ko.observable(false); self.showExpertSettings = ko.observable(false); @@ -37,7 +37,7 @@ $(function(){ self.minSpeed = ko.observable(20); self.fill_areas = ko.observable(false); self.show_fill_areas_checkbox = ko.observable(false); - + // image engraving stuff // preset values are a good start for wood engraving self.images_placed = ko.observable(false); @@ -46,16 +46,16 @@ $(function(){ }); self.imgIntensityWhite = ko.observable(0); self.imgIntensityBlack = ko.observable(500); - self.imgFeedrateWhite = ko.observable(1500); + self.imgFeedrateWhite = ko.observable(1500); self.imgFeedrateBlack = ko.observable(250); self.imgDithering = ko.observable(false); self.imgSharpening = ko.observable(1); self.imgContrast = ko.observable(1); self.beamDiameter = ko.observable(0.2); - + self.sharpeningMax = 25; self.contrastMax = 2; - + // preprocessing preview ... returns opacity 0.0 - 1.0 self.sharpenedPreview = ko.computed(function(){ if(self.imgDithering()) return 0; @@ -73,7 +73,6 @@ $(function(){ return contrastPercents - sharpeningPercents/2; } }, self); - self.maxSpeed.subscribe(function(val){ self._configureFeedrateSlider(); @@ -91,7 +90,7 @@ $(function(){ if(self.laserIntensity() === undefined){ var intensity = self.settings.settings.plugins.svgtogcode.defaultIntensity(); self.laserIntensity(intensity); - } + } if(self.laserSpeed() === undefined){ var speed = self.settings.settings.plugins.svgtogcode.defaultFeedrate(); self.laserSpeed(speed); @@ -107,7 +106,7 @@ $(function(){ self.convert(); } }; - + self.cancel_conversion = function(){ if(self.slicing_in_progress()){ //console.log('cancel slicing', self.slicing_in_progress()); @@ -139,14 +138,14 @@ $(function(){ if(uniqueDesigns > 1){ gcode_name += "_"+(uniqueDesigns-1)+"more"; } - + return gcode_name; - } else { + } else { console.error("no designs placed."); return; } }; - + self.settingsString = ko.computed(function(){ var intensity = self.laserIntensity(); var feedrate = self.laserSpeed(); @@ -248,7 +247,7 @@ $(function(){ } else { self.slicing_in_progress(true); self.workingArea.getCompositionSVG(self.fill_areas(), function(composition){ - self.svg = composition; + self.svg = composition; var filename = self.gcodeFilename() + self.settingsString() + '.gco'; var gcodeFilename = self._sanitize(filename); @@ -279,6 +278,10 @@ $(function(){ data.gcodeFilesToAppend = self.gcodeFilesToAppend; } + var snapelement = snap.select("#userContent"); + snapelement.bake(false, 5); + data.gcodedata = snapelement.toGcode(); + $.ajax({ url: API_BASEURL + "files/convert", type: "POST", @@ -286,7 +289,6 @@ $(function(){ contentType: "application/json; charset=UTF-8", data: JSON.stringify(data) }); - }); } }; @@ -303,7 +305,7 @@ $(function(){ self._configureFeedrateSlider(); self._configureImgSliders(); }; - + self.onSlicingProgress = function(slicer, model_path, machinecode_path, progress){ self.slicing_progress(progress); }; @@ -389,7 +391,7 @@ $(function(){ self._calcRealSpeed = function(sliderVal){ return Math.round(self.minSpeed() + sliderVal/100 * (self.maxSpeed() - self.minSpeed())); }; - + self._configureImgSliders = function() { self.contrastSlider = $("#svgtogcode_contrast_slider").slider({ step: .1, @@ -400,7 +402,7 @@ $(function(){ }).on("slide", function(ev){ self.imgContrast(ev.value); }); - + self.sharpeningSlider = $("#svgtogcode_sharpening_slider").slider({ step: 1, min: 1, @@ -419,9 +421,9 @@ $(function(){ }); } - - ADDITIONAL_VIEWMODELS.push([VectorConversionViewModel, - ["loginStateViewModel", "settingsViewModel", "printerStateViewModel", "workingAreaViewModel", "gcodeFilesViewModel"], + + ADDITIONAL_VIEWMODELS.push([VectorConversionViewModel, + ["loginStateViewModel", "settingsViewModel", "printerStateViewModel", "workingAreaViewModel", "gcodeFilesViewModel"], document.getElementById("dialog_vector_graphics_conversion")]); - + }); diff --git a/src/octoprint/plugins/svgtogcode/static/js/svg2gcode.js b/src/octoprint/plugins/svgtogcode/static/js/svg2gcode.js new file mode 100644 index 00000000..b88036fa --- /dev/null +++ b/src/octoprint/plugins/svgtogcode/static/js/svg2gcode.js @@ -0,0 +1,66 @@ +Snap.plugin(function (Snap, Element, Paper, global) { + /** + * generates and returns the svg as gcode + * + * @param {integer} variablename : description + * @returns {list of gcode} + */ + Element.prototype.toGcode = function () { + var gCodeList = []; + var elem = this.selectAll("path"); + for (var i = 0; i < elem.length; i++) { + gCodeList.push(elem[i].pathStringToGCode()); + } + return gCodeList; + } + + Element.prototype.pathStringToGCode = function () { + if (this.type !== "path") { + return this; + } + var gcode = [] + var startP = [0, 0] + var lastP = [0, 0] + var arr = Snap.parsePathString(this.realPath); + for (var i = 0; i < arr.length; i++) { + if (arr[i][0] == "M") { + gcode.push("G0X" + arr[i][1] + "Y" + arr[i][2]); + startP = [arr[i][1], arr[i][2]]; + lastP = [arr[i][1], arr[i][2]]; + } else if (arr[i][0] == "L") { + gcode.push("G1X" + arr[i][1] + "Y" + arr[i][2]); + lastP = [arr[i][1], arr[i][2]]; + } else if (arr[i][0] == "H") { + gcode.push("H"); + } else if (arr[i][0] == "V") { + gcode.push("V"); + } else if (arr[i][0] == "C") { + var x0 = lastP[0]; + var y0 = lastP[1]; + var x1 = arr[i][1]; + var y1 = arr[i][2]; + var x2 = arr[i][3]; + var y2 = arr[i][4]; + var x3 = arr[i][5]; + var y3 = arr[i][6]; + var tmp = Snap.path.getTotalLength("M" + lastP[0] + "," + lastP[1] + arr[i][0] + arr[i][1] + "," + arr[i][2] + "," + arr[i][3] + "," + arr[i][4] + "," + arr[i][5] + "," + arr[i][6]); + var range = Math.round(tmp) * 10; + for (var t = 1; t <= range; t++) { + obj = Snap.path.findDotsAtSegment(x0, y0, x1, y1, x2, y2, x3, y3, t / range) + gcode.push("G1X" + Math.round(obj.x * 100) / 100 + "Y" + Math.round(obj.y * 100) / 100); + } + lastP = [arr[i][5], arr[i][6]]; + } else if (arr[i][0] == "Q") { + gcode.push("NOT_IMPLEMENTED"); + } else if (arr[i][0] == "A") { + gcode.push("NOT_IMPLEMENTED"); + } else if (arr[i][0] == "Z") { + if (lastP[0] != startP[0] && lastP[1] != startP[1]) { + gcode.push("G1X" + startP[0] + "Y" + startP[1]); + } + } + } + //console.log(gcode.join("\n").length) + return gcode.join("\n"); + } +}); diff --git a/src/octoprint/server/api/files.py b/src/octoprint/server/api/files.py index 63266c14..e8bcdb4e 100644 --- a/src/octoprint/server/api/files.py +++ b/src/octoprint/server/api/files.py @@ -13,7 +13,7 @@ from octoprint.settings import settings, valid_boolean_trues from octoprint.server import printer, fileManager, slicingManager, eventManager, NO_CONTENT from octoprint.server.util.flask import restricted_access, get_json_command_from_request from octoprint.server.api import api -from octoprint.events import Events +from octoprint.events import eventManager, Events import octoprint.filemanager import shutil import octoprint.filemanager.util @@ -419,7 +419,7 @@ def gcodeFileCommand(filename, target): @restricted_access def gcodeConvertCommand(): target = FileDestinations.LOCAL; - + # valid file commands, dict mapping command name to mandatory parameters valid_commands = { "convert": [] @@ -427,14 +427,14 @@ def gcodeConvertCommand(): 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) +# return make_response("Could not slice: {result}".format(result=result), 500) # else: # output_path = fileManager.path_on_disk(location, path) # # append additioal gcodes @@ -461,18 +461,16 @@ def gcodeConvertCommand(): # #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 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'] - + 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): @@ -486,7 +484,7 @@ def gcodeConvertCommand(): 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()): @@ -500,7 +498,7 @@ def gcodeConvertCommand(): 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; @@ -518,7 +516,7 @@ def gcodeConvertCommand(): del data["profile"] else: profile = None -## + if "printerProfile" in data.keys() and data["printerProfile"]: printerProfile = data["printerProfile"] del data["printerProfile"] @@ -530,7 +528,7 @@ def gcodeConvertCommand(): 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(): @@ -548,34 +546,41 @@ def gcodeConvertCommand(): 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 + if data.has_key('gcodedata'): 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(output_path,'wb') as wfd: + for line in data['gcodedata']: + wfd.write(line) + eventManager().fire(Events.SLICING_DONE, {"stl": filename, "gcode": gcode_name, "gcode_location": target, "time": 1.0}) + else: + 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) + with open(path,'rb') as fd: + shutil.copyfileobj(fd, wfd, 1024*1024*10) - wfd.write( "\nM05\n") # ensure that the laser is off. + 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) + 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) + 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) @@ -593,7 +598,7 @@ def gcodeConvertCommand(): return r return NO_CONTENT - + @api.route("/files//", methods=["DELETE"]) @restricted_access