SettingOverrideDecorator.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. # Copyright (c) 2016 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import copy
  4. import uuid
  5. from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
  6. from UM.Signal import Signal, signalemitter
  7. from UM.Settings.InstanceContainer import InstanceContainer
  8. from UM.Settings.ContainerRegistry import ContainerRegistry
  9. from UM.Logger import Logger
  10. from UM.Application import Application
  11. from cura.Settings.PerObjectContainerStack import PerObjectContainerStack
  12. from cura.Settings.ExtruderManager import ExtruderManager
  13. @signalemitter
  14. class SettingOverrideDecorator(SceneNodeDecorator):
  15. """A decorator that adds a container stack to a Node. This stack should be queried for all settings regarding
  16. the linked node. The Stack in question will refer to the global stack (so that settings that are not defined by
  17. this stack still resolve.
  18. """
  19. activeExtruderChanged = Signal()
  20. """Event indicating that the user selected a different extruder."""
  21. _non_printing_mesh_settings = {"anti_overhang_mesh", "infill_mesh", "cutting_mesh"}
  22. """Non-printing meshes
  23. If these settings are True for any mesh, the mesh does not need a convex hull,
  24. and is sent to the slicer regardless of whether it fits inside the build volume.
  25. Note that Support Mesh is not in here because it actually generates
  26. g-code in the volume of the mesh.
  27. """
  28. _non_thumbnail_visible_settings = {"anti_overhang_mesh", "infill_mesh", "cutting_mesh", "support_mesh"}
  29. def __init__(self, *, force_update = True):
  30. super().__init__()
  31. self._stack = PerObjectContainerStack(container_id = "per_object_stack_" + str(id(self)))
  32. self._stack.setDirty(False) # This stack does not need to be saved.
  33. user_container = InstanceContainer(container_id = self._generateUniqueName())
  34. user_container.setMetaDataEntry("type", "user")
  35. self._stack.userChanges = user_container
  36. self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
  37. self._is_non_printing_mesh = False
  38. self._is_non_thumbnail_visible_mesh = False
  39. self._is_support_mesh = False
  40. self._is_cutting_mesh = False
  41. self._is_infill_mesh = False
  42. self._is_anti_overhang_mesh = False
  43. self._stack.propertyChanged.connect(self._onSettingChanged)
  44. Application.getInstance().getContainerRegistry().addContainer(self._stack)
  45. Application.getInstance().globalContainerStackChanged.connect(self._updateNextStack)
  46. self.activeExtruderChanged.connect(self._updateNextStack)
  47. if force_update:
  48. self._updateNextStack()
  49. def clearDecoratorData(self) -> None:
  50. super().clearDecoratorData()
  51. self._stack = None
  52. self._extruder_stack = None
  53. self._is_non_printing_mesh = False
  54. self._is_non_thumbnail_visible_mesh = False
  55. self._is_support_mesh = False
  56. self._is_cutting_mesh = False
  57. self._is_infill_mesh = False
  58. self._is_anti_overhang_mesh = False
  59. def _generateUniqueName(self):
  60. return "SettingOverrideInstanceContainer-%s" % uuid.uuid1()
  61. def __deepcopy__(self, memo):
  62. deep_copy = SettingOverrideDecorator(force_update = False)
  63. """Create a fresh decorator object"""
  64. instance_container = copy.deepcopy(self._stack.getContainer(0), memo)
  65. """Copy the instance"""
  66. # A unique name must be added, or replaceContainer will not replace it
  67. instance_container.setMetaDataEntry("id", self._generateUniqueName())
  68. ## Set the copied instance as the first (and only) instance container of the stack.
  69. deep_copy._stack.replaceContainer(0, instance_container)
  70. # Properly set the right extruder on the copy
  71. deep_copy.setActiveExtruder(self._extruder_stack)
  72. return deep_copy
  73. def getActiveExtruder(self):
  74. """Gets the currently active extruder to print this object with.
  75. :return: An extruder's container stack.
  76. """
  77. return self._extruder_stack
  78. def getActiveExtruderChangedSignal(self):
  79. """Gets the signal that emits if the active extruder changed.
  80. This can then be accessed via a decorator.
  81. """
  82. return self.activeExtruderChanged
  83. def getActiveExtruderPosition(self):
  84. """Gets the currently active extruders position
  85. :return: An extruder's position, or None if no position info is available.
  86. """
  87. # for support_meshes, always use the support_extruder
  88. if self._is_support_mesh:
  89. global_container_stack = Application.getInstance().getGlobalContainerStack()
  90. if global_container_stack:
  91. return str(global_container_stack.getProperty("support_extruder_nr", "value"))
  92. containers = ContainerRegistry.getInstance().findContainers(id = self.getActiveExtruder())
  93. if containers:
  94. container_stack = containers[0]
  95. return container_stack.getMetaDataEntry("position", default=None)
  96. def isCuttingMesh(self):
  97. return self._is_cutting_mesh
  98. def isSupportMesh(self):
  99. return self._is_support_mesh
  100. def isInfillMesh(self):
  101. return self._is_infill_mesh
  102. def isAntiOverhangMesh(self):
  103. return self._is_anti_overhang_mesh
  104. def _evaluateAntiOverhangMesh(self):
  105. return bool(self._stack.userChanges.getProperty("anti_overhang_mesh", "value"))
  106. def _evaluateIsCuttingMesh(self):
  107. return bool(self._stack.userChanges.getProperty("cutting_mesh", "value"))
  108. def _evaluateIsSupportMesh(self):
  109. return bool(self._stack.userChanges.getProperty("support_mesh", "value"))
  110. def _evaluateInfillMesh(self):
  111. return bool(self._stack.userChanges.getProperty("infill_mesh", "value"))
  112. def isNonPrintingMesh(self):
  113. return self._is_non_printing_mesh
  114. def _evaluateIsNonPrintingMesh(self):
  115. return any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
  116. def isNonThumbnailVisibleMesh(self):
  117. return self._is_non_thumbnail_visible_mesh
  118. def _evaluateIsNonThumbnailVisibleMesh(self):
  119. return any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_thumbnail_visible_settings)
  120. def _onSettingChanged(self, setting_key, property_name): # Reminder: 'property' is a built-in function
  121. # We're only interested in a few settings and only if it's value changed.
  122. if property_name == "value":
  123. # Trigger slice/need slicing if the value has changed.
  124. self._is_non_printing_mesh = self._evaluateIsNonPrintingMesh()
  125. self._is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh()
  126. if setting_key == "anti_overhang_mesh":
  127. self._is_anti_overhang_mesh = self._evaluateAntiOverhangMesh()
  128. elif setting_key == "support_mesh":
  129. self._is_support_mesh = self._evaluateIsSupportMesh()
  130. elif setting_key == "cutting_mesh":
  131. self._is_cutting_mesh = self._evaluateIsCuttingMesh()
  132. elif setting_key == "infill_mesh":
  133. self._is_infill_mesh = self._evaluateInfillMesh()
  134. Application.getInstance().getBackend().needsSlicing()
  135. Application.getInstance().getBackend().tickle()
  136. def _updateNextStack(self):
  137. """Makes sure that the stack upon which the container stack is placed is
  138. kept up to date.
  139. """
  140. if self._extruder_stack:
  141. extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = self._extruder_stack)
  142. if extruder_stack:
  143. if self._stack.getNextStack():
  144. old_extruder_stack_id = self._stack.getNextStack().getId()
  145. else:
  146. old_extruder_stack_id = ""
  147. self._stack.setNextStack(extruder_stack[0])
  148. # Trigger slice/need slicing if the extruder changed.
  149. if self._stack.getNextStack().getId() != old_extruder_stack_id:
  150. Application.getInstance().getBackend().needsSlicing()
  151. Application.getInstance().getBackend().tickle()
  152. else:
  153. Logger.log("e", "Extruder stack %s below per-object settings does not exist.", self._extruder_stack)
  154. else:
  155. self._stack.setNextStack(Application.getInstance().getGlobalContainerStack())
  156. def setActiveExtruder(self, extruder_stack_id):
  157. """Changes the extruder with which to print this node.
  158. :param extruder_stack_id: The new extruder stack to print with.
  159. """
  160. self._extruder_stack = extruder_stack_id
  161. self._updateNextStack()
  162. ExtruderManager.getInstance().resetSelectedObjectExtruders()
  163. self.activeExtruderChanged.emit()
  164. def getStack(self):
  165. return self._stack