diff --git a/plugins/SliceInfoPlugin/SliceInfo.py b/plugins/SliceInfoPlugin/SliceInfo.py index 7abb218dab..83f17dcb27 100755 --- a/plugins/SliceInfoPlugin/SliceInfo.py +++ b/plugins/SliceInfoPlugin/SliceInfo.py @@ -2,6 +2,7 @@ # Cura is released under the terms of the AGPLv3 or higher. from cura.CuraApplication import CuraApplication +from cura.Settings.ExtruderManager import ExtruderManager from UM.Extension import Extension from UM.Application import Application @@ -11,6 +12,8 @@ from UM.Message import Message from UM.i18n import i18nCatalog from UM.Logger import Logger +import time + from UM.Qt.Duration import DurationFormat from .SliceInfoJob import SliceInfoJob @@ -28,7 +31,7 @@ catalog = i18nCatalog("cura") # The data is only sent when the user in question gave permission to do so. All data is anonymous and # no model files are being sent (Just a SHA256 hash of the model). class SliceInfo(Extension): - info_url = "https://stats.youmagine.com/curastats/slice" + info_url = "http://stats.ultimaker.com/api/cura" def __init__(self): super().__init__() @@ -50,61 +53,139 @@ class SliceInfo(Extension): try: if not Preferences.getInstance().getValue("info/send_slice_info"): Logger.log("d", "'info/send_slice_info' is turned off.") - return # Do nothing, user does not want to send data - - # Listing all files placed on the buildplate - modelhashes = [] - for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()): - if node.callDecoration("isSliceable"): - modelhashes.append(node.getMeshData().getHash()) - - # Creating md5sums and formatting them as discussed on JIRA - modelhash_formatted = ",".join(modelhashes) + return # Do nothing, user does not want to send data global_container_stack = Application.getInstance().getGlobalContainerStack() - - # Get total material used (in mm^3) print_information = Application.getInstance().getPrintInformation() - material_radius = 0.5 * global_container_stack.getProperty("material_diameter", "value") - # Send material per extruder - material_used = [str(math.pi * material_radius * material_radius * material_length) for material_length in print_information.materialLengths] - material_used = ",".join(material_used) + data = dict() # The data that we're going to submit. + data["time_stamp"] = time.time() + data["schema_version"] = 0 + data["cura_version"] = Application.getInstance().getVersion() - containers = { "": global_container_stack.serialize() } - for container in global_container_stack.getContainers(): - container_id = container.getId() - try: - container_serialized = container.serialize() - except NotImplementedError: - Logger.log("w", "Container %s could not be serialized!", container_id) - continue - if container_serialized: - containers[container_id] = container_serialized - else: - Logger.log("i", "No data found in %s to be serialized!", container_id) + active_mode = Preferences.getInstance().getValue("cura/active_mode") + if active_mode == 0: + data["active_mode"] = "recommended" + else: + data["active_mode"] = "custom" - # Bundle the collected data - submitted_data = { - "processor": platform.processor(), - "machine": platform.machine(), - "platform": platform.platform(), - "settings": json.dumps(containers), # bundle of containers with their serialized contents - "version": Application.getInstance().getVersion(), - "modelhash": modelhash_formatted, - "printtime": print_information.currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601), - "filament": material_used, - "language": Preferences.getInstance().getValue("general/language"), - } + data["machine_settings_changed_by_user"] = global_container_stack.definitionChanges.getId() != "empty" + data["language"] = Preferences.getInstance().getValue("general/language") + data["os"] = {"type": platform.system(), "version": platform.version()} + + data["active_machine"] = {"definition_id": global_container_stack.definition.getId(), "manufacturer": global_container_stack.definition.getMetaData().get("manufacturer","")} + + data["extruders"] = [] + extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId())) + extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position")) + + if not extruders: + extruders = [global_container_stack] + + for extruder in extruders: + extruder_dict = dict() + extruder_dict["active"] = ExtruderManager.getInstance().getActiveExtruderStack() == extruder + extruder_dict["material"] = {"GUID": extruder.material.getMetaData().get("GUID", ""), + "type": extruder.material.getMetaData().get("material", ""), + "brand": extruder.material.getMetaData().get("brand", "") + } + extruder_dict["material_used"] = print_information.materialLengths[int(extruder.getMetaDataEntry("position", "0"))] + extruder_dict["variant"] = extruder.variant.getName() + extruder_dict["nozzle_size"] = extruder.getProperty("machine_nozzle_size", "value") + + extruder_settings = dict() + extruder_settings["wall_line_count"] = extruder.getProperty("wall_line_count", "value") + extruder_settings["retraction_enable"] = extruder.getProperty("retraction_enable", "value") + extruder_settings["infill_sparse_density"] = extruder.getProperty("infill_sparse_density", "value") + extruder_settings["infill_pattern"] = extruder.getProperty("infill_pattern", "value") + extruder_settings["gradual_infill_steps"] = extruder.getProperty("gradual_infill_steps", "value") + extruder_settings["default_material_print_temperature"] = extruder.getProperty("default_material_print_temperature", "value") + extruder_settings["material_print_temperature"] = extruder.getProperty("material_print_temperature", "value") + extruder_dict["extruder_settings"] = extruder_settings + data["extruders"].append(extruder_dict) + + data["quality_profile"] = global_container_stack.quality.getMetaData().get("quality_type") + + data["models"] = [] + # Listing all files placed on the build plate + for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()): + if node.callDecoration("isSliceable"): + model = dict() + model["hash"] = node.getMeshData().getHash() + bounding_box = node.getBoundingBox() + model["bounding_box"] = {"minimum": {"x": bounding_box.minimum.x, + "y": bounding_box.minimum.y, + "z": bounding_box.minimum.z}, + "maximum": {"x": bounding_box.maximum.x, + "y": bounding_box.maximum.y, + "z": bounding_box.maximum.z}} + model["transformation"] = {"data": str(node.getWorldTransformation().getData()).replace("\n", "")} + extruder_position = node.callDecoration("getActiveExtruderPosition") + model["extruder"] = 0 if extruder_position is None else int(extruder_position) + + model_settings = dict() + model_stack = node.callDecoration("getStack") + if model_stack: + model_settings["support_enabled"] = model_stack.getProperty("support_enable", "value") + model_settings["support_extruder_nr"] = int(model_stack.getProperty("support_extruder_nr", "value")) + + # Mesh modifiers; + model_settings["infill_mesh"] = model_stack.getProperty("infill_mesh", "value") + model_settings["cutting_mesh"] = model_stack.getProperty("cutting_mesh", "value") + model_settings["support_mesh"] = model_stack.getProperty("support_mesh", "value") + model_settings["anti_overhang_mesh"] = model_stack.getProperty("anti_overhang_mesh", "value") + + model_settings["wall_line_count"] = model_stack.getProperty("wall_line_count", "value") + model_settings["retraction_enable"] = model_stack.getProperty("retraction_enable", "value") + + # Infill settings + model_settings["infill_sparse_density"] = model_stack.getProperty("infill_sparse_density", "value") + model_settings["infill_pattern"] = model_stack.getProperty("infill_pattern", "value") + model_settings["gradual_infill_steps"] = model_stack.getProperty("gradual_infill_steps", "value") + + model["model_settings"] = model_settings + + data["models"].append(model) + + print_times = print_information.printTimesPerFeature + data["print_times"] = {"travel": int(print_times["travel"].getDisplayString(DurationFormat.Format.Seconds)), + "support": int(print_times["support"].getDisplayString(DurationFormat.Format.Seconds)), + "infill": int(print_times["infill"].getDisplayString(DurationFormat.Format.Seconds)), + "total": int(print_information.currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))} + + print_settings = dict() + print_settings["layer_height"] = global_container_stack.getProperty("layer_height", "value") + + # Support settings + print_settings["support_enabled"] = global_container_stack.getProperty("support_enable", "value") + print_settings["support_extruder_nr"] = int(global_container_stack.getProperty("support_extruder_nr", "value")) + + # Platform adhesion settings + print_settings["adhesion_type"] = global_container_stack.getProperty("adhesion_type", "value") + + # Shell settings + print_settings["wall_line_count"] = global_container_stack.getProperty("wall_line_count", "value") + print_settings["retraction_enable"] = global_container_stack.getProperty("retraction_enable", "value") + + # Prime tower settings + print_settings["prime_tower_enable"] = global_container_stack.getProperty("prime_tower_enable", "value") + + # Infill settings + print_settings["infill_sparse_density"] = global_container_stack.getProperty("infill_sparse_density", "value") + print_settings["infill_pattern"] = global_container_stack.getProperty("infill_pattern", "value") + print_settings["gradual_infill_steps"] = global_container_stack.getProperty("gradual_infill_steps", "value") + + print_settings["print_sequence"] = global_container_stack.getProperty("print_sequence", "value") + + data["print_settings"] = print_settings # Convert data to bytes - submitted_data = urllib.parse.urlencode(submitted_data) - binary_data = submitted_data.encode("utf-8") + binary_data = json.dumps(data).encode("utf-8") # Sending slice info non-blocking reportJob = SliceInfoJob(self.info_url, binary_data) reportJob.start() - except Exception as e: + except Exception: # We really can't afford to have a mistake here, as this would break the sending of g-code to a device # (Either saving or directly to a printer). The functionality of the slice data is not *that* important. - Logger.log("e", "Exception raised while sending slice info: %s" %(repr(e))) # But we should be notified about these problems of course. + Logger.logException("e", "Exception raised while sending slice info.") # But we should be notified about these problems of course.