CuraSceneNode.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. # Copyright (c) 2020 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from copy import deepcopy
  4. from typing import cast, Dict, List, Optional
  5. from UM.Application import Application
  6. from UM.Math.AxisAlignedBox import AxisAlignedBox
  7. from UM.Math.Polygon import Polygon # For typing.
  8. from UM.Scene.SceneNode import SceneNode
  9. from UM.Scene.SceneNodeDecorator import SceneNodeDecorator # To cast the deepcopy of every decorator back to SceneNodeDecorator.
  10. import cura.CuraApplication # To get the build plate.
  11. from UM.Scene.SceneNodeSettings import SceneNodeSettings
  12. from cura.Settings.ExtruderStack import ExtruderStack # For typing.
  13. from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator # For per-object settings.
  14. class CuraSceneNode(SceneNode):
  15. """Scene nodes that are models are only seen when selecting the corresponding build plate
  16. Note that many other nodes can just be UM SceneNode objects.
  17. """
  18. def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "", no_setting_override: bool = False) -> None:
  19. super().__init__(parent = parent, visible = visible, name = name)
  20. if not no_setting_override:
  21. self.addDecorator(SettingOverrideDecorator()) # Now we always have a getActiveExtruderPosition, unless explicitly disabled
  22. self._outside_buildarea = False
  23. self._print_order = 0
  24. def setOutsideBuildArea(self, new_value: bool) -> None:
  25. self._outside_buildarea = new_value
  26. @property
  27. def printOrder(self):
  28. return self._print_order
  29. @printOrder.setter
  30. def printOrder(self, new_value):
  31. self._print_order = new_value
  32. def isOutsideBuildArea(self) -> bool:
  33. return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
  34. @property
  35. def isDropDownEnabled(self) ->bool:
  36. return self.getSetting(SceneNodeSettings.AutoDropDown, Application.getInstance().getPreferences().getValue("physics/automatic_drop_down"))
  37. def isVisible(self) -> bool:
  38. return super().isVisible() and self.callDecoration("getBuildPlateNumber") == cura.CuraApplication.CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
  39. def isSelectable(self) -> bool:
  40. return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == cura.CuraApplication.CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
  41. def isSupportMesh(self) -> bool:
  42. per_mesh_stack = self.callDecoration("getStack")
  43. if not per_mesh_stack:
  44. return False
  45. return per_mesh_stack.getProperty("support_mesh", "value")
  46. def getPrintingExtruder(self) -> Optional[ExtruderStack]:
  47. """Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned
  48. TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded
  49. """
  50. global_container_stack = Application.getInstance().getGlobalContainerStack()
  51. if global_container_stack is None:
  52. return None
  53. per_mesh_stack = self.callDecoration("getStack")
  54. extruders = global_container_stack.extruderList
  55. # Use the support extruder instead of the active extruder if this is a support_mesh
  56. if per_mesh_stack:
  57. if per_mesh_stack.getProperty("support_mesh", "value"):
  58. return extruders[int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))]
  59. # It's only set if you explicitly choose an extruder
  60. extruder_id = self.callDecoration("getActiveExtruder")
  61. for extruder in extruders:
  62. # Find out the extruder if we know the id.
  63. if extruder_id is not None:
  64. if extruder_id == extruder.getId():
  65. return extruder
  66. else: # If the id is unknown, then return the extruder in the position 0
  67. try:
  68. if extruder.getMetaDataEntry("position", default = "0") == "0": # Check if the position is zero
  69. return extruder
  70. except ValueError:
  71. continue
  72. # This point should never be reached
  73. return None
  74. def getDiffuseColor(self) -> List[float]:
  75. """Return the color of the material used to print this model"""
  76. printing_extruder = self.getPrintingExtruder()
  77. material_color = "#808080" # Fallback color
  78. if printing_extruder is not None and printing_extruder.material:
  79. material_color = printing_extruder.material.getMetaDataEntry("color_code", default = material_color)
  80. # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs
  81. # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0])
  82. return [
  83. int(material_color[1:3], 16) / 255,
  84. int(material_color[3:5], 16) / 255,
  85. int(material_color[5:7], 16) / 255,
  86. 1.0
  87. ]
  88. def collidesWithAreas(self, areas: List[Polygon]) -> bool:
  89. """Return if any area collides with the convex hull of this scene node"""
  90. convex_hull = self.callDecoration("getPrintingArea")
  91. if convex_hull:
  92. if not convex_hull.isValid():
  93. return False
  94. # Check for collisions between provided areas and the object
  95. for area in areas:
  96. overlap = convex_hull.intersectsPolygon(area)
  97. if overlap is None:
  98. continue
  99. return True
  100. return False
  101. def _calculateAABB(self) -> None:
  102. """Override of SceneNode._calculateAABB to exclude non-printing-meshes from bounding box"""
  103. self._aabb = None
  104. if self._mesh_data:
  105. self._aabb = self._mesh_data.getExtents(self.getWorldTransformation(copy = False))
  106. for child in self.getAllChildren():
  107. if child.callDecoration("isNonPrintingMesh"):
  108. # Non-printing-meshes inside a group should not affect push apart or drop to build plate
  109. continue
  110. child_bb = child.getBoundingBox()
  111. if child_bb is None or child_bb.minimum == child_bb.maximum:
  112. # Child had a degenerate bounding box, such as an empty group. Don't count it along.
  113. continue
  114. if self._aabb is None:
  115. self._aabb = child_bb
  116. else:
  117. self._aabb = self._aabb + child_bb
  118. if self._aabb is None: # No children that should be included? Just use your own position then, but it's an invalid AABB.
  119. position = self.getWorldPosition()
  120. self._aabb = AxisAlignedBox(minimum = position, maximum = position)
  121. def __deepcopy__(self, memo: Dict[int, object]) -> "CuraSceneNode":
  122. """Taken from SceneNode, but replaced SceneNode with CuraSceneNode"""
  123. copy = CuraSceneNode(no_setting_override = True) # Setting override will be added later
  124. copy.setTransformation(self.getLocalTransformation(copy= False))
  125. copy.setMeshData(self._mesh_data)
  126. copy.setVisible(cast(bool, deepcopy(self._visible, memo)))
  127. copy.source_mime_type = cast(str, deepcopy(self.source_mime_type, memo))
  128. copy._selectable = cast(bool, deepcopy(self._selectable, memo))
  129. copy._name = cast(str, deepcopy(self._name, memo))
  130. for decorator in self._decorators:
  131. copy.addDecorator(cast(SceneNodeDecorator, deepcopy(decorator, memo)))
  132. for child in self._children:
  133. copy.addChild(cast(SceneNode, deepcopy(child, memo)))
  134. self.calculateBoundingBoxMesh()
  135. return copy
  136. def transformChanged(self) -> None:
  137. self._transformChanged()
  138. def __repr__(self) -> str:
  139. return "{print_order}. {name}".format(print_order = self._print_order, name = self.getName())