SliceInfo.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  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. import collections
  12. import json
  13. import os.path
  14. import copy
  15. import platform
  16. import math
  17. import urllib.request
  18. import urllib.parse
  19. catalog = i18nCatalog("cura")
  20. ## This Extension runs in the background and sends several bits of information to the Ultimaker servers.
  21. # The data is only sent when the user in question gave permission to do so. All data is anonymous and
  22. # no model files are being sent (Just a SHA256 hash of the model).
  23. class SliceInfo(Extension):
  24. info_url = "https://stats.youmagine.com/curastats/slice"
  25. def __init__(self):
  26. super().__init__()
  27. Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._onWriteStarted)
  28. Preferences.getInstance().addPreference("info/send_slice_info", True)
  29. Preferences.getInstance().addPreference("info/asked_send_slice_info", False)
  30. if not Preferences.getInstance().getValue("info/asked_send_slice_info"):
  31. self.send_slice_info_message = Message(catalog.i18nc("@info", "Cura automatically sends slice info. You can disable this in preferences"), lifetime = 0, dismissable = False)
  32. self.send_slice_info_message.addAction("Dismiss", catalog.i18nc("@action:button", "Dismiss"), None, "")
  33. self.send_slice_info_message.actionTriggered.connect(self.messageActionTriggered)
  34. self.send_slice_info_message.show()
  35. def messageActionTriggered(self, message_id, action_id):
  36. self.send_slice_info_message.hide()
  37. Preferences.getInstance().setValue("info/asked_send_slice_info", True)
  38. def _onWriteStarted(self, output_device):
  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. global_container_stack = Application.getInstance().getGlobalContainerStack()
  43. # Get total material used (in mm^3)
  44. print_information = Application.getInstance().getPrintInformation()
  45. material_radius = 0.5 * global_container_stack.getProperty("material_diameter", "value")
  46. material_used = math.pi * material_radius * material_radius * print_information.materialAmount #Volume of material used
  47. # Get model information (bounding boxes, hashes and transformation matrix)
  48. models_info = []
  49. for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
  50. if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
  51. if not getattr(node, "_outside_buildarea", False):
  52. model_info = {}
  53. model_info["hash"] = node.getMeshData().getHash()
  54. model_info["bounding_box"] = {}
  55. model_info["bounding_box"]["minimum"] = {}
  56. model_info["bounding_box"]["minimum"]["x"] = node.getBoundingBox().minimum.x
  57. model_info["bounding_box"]["minimum"]["y"] = node.getBoundingBox().minimum.y
  58. model_info["bounding_box"]["minimum"]["z"] = node.getBoundingBox().minimum.z
  59. model_info["bounding_box"]["maximum"] = {}
  60. model_info["bounding_box"]["maximum"]["x"] = node.getBoundingBox().maximum.x
  61. model_info["bounding_box"]["maximum"]["y"] = node.getBoundingBox().maximum.y
  62. model_info["bounding_box"]["maximum"]["z"] = node.getBoundingBox().maximum.z
  63. model_info["transformation"] = str(node.getWorldTransformation().getData())
  64. models_info.append(model_info)
  65. # Bundle the collected data
  66. submitted_data = {
  67. "processor": platform.processor(),
  68. "machine": platform.machine(),
  69. "platform": platform.platform(),
  70. "settings": global_container_stack.serialize(), # global_container with references on used containers
  71. "version": Application.getInstance().getVersion(),
  72. "modelhash": "None",
  73. "printtime": print_information.currentPrintTime.getDisplayString(),
  74. "filament": material_used,
  75. "language": Preferences.getInstance().getValue("general/language"),
  76. "materials_profiles ": {}
  77. }
  78. for container in global_container_stack.getContainers():
  79. container_id = container.getId()
  80. try:
  81. container_serialized = container.serialize()
  82. except NotImplementedError:
  83. Logger.log("w", "Container %s could not be serialized!", container_id)
  84. continue
  85. if container_serialized:
  86. submitted_data["settings_%s" %(container_id)] = container_serialized # This can be anything, eg. INI, JSON, etc.
  87. else:
  88. Logger.log("i", "No data found in %s to be serialized!", container_id)
  89. # Convert data to bytes
  90. submitted_data = urllib.parse.urlencode(submitted_data)
  91. binary_data = submitted_data.encode("utf-8")
  92. # Submit data
  93. try:
  94. f = urllib.request.urlopen(self.info_url, data = binary_data, timeout = 1)
  95. Logger.log("i", "Sent anonymous slice info to %s", self.info_url)
  96. f.close()
  97. except Exception as e:
  98. Logger.logException("e", e)