SliceInfo.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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.Scene.SceneNode import SceneNode
  9. from UM.Message import Message
  10. from UM.i18n import i18nCatalog
  11. from UM.Logger import Logger
  12. from UM.Platform import Platform
  13. from UM.Qt.Duration import DurationFormat
  14. from UM.Job import Job
  15. import platform
  16. import math
  17. import urllib.request
  18. import urllib.parse
  19. import ssl
  20. import hashlib
  21. catalog = i18nCatalog("cura")
  22. class SliceInfoJob(Job):
  23. data = None
  24. url = None
  25. def __init__(self, url, data):
  26. super().__init__()
  27. self.url = url
  28. self.data = data
  29. def run(self):
  30. if not self.url or not self.data:
  31. Logger.log("e", "URL or DATA for sending slice info was not set!")
  32. return
  33. # Submit data
  34. kwoptions = {"data" : self.data,
  35. "timeout" : 5
  36. }
  37. if Platform.isOSX():
  38. kwoptions["context"] = ssl._create_unverified_context()
  39. Logger.log("d", "Sending anonymous slice info to [%s]...", self.url)
  40. try:
  41. f = urllib.request.urlopen(self.url, **kwoptions)
  42. Logger.log("i", "Sent anonymous slice info.")
  43. f.close()
  44. except urllib.error.HTTPError as http_exception:
  45. Logger.log("e", "An HTTP error occurred while trying to send slice information: %s" % http_exception)
  46. except Exception as e: # We don't want any exception to cause problems
  47. Logger.log("e", "An exception occurred while trying to send slice information: %s" % e)
  48. ## This Extension runs in the background and sends several bits of information to the Ultimaker servers.
  49. # The data is only sent when the user in question gave permission to do so. All data is anonymous and
  50. # no model files are being sent (Just a SHA256 hash of the model).
  51. class SliceInfo(Extension):
  52. info_url = "http://stats.youmagine.com/curastats/slice"
  53. def __init__(self):
  54. super().__init__()
  55. Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
  56. Preferences.getInstance().addPreference("info/send_slice_info", True)
  57. Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
  58. if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
  59. self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura collects anonymised slicing statistics. You can disable this in preferences"), lifetime = 0, dismissable = False)
  60. self.send_slice_info_message.addAction("Dismiss", catalog.i18nc("@action:button", "Dismiss"), None, "")
  61. self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
  62. self.send_slice_info_message.show()
  63. def messageActionTriggered(self, message_id, action_id):
  64. self.send_slice_info_message.hide()
  65. Preferences.getInstance().setValue("info/asked_send_slice_info", True)
  66. def _onWriteStarted(self, output_device):
  67. try:
  68. if not Preferences.getInstance().getValue("info/send_slice_info"):
  69. Logger.log("d", "'info/send_slice_info' is turned off.")
  70. return # Do nothing, user does not want to send data
  71. # Listing all files placed on the buildplate
  72. modelhashes = []
  73. for node in DepthFirstIterator(CuraApplication.getInstance().getController().getScene().getRoot()):
  74. if type(node) is not SceneNode or not node.getMeshData():
  75. continue
  76. modelhashes.append(node.getMeshData().getHash())
  77. # Creating md5sums and formatting them as discussed on JIRA
  78. modelhash_formatted = ",".join(modelhashes)
  79. global_container_stack = Application.getInstance().getGlobalContainerStack()
  80. # Get total material used (in mm^3)
  81. print_information = Application.getInstance().getPrintInformation()
  82. material_radius = 0.5 * global_container_stack.getProperty("material_diameter", "value")
  83. # TODO: Send material per extruder instead of mashing it on a pile
  84. material_used = math.pi * material_radius * material_radius * sum(print_information.materialLengths) #Volume of all materials used
  85. # Bundle the collected data
  86. submitted_data = {
  87. "processor": platform.processor(),
  88. "machine": platform.machine(),
  89. "platform": platform.platform(),
  90. "settings": global_container_stack.serialize(), # global_container with references on used containers
  91. "version": Application.getInstance().getVersion(),
  92. "modelhash": modelhash_formatted,
  93. "printtime": print_information.currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601),
  94. "filament": material_used,
  95. "language": Preferences.getInstance().getValue("general/language"),
  96. }
  97. for container in global_container_stack.getContainers():
  98. container_id = container.getId()
  99. try:
  100. container_serialized = container.serialize()
  101. except NotImplementedError:
  102. Logger.log("w", "Container %s could not be serialized!", container_id)
  103. continue
  104. if container_serialized:
  105. submitted_data["settings_%s" %(container_id)] = container_serialized # This can be anything, eg. INI, JSON, etc.
  106. else:
  107. Logger.log("i", "No data found in %s to be serialized!", container_id)
  108. # Convert data to bytes
  109. submitted_data = urllib.parse.urlencode(submitted_data)
  110. binary_data = submitted_data.encode("utf-8")
  111. # Sending slice info non-blocking
  112. reportJob = SliceInfoJob(self.info_url, binary_data)
  113. reportJob.start()
  114. except Exception as e:
  115. # We really can't afford to have a mistake here, as this would break the sending of g-code to a device
  116. # (Either saving or directly to a printer). The functionality of the slice data is not *that* important.
  117. Logger.log("e", "Exception raised while sending slice info: %s" %(repr(e))) # But we should be notified about these problems of course.