SliceInfo.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from UM.Extension import Extension
  4. from UM.Application import Application
  5. from UM.Preferences import Preferences
  6. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  7. from UM.Scene.SceneNode import SceneNode
  8. from UM.Message import Message
  9. from UM.i18n import i18nCatalog
  10. from UM.Logger import Logger
  11. from UM.Platform import Platform
  12. import collections
  13. import json
  14. import os.path
  15. import copy
  16. import platform
  17. import math
  18. import urllib.request
  19. import urllib.parse
  20. import ssl
  21. catalog = i18nCatalog("cura")
  22. ## This Extension runs in the background and sends several bits of information to the Ultimaker servers.
  23. # The data is only sent when the user in question gave permission to do so. All data is anonymous and
  24. # no model files are being sent (Just a SHA256 hash of the model).
  25. class SliceInfo(Extension):
  26. info_url = "https://stats.youmagine.com/curastats/slice"
  27. def __init__(self):
  28. super().__init__()
  29. Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
  30. Preferences.getInstance().addPreference("info/send_slice_info", True)
  31. Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
  32. if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
  33. self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura automatically sends slice info. You can disable this in preferences"), lifetime = 0, dismissable = False)
  34. self.send_slice_info_message.addAction("Dismiss", catalog.i18nc("@action:button", "Dismiss"), None, "")
  35. self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
  36. self.send_slice_info_message.show()
  37. def messageActionTriggered(self, message_id, action_id):
  38. self.send_slice_info_message.hide()
  39. Preferences.getInstance().setValue("info/asked_send_slice_info", True)
  40. def _onWriteStarted(self, output_device):
  41. try:
  42. if not Preferences.getInstance().getValue("info/send_slice_info"):
  43. Logger.log("d", "'info/send_slice_info' is turned off.")
  44. return # Do nothing, user does not want to send data
  45. global_container_stack = Application.getInstance().getGlobalContainerStack()
  46. # Get total material used (in mm^3)
  47. print_information = Application.getInstance().getPrintInformation()
  48. material_radius = 0.5 * global_container_stack.getProperty("material_diameter", "value")
  49. # TODO: Send material per extruder instead of mashing it on a pile
  50. material_used = math.pi * material_radius * material_radius * sum(print_information.materialAmounts) #Volume of all materials used
  51. # Get model information (bounding boxes, hashes and transformation matrix)
  52. models_info = []
  53. for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
  54. if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
  55. if not getattr(node, "_outside_buildarea", False):
  56. model_info = {}
  57. model_info["hash"] = node.getMeshData().getHash()
  58. model_info["bounding_box"] = {}
  59. model_info["bounding_box"]["minimum"] = {}
  60. model_info["bounding_box"]["minimum"]["x"] = node.getBoundingBox().minimum.x
  61. model_info["bounding_box"]["minimum"]["y"] = node.getBoundingBox().minimum.y
  62. model_info["bounding_box"]["minimum"]["z"] = node.getBoundingBox().minimum.z
  63. model_info["bounding_box"]["maximum"] = {}
  64. model_info["bounding_box"]["maximum"]["x"] = node.getBoundingBox().maximum.x
  65. model_info["bounding_box"]["maximum"]["y"] = node.getBoundingBox().maximum.y
  66. model_info["bounding_box"]["maximum"]["z"] = node.getBoundingBox().maximum.z
  67. model_info["transformation"] = str(node.getWorldTransformation().getData())
  68. models_info.append(model_info)
  69. # Bundle the collected data
  70. submitted_data = {
  71. "processor": platform.processor(),
  72. "machine": platform.machine(),
  73. "platform": platform.platform(),
  74. "settings": global_container_stack.serialize(), # global_container with references on used containers
  75. "version": Application.getInstance().getVersion(),
  76. "modelhash": "None",
  77. "printtime": print_information.currentPrintTime.getDisplayString(),
  78. "filament": material_used,
  79. "language": Preferences.getInstance().getValue("general/language"),
  80. "materials_profiles ": {}
  81. }
  82. for container in global_container_stack.getContainers():
  83. container_id = container.getId()
  84. try:
  85. container_serialized = container.serialize()
  86. except NotImplementedError:
  87. Logger.log("w", "Container %s could not be serialized!", container_id)
  88. continue
  89. if container_serialized:
  90. submitted_data["settings_%s" %(container_id)] = container_serialized # This can be anything, eg. INI, JSON, etc.
  91. else:
  92. Logger.log("i", "No data found in %s to be serialized!", container_id)
  93. # Convert data to bytes
  94. submitted_data = urllib.parse.urlencode(submitted_data)
  95. binary_data = submitted_data.encode("utf-8")
  96. # Submit data
  97. kwoptions = {"data" : binary_data,
  98. "timeout" : 1
  99. }
  100. if Platform.isOSX():
  101. kwoptions["context"] = ssl._create_unverified_context()
  102. try:
  103. f = urllib.request.urlopen(self.info_url, **kwoptions)
  104. Logger.log("i", "Sent anonymous slice info to %s", self.info_url)
  105. f.close()
  106. except Exception as e:
  107. Logger.logException("e", "An exception occurred while trying to send slice information")
  108. except:
  109. # We really can't afford to have a mistake here, as this would break the sending of g-code to a device
  110. # (Either saving or directly to a printer). The functionality of the slice data is not *that* important.
  111. pass