def is_slicer_configured(self): cura_engine = normalize_path(self._settings.get(["cura_engine"])) if self._is_engine_configured(cura_engine=cura_engine): return True else: self._logger.info("Path to CuraEngine has not been configured yet or does not exist (currently set to %r), Cura will not be selectable for slicing" % cura_engine) return False
def is_slicer_configured(self): cura_engine = normalize_path(self._settings.get(["cura_engine"])) if self._is_engine_configured(cura_engine=cura_engine): return True else: self._logger.info( "Path to CuraEngine has not been configured yet or does not exist (currently set to %r), Cura will not be selectable for slicing" % cura_engine) return False
def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, position=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None): if not profile_path: profile_path = self._settings.get(["default_profile"]) if not machinecode_path: path, _ = os.path.splitext(model_path) machinecode_path = path + ".gco" if position and isinstance( position, dict) and "x" in position and "y" in position: posX = position["x"] posY = position["y"] elif printer_profile["volume"][ "formFactor"] == "circular" or printer_profile["volume"][ "origin"] == "center": posX = 0 posY = 0 else: posX = printer_profile["volume"]["width"] / 2.0 posY = printer_profile["volume"]["depth"] / 2.0 self._slic3r_logger.info( "### Slicing %s to %s using profile stored at %s" % (model_path, machinecode_path, profile_path)) executable = normalize_path(self._settings.get(["slic3r_engine"])) if not executable: return False, "Path to Slic3r is not configured " import sarge working_dir, _ = os.path.split(executable) args = [ '"%s"' % executable, '--load', '"%s"' % profile_path, '--print-center', '"%f,%f"' % (posX, posY), '-o', '"%s"' % machinecode_path, '"%s"' % model_path ] command = " ".join(args) self._logger.info("Running %r in %s" % (command, working_dir)) try: p = sarge.run(command, cwd=working_dir, async=True, stdout=sarge.Capture(), stderr=sarge.Capture()) p.wait_events() try: with self._slicing_commands_mutex: self._slicing_commands[machinecode_path] = p.commands[0] line_seen = False while p.returncode is None: stdout_line = p.stdout.readline(timeout=0.5) stderr_line = p.stderr.readline(timeout=0.5) if not stdout_line and not stderr_line: if line_seen: break else: continue line_seen = True if stdout_line: self._slic3r_logger.debug("stdout: " + stdout_line.strip()) if stderr_line: self._slic3r_logger.debug("stderr: " + stderr_line.strip()) finally: p.close() with self._cancelled_jobs_mutex: if machinecode_path in self._cancelled_jobs: self._slic3r_logger.info("### Cancelled") raise octoprint.slicing.SlicingCancelled() self._slic3r_logger.info("### Finished, returncode %d" % p.returncode) if p.returncode == 0: return True, None else: self._logger.warn( "Could not slice via Slic3r, got return code %r" % p.returncode) return False, "Got returncode %r" % p.returncode except octoprint.slicing.SlicingCancelled as e: raise e except: self._logger.exception( "Could not slice via Slic3r, got an unknown error") return False, "Unknown error, please consult the log file" finally: with self._cancelled_jobs_mutex: if machinecode_path in self._cancelled_jobs: self._cancelled_jobs.remove(machinecode_path) with self._slicing_commands_mutex: if machinecode_path in self._slicing_commands: del self._slicing_commands[machinecode_path] self._slic3r_logger.info("-" * 40)
def is_slicer_configured(self): slic3r_engine = normalize_path(self._settings.get(["slic3r_engine"])) return slic3r_engine is not None and os.path.exists(slic3r_engine)
def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, position=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None): try: with self._job_mutex: executable = normalize_path( self._settings.get(["cura_engine_path"])) if not executable: self._logger.error(u"Path to CuraEngine is not configured") return False, "Path to CuraEngine is not configured" working_dir = os.path.dirname(executable) if not profile_path: profile_path = self._settings.get(["default_profile"]) profile_dict = get_profile_dict_from_yaml(profile_path) if "material_diameter" in profile_dict: filament_diameter = float( profile_dict["material_diameter"]) else: filament_diameter = None if on_progress: if not on_progress_args: on_progress_args = () if not on_progress_kwargs: on_progress_kwargs = dict() command_args = self._build_command(executable, model_path, printer_profile, machinecode_path, profile_dict, position) self._logger.info(u"Running %r in %s" % (" ".join(command_args), working_dir)) import sarge p = sarge.run(command_args, cwd=working_dir, async=True, stdout=sarge.Capture(), stderr=sarge.Capture()) p.wait_events() self._slicing_commands[machinecode_path] = p.commands[0] returncode, analysis = self._parse_slicing_output( p, on_progress, on_progress_args, on_progress_kwargs, filament_diameter=filament_diameter) with self._job_mutex: if machinecode_path in self._cancelled_jobs: self._cura_engine_logger.info(u"### Cancelled") raise octoprint.slicing.SlicingCancelled() self._cura_engine_logger.info(u"### Finished, returncode %d" % returncode) if returncode == 0: self._logger.info(u"Slicing complete.") return True, dict(analysis=analysis) else: self._logger.warn( u"Could not slice via Cura, got return code %r" % returncode) return False, "Got return code %r" % returncode except octoprint.slicing.SlicingCancelled as e: raise e except: self._logger.exception( u"Could not slice via Cura Engine, got an unknown error") return False, "Unknown error, please consult the log file" finally: with self._job_mutex: if machinecode_path in self._cancelled_jobs: self._cancelled_jobs.remove(machinecode_path) if machinecode_path in self._slicing_commands: del self._slicing_commands[machinecode_path] self._cura_engine_logger.info("-" * 40)
def _is_engine_configured(self, cura_engine=None): if cura_engine is None: cura_engine = normalize_path(self._settings.get(["cura_engine"])) return cura_engine is not None and os.path.isfile( cura_engine) and os.access(cura_engine, os.X_OK)
def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, position=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None): try: with self._job_mutex: if not profile_path: profile_path = self._settings.get(["default_profile"]) if not machinecode_path: path, _ = os.path.splitext(model_path) machinecode_path = path + ".gco" if position and isinstance( position, dict) and "x" in position and "y" in position: pos_x = position["x"] pos_y = position["y"] else: pos_x = None pos_y = None if on_progress: if not on_progress_args: on_progress_args = () if not on_progress_kwargs: on_progress_kwargs = dict() self._cura_logger.info( u"### Slicing {} to {} using profile stored at {}".format( to_unicode(model_path, errors="replace"), to_unicode(machinecode_path, errors="replace"), to_unicode(profile_path, errors="replace"))) executable = normalize_path(self._settings.get(["cura_engine" ])) if not executable: return False, u"Path to CuraEngine is not configured " working_dir = os.path.dirname(executable) slicing_profile = Profile(self._load_profile(profile_path), printer_profile, pos_x, pos_y) # NOTE: We can assume an extruder count of 1 here since the only way we currently # support dual extrusion in this implementation is by using the second extruder for support (which # the engine conversion will automatically detect and adapt accordingly). # # We currently do only support STL files as sliceables, which by default can only contain one mesh, # so no risk of having to slice multi-objects at the moment, which would necessitate a full analysis # of the objects to slice to determine amount of needed extruders to use here. If we ever decide to # also support dual extrusion slicing (including composition from multiple STLs or support for OBJ or # AMF files and the like), this code needs to be adapted! # # The extruder count is needed to decide which start/end gcode will be used from the Cura profile. # Stock Cura implementation counts the number of objects in the scene for this (and also takes a look # at the support usage, like the engine conversion here does). We only ever have one object. engine_settings = self._convert_to_engine(profile_path, printer_profile, pos_x=pos_x, pos_y=pos_y, used_extruders=1) # Start building the argument list for the CuraEngine command execution args = [executable, '-v', '-p'] # Add the settings (sorted alphabetically) to the command for k, v in sorted(engine_settings.items(), key=lambda s: s[0]): args += ["-s", "%s=%s" % (k, str(v))] args += ["-o", machinecode_path, model_path] self._logger.info(u"Running {!r} in {}".format( u" ".join( map(lambda x: to_unicode(x, errors="replace"), args)), working_dir)) import sarge p = sarge.run(args, cwd=working_dir, async=True, stdout=sarge.Capture(), stderr=sarge.Capture()) p.wait_events() self._slicing_commands[machinecode_path] = p.commands[0] try: layer_count = None step_factor = dict(inset=0, skin=1, export=2) analysis = None while p.returncode is None: line = p.stderr.readline(timeout=0.5) if not line: p.commands[0].poll() continue line = to_unicode(line, errors="replace") self._cura_logger.debug(line.strip()) if on_progress is not None: # The Cura slicing process has three individual steps, each consisting of <layer_count> substeps: # # - inset # - skin # - export # # So each layer will be processed three times, once for each step, resulting in a total amount of # substeps of 3 * <layer_count>. # # The CuraEngine reports the calculated layer count and the continuous progress on stderr. # The layer count gets reported right at the beginning in a line of the format: # # Layer count: <layer_count> # # The individual progress per each of the three steps gets reported on stderr in a line of # the format: # # Progress:<step>:<current_layer>:<layer_count> # # Thus, for determining the overall progress the following formula applies: # # progress = <step_factor> * <layer_count> + <current_layer> / <layer_count> * 3 # # with <step_factor> being 0 for "inset", 1 for "skin" and 2 for "export". if line.startswith( u"Layer count:") and layer_count is None: try: layer_count = float( line[len(u"Layer count:"):].strip()) except: pass elif line.startswith(u"Progress:"): split_line = line[len(u"Progress:"):].strip( ).split(":") if len(split_line) == 3: step, current_layer, _ = split_line try: current_layer = float(current_layer) except: pass else: if not step in step_factor: continue on_progress_kwargs["_progress"] = ( step_factor[step] * layer_count + current_layer) / (layer_count * 3) on_progress(*on_progress_args, **on_progress_kwargs) elif line.startswith(u"Print time:"): try: print_time = int( line[len(u"Print time:"):].strip()) if analysis is None: analysis = dict() analysis["estimatedPrintTime"] = print_time except: pass # Get the filament usage elif line.startswith(u"Filament:") or line.startswith( u"Filament2:"): if line.startswith(u"Filament:"): filament_str = line[len(u"Filament:"):].strip() tool_key = "tool0" else: filament_str = line[len(u"Filament2:"):].strip( ) tool_key = "tool1" try: filament = int(filament_str) if analysis is None: analysis = dict() if not "filament" in analysis: analysis["filament"] = dict() if not tool_key in analysis["filament"]: analysis["filament"][tool_key] = dict() if slicing_profile.get_float( "filament_diameter") is not None: if slicing_profile.get( "gcode_flavor" ) == GcodeFlavors.ULTIGCODE or slicing_profile.get( "gcode_flavor" ) == GcodeFlavors.REPRAP_VOLUME: analysis["filament"][ tool_key] = _get_usage_from_volume( filament, slicing_profile.get_float( "filament_diameter")) else: analysis["filament"][ tool_key] = _get_usage_from_length( filament, slicing_profile.get_float( "filament_diameter")) except: pass finally: p.close() with self._job_mutex: if machinecode_path in self._cancelled_jobs: self._cura_logger.info(u"### Cancelled") raise octoprint.slicing.SlicingCancelled() self._cura_logger.info(u"### Finished, returncode %d" % p.returncode) if p.returncode == 0: return True, dict(analysis=analysis) else: self._logger.warn( u"Could not slice via Cura, got return code %r" % p.returncode) return False, "Got returncode %r" % p.returncode except octoprint.slicing.SlicingCancelled as e: raise e except: self._logger.exception( u"Could not slice via Cura, got an unknown error") return False, "Unknown error, please consult the log file" finally: with self._job_mutex: if machinecode_path in self._cancelled_jobs: self._cancelled_jobs.remove(machinecode_path) if machinecode_path in self._slicing_commands: del self._slicing_commands[machinecode_path] self._cura_logger.info("-" * 40)
def is_slicer_configured(self): cura_engine = normalize_path(self._settings.get(["cura_engine"])) return self._is_engine_configured(cura_engine=cura_engine)
def _is_engine_configured(self, cura_engine=None): if cura_engine is None: cura_engine = normalize_path(self._settings.get(["cura_engine"])) return cura_engine is not None and os.path.isfile(cura_engine) and os.access(cura_engine, os.X_OK)
def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, position=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None): try: with self._job_mutex: if not profile_path: profile_path = self._settings.get(["default_profile"]) if not machinecode_path: path, _ = os.path.splitext(model_path) machinecode_path = path + ".gco" if position and isinstance(position, dict) and "x" in position and "y" in position: pos_x = position["x"] pos_y = position["y"] else: pos_x = None pos_y = None if on_progress: if not on_progress_args: on_progress_args = () if not on_progress_kwargs: on_progress_kwargs = dict() self._cura_logger.info(u"### Slicing %s to %s using profile stored at %s" % (model_path, machinecode_path, profile_path)) executable = normalize_path(self._settings.get(["cura_engine"])) if not executable: return False, "Path to CuraEngine is not configured " working_dir = os.path.dirname(executable) slicing_profile = Profile(self._load_profile(profile_path), printer_profile, pos_x, pos_y) # NOTE: We can assume an extruder count of 1 here since the only way we currently # support dual extrusion in this implementation is by using the second extruder for support (which # the engine conversion will automatically detect and adapt accordingly). # # We currently do only support STL files as sliceables, which by default can only contain one mesh, # so no risk of having to slice multi-objects at the moment, which would necessitate a full analysis # of the objects to slice to determine amount of needed extruders to use here. If we ever decide to # also support dual extrusion slicing (including composition from multiple STLs or support for OBJ or # AMF files and the like), this code needs to be adapted! # # The extruder count is needed to decide which start/end gcode will be used from the Cura profile. # Stock Cura implementation counts the number of objects in the scene for this (and also takes a look # at the support usage, like the engine conversion here does). We only ever have one object. engine_settings = self._convert_to_engine(profile_path, printer_profile, pos_x=pos_x, pos_y=pos_y, used_extruders=1) # Start building the argument list for the CuraEngine command execution args = [executable, '-v', '-p'] # Add the settings (sorted alphabetically) to the command for k, v in sorted(engine_settings.items(), key=lambda s: s[0]): args += ["-s", "%s=%s" % (k, str(v))] args += ["-o", machinecode_path, model_path] self._logger.info(u"Running %r in %s" % (" ".join(args), working_dir)) import sarge p = sarge.run(args, cwd=working_dir, async=True, stdout=sarge.Capture(), stderr=sarge.Capture()) p.wait_events() self._slicing_commands[machinecode_path] = p.commands[0] try: layer_count = None step_factor = dict( inset=0, skin=1, export=2 ) analysis = None while p.returncode is None: line = p.stderr.readline(timeout=0.5) if not line: p.commands[0].poll() continue line = octoprint.util.to_unicode(line, errors="replace") self._cura_logger.debug(line.strip()) if on_progress is not None: # The Cura slicing process has three individual steps, each consisting of <layer_count> substeps: # # - inset # - skin # - export # # So each layer will be processed three times, once for each step, resulting in a total amount of # substeps of 3 * <layer_count>. # # The CuraEngine reports the calculated layer count and the continuous progress on stderr. # The layer count gets reported right at the beginning in a line of the format: # # Layer count: <layer_count> # # The individual progress per each of the three steps gets reported on stderr in a line of # the format: # # Progress:<step>:<current_layer>:<layer_count> # # Thus, for determining the overall progress the following formula applies: # # progress = <step_factor> * <layer_count> + <current_layer> / <layer_count> * 3 # # with <step_factor> being 0 for "inset", 1 for "skin" and 2 for "export". if line.startswith(u"Layer count:") and layer_count is None: try: layer_count = float(line[len(u"Layer count:"):].strip()) except: pass elif line.startswith(u"Progress:"): split_line = line[len(u"Progress:"):].strip().split(":") if len(split_line) == 3: step, current_layer, _ = split_line try: current_layer = float(current_layer) except: pass else: if not step in step_factor: continue on_progress_kwargs["_progress"] = (step_factor[step] * layer_count + current_layer) / (layer_count * 3) on_progress(*on_progress_args, **on_progress_kwargs) elif line.startswith(u"Print time:"): try: print_time = int(line[len(u"Print time:"):].strip()) if analysis is None: analysis = dict() analysis["estimatedPrintTime"] = print_time except: pass # Get the filament usage elif line.startswith(u"Filament:") or line.startswith(u"Filament2:"): if line.startswith(u"Filament:"): filament_str = line[len(u"Filament:"):].strip() tool_key = "tool0" else: filament_str = line[len(u"Filament2:"):].strip() tool_key = "tool1" try: filament = int(filament_str) if analysis is None: analysis = dict() if not "filament" in analysis: analysis["filament"] = dict() if not tool_key in analysis["filament"]: analysis["filament"][tool_key] = dict() if slicing_profile.get_float("filament_diameter") is not None: if slicing_profile.get("gcode_flavor") == GcodeFlavors.ULTIGCODE or slicing_profile.get("gcode_flavor") == GcodeFlavors.REPRAP_VOLUME: analysis["filament"][tool_key] = _get_usage_from_volume(filament, slicing_profile.get_float("filament_diameter")) else: analysis["filament"][tool_key] = _get_usage_from_length(filament, slicing_profile.get_float("filament_diameter")) except: pass finally: p.close() with self._job_mutex: if machinecode_path in self._cancelled_jobs: self._cura_logger.info(u"### Cancelled") raise octoprint.slicing.SlicingCancelled() self._cura_logger.info(u"### Finished, returncode %d" % p.returncode) if p.returncode == 0: return True, dict(analysis=analysis) else: self._logger.warn(u"Could not slice via Cura, got return code %r" % p.returncode) return False, "Got returncode %r" % p.returncode except octoprint.slicing.SlicingCancelled as e: raise e except: self._logger.exception(u"Could not slice via Cura, got an unknown error") return False, "Unknown error, please consult the log file" finally: with self._job_mutex: if machinecode_path in self._cancelled_jobs: self._cancelled_jobs.remove(machinecode_path) if machinecode_path in self._slicing_commands: del self._slicing_commands[machinecode_path] self._cura_logger.info("-" * 40)
def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, position=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None): if not profile_path: profile_path = self._settings.get(["default_profile"]) if not machinecode_path: path, _ = os.path.splitext(model_path) machinecode_path = path + ".gco" if position and isinstance(position, dict) and "x" in position and "y" in position: posX = position["x"] posY = position["y"] elif printer_profile["volume"]["formFactor"] == "circular" or printer_profile["volume"]["origin"] == "center" : posX = 0 posY = 0 else: posX = printer_profile["volume"]["width"] / 2.0 posY = printer_profile["volume"]["depth"] / 2.0 self._slic3r_logger.info("### Slicing %s to %s using profile stored at %s" % (model_path, machinecode_path, profile_path)) executable = normalize_path(self._settings.get(["slic3r_engine"])) if not executable: return False, "Path to Slic3r is not configured " import sarge working_dir, _ = os.path.split(executable) args = ['"%s"' % executable, '--load', '"%s"' % profile_path, '--print-center', '"%f,%f"' % (posX, posY), '-o', '"%s"' % machinecode_path, '"%s"' % model_path] command = " ".join(args) self._logger.info("Running %r in %s" % (command, working_dir)) try: if parse_version(sarge.__version__) >= parse_version('0.1.5'): # Because in version 0.1.5 the name was changed in sarge. async_kwarg = 'async_' else: async_kwarg = 'async' p = sarge.run(command, cwd=working_dir, stdout=sarge.Capture(), stderr=sarge.Capture(), **{async_kwarg: True}) p.wait_events() try: with self._slicing_commands_mutex: self._slicing_commands[machinecode_path] = p.commands[0] line_seen = False while p.returncode is None: stdout_line = p.stdout.readline(timeout=0.5) stderr_line = p.stderr.readline(timeout=0.5) if not stdout_line and not stderr_line: if line_seen: break else: continue line_seen = True if stdout_line: self._slic3r_logger.debug("stdout: " + stdout_line.strip()) if stderr_line: self._slic3r_logger.debug("stderr: " + stderr_line.strip()) finally: p.close() with self._cancelled_jobs_mutex: if machinecode_path in self._cancelled_jobs: self._slic3r_logger.info("### Cancelled") raise octoprint.slicing.SlicingCancelled() self._slic3r_logger.info("### Finished, returncode %d" % p.returncode) if p.returncode == 0: analysis = get_analysis_from_gcode(machinecode_path) self._slic3r_logger.info("Analysis found in gcode: %s" % str(analysis)) if analysis: analysis = {'analysis': analysis} return True, analysis else: self._logger.warn("Could not slice via Slic3r, got return code %r" % p.returncode) return False, "Got returncode %r" % p.returncode except octoprint.slicing.SlicingCancelled as e: raise e except: self._logger.exception("Could not slice via Slic3r, got an unknown error") return False, "Unknown error, please consult the log file" finally: with self._cancelled_jobs_mutex: if machinecode_path in self._cancelled_jobs: self._cancelled_jobs.remove(machinecode_path) with self._slicing_commands_mutex: if machinecode_path in self._slicing_commands: del self._slicing_commands[machinecode_path] self._slic3r_logger.info("-" * 40)
def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, position=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None): if not profile_path: profile_path = self._settings.get(["default_profile"]) if not machinecode_path: path, _ = os.path.splitext(model_path) machinecode_path = path + ".gco" if position and isinstance( position, dict) and "x" in position and "y" in position: posX = position["x"] posY = position["y"] elif printer_profile["volume"][ "formFactor"] == "circular" or printer_profile["volume"][ "origin"] == "center": posX = 0 posY = 0 else: posX = printer_profile["volume"]["width"] / 2.0 posY = printer_profile["volume"]["depth"] / 2.0 self._slic3r_logger.info( "### Slicing %s to %s using profile stored at %s" % (model_path, machinecode_path, profile_path)) executable = normalize_path(self._settings.get(["slic3r_engine"])) if not executable: return False, "Path to Slic3r is not configured " args = [ '"%s"' % executable, '--load', '"%s"' % profile_path, '--print-center', '"%f,%f"' % (posX, posY), '-o', '"%s"' % machinecode_path, '"%s"' % model_path ] try: import subprocess help_process = subprocess.Popen((executable, '--help'), stdout=subprocess.PIPE) help_text = help_process.communicate()[0] if help_text.startswith('PrusaSlicer-2'): args = [ '"%s"' % executable, '--slice --load', '"%s"' % profile_path, '--center', '"%f,%f"' % (posX, posY), '-o', '"%s"' % machinecode_path, '"%s"' % model_path ] self._logger.info("Running Prusa Slic3r >= 2") except: self._logger.info("Error during Prusa Slic3r detection") import sarge working_dir, _ = os.path.split(executable) command = " ".join(args) self._logger.info("Running %r in %s" % (command, working_dir)) try: if parse_version(sarge.__version__) >= parse_version( '0.1.5' ): # Because in version 0.1.5 the name was changed in sarge. async_kwarg = 'async_' else: async_kwarg = 'async' p = sarge.run(command, cwd=working_dir, stdout=sarge.Capture(), stderr=sarge.Capture(), **{async_kwarg: True}) p.wait_events() last_error = "" try: with self._slicing_commands_mutex: self._slicing_commands[machinecode_path] = p.commands[0] line_seen = False while p.returncode is None: stdout_line = p.stdout.readline(timeout=0.5) stderr_line = p.stderr.readline(timeout=0.5) if not stdout_line and not stderr_line: if line_seen: break else: continue line_seen = True if stdout_line: self._slic3r_logger.debug("stdout: " + stdout_line.strip()) if stderr_line: self._slic3r_logger.debug("stderr: " + stderr_line.strip()) if (len(stderr_line.strip()) > 0): last_error = stderr_line.strip() finally: p.close() with self._cancelled_jobs_mutex: if machinecode_path in self._cancelled_jobs: self._slic3r_logger.info("### Cancelled") raise octoprint.slicing.SlicingCancelled() self._slic3r_logger.info("### Finished, returncode %d" % p.returncode) if p.returncode == 0: analysis = get_analysis_from_gcode(machinecode_path) self._slic3r_logger.info("Analysis found in gcode: %s" % str(analysis)) if analysis: analysis = {'analysis': analysis} return True, analysis else: self._logger.warn( "Could not slice via Slic3r, got return code %r" % p.returncode) self._logger.warn("Error was: %s" % last_error) return False, "Got returncode %r: %s" % (p.returncode, last_error) except octoprint.slicing.SlicingCancelled as e: raise e except: self._logger.exception( "Could not slice via Slic3r, got an unknown error") return False, "Unknown error, please consult the log file" finally: with self._cancelled_jobs_mutex: if machinecode_path in self._cancelled_jobs: self._cancelled_jobs.remove(machinecode_path) with self._slicing_commands_mutex: if machinecode_path in self._slicing_commands: del self._slicing_commands[machinecode_path] self._slic3r_logger.info("-" * 40)
def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, position=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None): try: with self._job_mutex: executable = normalize_path(self._settings.get(["cura_engine_path"])) if not executable: self._logger.error(u"Path to CuraEngine is not configured") return False, "Path to CuraEngine is not configured" working_dir = os.path.dirname(executable) if not profile_path: profile_path = self._settings.get(["default_profile"]) profile_dict = get_profile_dict_from_yaml(profile_path) if "material_diameter" in profile_dict: filament_diameter = float(profile_dict["material_diameter"]) else: filament_diameter = None if on_progress: if not on_progress_args: on_progress_args = () if not on_progress_kwargs: on_progress_kwargs = dict() command_args = self._build_command(executable, model_path, printer_profile, machinecode_path, profile_dict, position) self._logger.info(u"Running %r in %s" % (" ".join(command_args), working_dir)) import sarge p = sarge.run(command_args, cwd=working_dir, async=True, stdout=sarge.Capture(), stderr=sarge.Capture()) p.wait_events() self._slicing_commands[machinecode_path] = p.commands[0] returncode, analysis = self._parse_slicing_output(p, on_progress, on_progress_args, on_progress_kwargs, filament_diameter=filament_diameter) with self._job_mutex: if machinecode_path in self._cancelled_jobs: self._cura_engine_logger.info(u"### Cancelled") raise octoprint.slicing.SlicingCancelled() self._cura_engine_logger.info(u"### Finished, returncode %d" % returncode) if returncode == 0: self._logger.info(u"Slicing complete.") return True, dict(analysis=analysis) else: self._logger.warn(u"Could not slice via Cura, got return code %r" % returncode) return False, "Got return code %r" % returncode except octoprint.slicing.SlicingCancelled as e: raise e except: self._logger.exception(u"Could not slice via Cura Engine, got an unknown error") return False, "Unknown error, please consult the log file" finally: with self._job_mutex: if machinecode_path in self._cancelled_jobs: self._cancelled_jobs.remove(machinecode_path) if machinecode_path in self._slicing_commands: del self._slicing_commands[machinecode_path] self._cura_engine_logger.info("-" * 40)
def do_slice(self, model_path, printer_profile, machinecode_path=None, profile_path=None, position=None, on_progress=None, on_progress_args=None, on_progress_kwargs=None): try: with self._job_mutex: if not profile_path: profile_path = self._settings.get(["default_profile"]) if not machinecode_path: path, _ = os.path.splitext(model_path) machinecode_path = path + ".gco" if position and isinstance( position, dict) and "x" in position and "y" in position: posX = position["x"] posY = position["y"] else: posX = None posY = None if on_progress: if not on_progress_args: on_progress_args = () if not on_progress_kwargs: on_progress_kwargs = dict() self._cura_logger.info( u"### Slicing %s to %s using profile stored at %s" % (model_path, machinecode_path, profile_path)) executable = normalize_path(self._settings.get(["cura_engine" ])) if not executable: return False, "Path to CuraEngine is not configured " working_dir = os.path.dirname(executable) engine_settings = self._convert_to_engine( profile_path, printer_profile, posX, posY) # Start building the argument list for the CuraEngine command execution args = [executable, '-v', '-p'] # Add the settings (sorted alphabetically) to the command for k, v in sorted(engine_settings.items(), key=lambda s: s[0]): args += ["-s", "%s=%s" % (k, str(v))] args += ["-o", machinecode_path, model_path] self._logger.info(u"Running %r in %s" % (" ".join(args), working_dir)) import sarge p = sarge.run(args, cwd=working_dir, async=True, stdout=sarge.Capture(), stderr=sarge.Capture()) p.wait_events() self._slicing_commands[machinecode_path] = p.commands[0] try: layer_count = None step_factor = dict(inset=0, skin=1, export=2) analysis = None while p.returncode is None: line = p.stderr.readline(timeout=0.5) if not line: p.commands[0].poll() continue line = octoprint.util.to_unicode(line, errors="replace") self._cura_logger.debug(line.strip()) if on_progress is not None: # The Cura slicing process has three individual steps, each consisting of <layer_count> substeps: # # - inset # - skin # - export # # So each layer will be processed three times, once for each step, resulting in a total amount of # substeps of 3 * <layer_count>. # # The CuraEngine reports the calculated layer count and the continuous progress on stderr. # The layer count gets reported right at the beginning in a line of the format: # # Layer count: <layer_count> # # The individual progress per each of the three steps gets reported on stderr in a line of # the format: # # Progress:<step>:<current_layer>:<layer_count> # # Thus, for determining the overall progress the following formula applies: # # progress = <step_factor> * <layer_count> + <current_layer> / <layer_count> * 3 # # with <step_factor> being 0 for "inset", 1 for "skin" and 2 for "export". if line.startswith( u"Layer count:") and layer_count is None: try: layer_count = float( line[len(u"Layer count:"):].strip()) except: pass elif line.startswith(u"Progress:"): split_line = line[len(u"Progress:"):].strip( ).split(":") if len(split_line) == 3: step, current_layer, _ = split_line try: current_layer = float(current_layer) except: pass else: if not step in step_factor: continue on_progress_kwargs["_progress"] = ( step_factor[step] * layer_count + current_layer) / (layer_count * 3) on_progress(*on_progress_args, **on_progress_kwargs) elif line.startswith(u"Print time:"): try: print_time = int( line[len(u"Print time:"):].strip()) if analysis is None: analysis = dict() analysis["estimatedPrintTime"] = print_time except: pass # Get the filament usage elif line.startswith(u"Filament:") or line.startswith( u"Filament2:"): if line.startswith(u"Filament:"): filament_str = line[len(u"Filament:"):].strip() tool_key = "tool0" else: filament_str = line[len(u"Filament2:"):].strip( ) tool_key = "tool1" try: filament = int(filament_str) if analysis is None: analysis = dict() if not "filament" in analysis: analysis["filament"] = dict() if not tool_key in analysis["filament"]: analysis["filament"][tool_key] = dict() if profile.get_float( "filament_diameter") != None: if profile.get( "gcode_flavor" ) == GcodeFlavors.ULTIGCODE or profile.get( "gcode_flavor" ) == GcodeFlavors.REPRAP_VOLUME: analysis["filament"][ tool_key] = _get_usage_from_volume( filament, profile.get_float( "filament_diameter")) else: analysis["filament"][ tool_key] = _get_usage_from_length( filament, profile.get_float( "filament_diameter")) except: pass finally: p.close() with self._job_mutex: if machinecode_path in self._cancelled_jobs: self._cura_logger.info(u"### Cancelled") raise octoprint.slicing.SlicingCancelled() self._cura_logger.info(u"### Finished, returncode %d" % p.returncode) if p.returncode == 0: return True, dict(analysis=analysis) else: self._logger.warn( u"Could not slice via Cura, got return code %r" % p.returncode) return False, "Got returncode %r" % p.returncode except octoprint.slicing.SlicingCancelled as e: raise e except: self._logger.exception( u"Could not slice via Cura, got an unknown error") return False, "Unknown error, please consult the log file" finally: with self._job_mutex: if machinecode_path in self._cancelled_jobs: self._cancelled_jobs.remove(machinecode_path) if machinecode_path in self._slicing_commands: del self._slicing_commands[machinecode_path] self._cura_logger.info("-" * 40)