SliceInfo.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from cura.CuraApplication import CuraApplication
  4. from UM.Extension import Extension
  5. from UM.Application import Application
  6. from UM.Preferences import Preferences
  7. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  8. from UM.Message import Message
  9. from UM.i18n import i18nCatalog
  10. from UM.Logger import Logger
  11. from UM.Qt.Duration import DurationFormat
  12. from .SliceInfoJob import SliceInfoJob
  13. import platform
  14. import math
  15. import urllib.request
  16. import urllib.parse
  17. import json
  18. catalog = i18nCatalog("cura")
  19. ## This Extension runs in the background and sends several bits of information to the Ultimaker servers.
  20. # The data is only sent when the user in question gave permission to do so. All data is anonymous and
  21. # no model files are being sent (Just a SHA256 hash of the model).
  22. class SliceInfo(Extension):
  23. info_url = "https://stats.youmagine.com/curastats/slice"
  24. def __init__(self):
  25. super().__init__()
  26. Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
  27. Preferences.getInstance().addPreference("info/send_slice_info", True)
  28. Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
  29. if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
  30. self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymised slicing statistics. You can disable this in preferences"), lifetime = 0, dismissable = False)
  31. self.send_slice_info_message.addAction("Dismiss", catalog.i18nc("@action:button", "Dismiss"), None, "")
  32. self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
  33. self.send_slice_info_message.show()
  34. def messageActionTriggered(self, message_id, action_id):
  35. self.send_slice_info_message.hide()
  36. Preferences.getInstance().setValue("info/asked_send_slice_info", True)
  37. def _onWriteStarted(self, output_device):
  38. try:
  39. if not Preferences.getInstance().getValue("info/send_slice_info"):
  40. Logger.log("d", "'info/send_slice_info' is turned off.")
  41. return # Do nothing, user does not want to send data
  42. # Listing all files placed on the buildplate
  43. modelhashes = []
  44. for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()):
  45. if node.callDecoration("isSliceable"):
  46. modelhashes.append(node.getMeshData().getHash())
  47. # Creating md5sums and formatting them as discussed on JIRA
  48. modelhash_formatted = ",".join(modelhashes)
  49. global_container_stack = Application.getInstance().getGlobalContainerStack()
  50. # Get total material used (in mm^3)
  51. print_information = Application.getInstance().getPrintInformation()
  52. material_radius = 0.5 * global_container_stack.getProperty("material_diameter", "value")
  53. # Send material per extruder
  54. material_used = [str(math.pi * material_radius * material_radius * material_length) for material_length in print_information.materialLengths]
  55. material_used = ",".join(material_used)
  56. containers = { "": global_container_stack.serialize() }
  57. for container in global_container_stack.getContainers():
  58. container_id = container.getId()
  59. try:
  60. container_serialized = container.serialize()
  61. except NotImplementedError:
  62. Logger.log("w", "Container %s could not be serialized!", container_id)
  63. continue
  64. if container_serialized:
  65. containers[container_id] = container_serialized
  66. else:
  67. Logger.log("i", "No data found in %s to be serialized!", container_id)
  68. # Bundle the collected data
  69. submitted_data = {
  70. "processor": platform.processor(),
  71. "machine": platform.machine(),
  72. "platform": platform.platform(),
  73. "settings": json.dumps(containers), # bundle of containers with their serialized contents
  74. "version": Application.getInstance().getVersion(),
  75. "modelhash": modelhash_formatted,
  76. "printtime": print_information.currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601),
  77. "filament": material_used,
  78. "language": Preferences.getInstance().getValue("general/language"),
  79. }
  80. # Convert data to bytes
  81. submitted_data = urllib.parse.urlencode(submitted_data)
  82. binary_data = submitted_data.encode("utf-8")
  83. # Sending slice info non-blocking
  84. reportJob = SliceInfoJob(self.info_url, binary_data)
  85. reportJob.start()
  86. except Exception as e:
  87. # We really can't afford to have a mistake here, as this would break the sending of g-code to a device
  88. # (Either saving or directly to a printer). The functionality of the slice data is not *that* important.
  89. Logger.log("e", "Exception raised while sending slice info: %s" %(repr(e))) # But we should be notified about these problems of course.