SliceInfo.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from cura.CuraApplication import CuraApplication
  4. from cura.Settings.ExtruderManager import ExtruderManager
  5. from UM.Extension import Extension
  6. from UM.Application import Application
  7. from UM.Preferences import Preferences
  8. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  9. from UM.Message import Message
  10. from UM.i18n import i18nCatalog
  11. from UM.Logger import Logger
  12. import time
  13. from UM.Qt.Duration import DurationFormat
  14. from .SliceInfoJob import SliceInfoJob
  15. import platform
  16. import math
  17. import urllib.request
  18. import urllib.parse
  19. import json
  20. catalog = i18nCatalog("cura")
  21. ## This Extension runs in the background and sends several bits of information to the Ultimaker servers.
  22. # The data is only sent when the user in question gave permission to do so. All data is anonymous and
  23. # no model files are being sent (Just a SHA256 hash of the model).
  24. class SliceInfo(Extension):
  25. info_url = "https://stats.ultimaker.com/api/cura"
  26. def __init__(self):
  27. super().__init__()
  28. Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
  29. Preferences.getInstance().addPreference("info/send_slice_info", True)
  30. Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
  31. if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
  32. self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymised slicing statistics. You can disable this in the preferences."),
  33. lifetime = 0,
  34. dismissable = False,
  35. title = catalog.i18nc("@info:title", "Collecting Data"))
  36. self.send_slice_info_message.addAction("Dismiss", catalog.i18nc("@action:button", "Dismiss"), None, "")
  37. self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
  38. self.send_slice_info_message.show()
  39. def messageActionTriggered(self, message_id, action_id):
  40. self.send_slice_info_message.hide()
  41. Preferences.getInstance().setValue("info/asked_send_slice_info", True)
  42. def _onWriteStarted(self, output_device):
  43. try:
  44. if not Preferences.getInstance().getValue("info/send_slice_info"):
  45. Logger.log("d", "'info/send_slice_info' is turned off.")
  46. return # Do nothing, user does not want to send data
  47. global_container_stack = Application.getInstance().getGlobalContainerStack()
  48. print_information = Application.getInstance().getPrintInformation()
  49. data = dict() # The data that we're going to submit.
  50. data["time_stamp"] = time.time()
  51. data["schema_version"] = 0
  52. data["cura_version"] = Application.getInstance().getVersion()
  53. active_mode = Preferences.getInstance().getValue("cura/active_mode")
  54. if active_mode == 0:
  55. data["active_mode"] = "recommended"
  56. else:
  57. data["active_mode"] = "custom"
  58. definition_changes = global_container_stack.definitionChanges
  59. machine_settings_changed_by_user = False
  60. if definition_changes.getId() != "empty":
  61. # Now a definition_changes container will always be created for a stack,
  62. # so we also need to check if there is any instance in the definition_changes container
  63. if definition_changes.getAllKeys():
  64. machine_settings_changed_by_user = True
  65. data["machine_settings_changed_by_user"] = machine_settings_changed_by_user
  66. data["language"] = Preferences.getInstance().getValue("general/language")
  67. data["os"] = {"type": platform.system(), "version": platform.version()}
  68. data["active_machine"] = {"definition_id": global_container_stack.definition.getId(), "manufacturer": global_container_stack.definition.getMetaData().get("manufacturer","")}
  69. data["extruders"] = []
  70. extruder_count = len(global_container_stack.extruders)
  71. extruders = []
  72. if extruder_count > 1:
  73. extruders = list(ExtruderManager.getInstance().getMachineExtruders(global_container_stack.getId()))
  74. extruders = sorted(extruders, key = lambda extruder: extruder.getMetaDataEntry("position"))
  75. if not extruders:
  76. extruders = [global_container_stack]
  77. for extruder in extruders:
  78. extruder_dict = dict()
  79. extruder_dict["active"] = ExtruderManager.getInstance().getActiveExtruderStack() == extruder
  80. extruder_dict["material"] = {"GUID": extruder.material.getMetaData().get("GUID", ""),
  81. "type": extruder.material.getMetaData().get("material", ""),
  82. "brand": extruder.material.getMetaData().get("brand", "")
  83. }
  84. extruder_dict["material_used"] = print_information.materialLengths[int(extruder.getMetaDataEntry("position", "0"))]
  85. extruder_dict["variant"] = extruder.variant.getName()
  86. extruder_dict["nozzle_size"] = extruder.getProperty("machine_nozzle_size", "value")
  87. extruder_settings = dict()
  88. extruder_settings["wall_line_count"] = extruder.getProperty("wall_line_count", "value")
  89. extruder_settings["retraction_enable"] = extruder.getProperty("retraction_enable", "value")
  90. extruder_settings["infill_sparse_density"] = extruder.getProperty("infill_sparse_density", "value")
  91. extruder_settings["infill_pattern"] = extruder.getProperty("infill_pattern", "value")
  92. extruder_settings["gradual_infill_steps"] = extruder.getProperty("gradual_infill_steps", "value")
  93. extruder_settings["default_material_print_temperature"] = extruder.getProperty("default_material_print_temperature", "value")
  94. extruder_settings["material_print_temperature"] = extruder.getProperty("material_print_temperature", "value")
  95. extruder_dict["extruder_settings"] = extruder_settings
  96. data["extruders"].append(extruder_dict)
  97. data["quality_profile"] = global_container_stack.quality.getMetaData().get("quality_type")
  98. data["models"] = []
  99. # Listing all files placed on the build plate
  100. for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()):
  101. if node.callDecoration("isSliceable"):
  102. model = dict()
  103. model["hash"] = node.getMeshData().getHash()
  104. bounding_box = node.getBoundingBox()
  105. model["bounding_box"] = {"minimum": {"x": bounding_box.minimum.x,
  106. "y": bounding_box.minimum.y,
  107. "z": bounding_box.minimum.z},
  108. "maximum": {"x": bounding_box.maximum.x,
  109. "y": bounding_box.maximum.y,
  110. "z": bounding_box.maximum.z}}
  111. model["transformation"] = {"data": str(node.getWorldTransformation().getData()).replace("\n", "")}
  112. extruder_position = node.callDecoration("getActiveExtruderPosition")
  113. model["extruder"] = 0 if extruder_position is None else int(extruder_position)
  114. model_settings = dict()
  115. model_stack = node.callDecoration("getStack")
  116. if model_stack:
  117. model_settings["support_enabled"] = model_stack.getProperty("support_enable", "value")
  118. model_settings["support_extruder_nr"] = int(model_stack.getProperty("support_extruder_nr", "value"))
  119. # Mesh modifiers;
  120. model_settings["infill_mesh"] = model_stack.getProperty("infill_mesh", "value")
  121. model_settings["cutting_mesh"] = model_stack.getProperty("cutting_mesh", "value")
  122. model_settings["support_mesh"] = model_stack.getProperty("support_mesh", "value")
  123. model_settings["anti_overhang_mesh"] = model_stack.getProperty("anti_overhang_mesh", "value")
  124. model_settings["wall_line_count"] = model_stack.getProperty("wall_line_count", "value")
  125. model_settings["retraction_enable"] = model_stack.getProperty("retraction_enable", "value")
  126. # Infill settings
  127. model_settings["infill_sparse_density"] = model_stack.getProperty("infill_sparse_density", "value")
  128. model_settings["infill_pattern"] = model_stack.getProperty("infill_pattern", "value")
  129. model_settings["gradual_infill_steps"] = model_stack.getProperty("gradual_infill_steps", "value")
  130. model["model_settings"] = model_settings
  131. data["models"].append(model)
  132. print_times = print_information._print_time_message_values
  133. data["print_times"] = {"travel": int(print_times["travel"].getDisplayString(DurationFormat.Format.Seconds)),
  134. "support": int(print_times["support"].getDisplayString(DurationFormat.Format.Seconds)),
  135. "infill": int(print_times["infill"].getDisplayString(DurationFormat.Format.Seconds)),
  136. "total": int(print_information.currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))}
  137. print_settings = dict()
  138. print_settings["layer_height"] = global_container_stack.getProperty("layer_height", "value")
  139. # Support settings
  140. print_settings["support_enabled"] = global_container_stack.getProperty("support_enable", "value")
  141. print_settings["support_extruder_nr"] = int(global_container_stack.getProperty("support_extruder_nr", "value"))
  142. # Platform adhesion settings
  143. print_settings["adhesion_type"] = global_container_stack.getProperty("adhesion_type", "value")
  144. # Shell settings
  145. print_settings["wall_line_count"] = global_container_stack.getProperty("wall_line_count", "value")
  146. print_settings["retraction_enable"] = global_container_stack.getProperty("retraction_enable", "value")
  147. # Prime tower settings
  148. print_settings["prime_tower_enable"] = global_container_stack.getProperty("prime_tower_enable", "value")
  149. # Infill settings
  150. print_settings["infill_sparse_density"] = global_container_stack.getProperty("infill_sparse_density", "value")
  151. print_settings["infill_pattern"] = global_container_stack.getProperty("infill_pattern", "value")
  152. print_settings["gradual_infill_steps"] = global_container_stack.getProperty("gradual_infill_steps", "value")
  153. print_settings["print_sequence"] = global_container_stack.getProperty("print_sequence", "value")
  154. data["print_settings"] = print_settings
  155. # Send the name of the output device type that is used.
  156. data["output_to"] = type(output_device).__name__
  157. # Convert data to bytes
  158. binary_data = json.dumps(data).encode("utf-8")
  159. # Sending slice info non-blocking
  160. reportJob = SliceInfoJob(self.info_url, binary_data)
  161. reportJob.start()
  162. except Exception:
  163. # We really can't afford to have a mistake here, as this would break the sending of g-code to a device
  164. # (Either saving or directly to a printer). The functionality of the slice data is not *that* important.
  165. Logger.logException("e", "Exception raised while sending slice info.") # But we should be notified about these problems of course.