CuraSceneNode.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. # Copyright (c) 2018 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 cura.Settings.ExtruderStack import ExtruderStack #For typing.
  12. from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator #For per-object settings.
  13. ## Scene nodes that are models are only seen when selecting the corresponding build plate
  14. # Note that many other nodes can just be UM SceneNode objects.
  15. class CuraSceneNode(SceneNode):
  16. def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "", no_setting_override: bool = False) -> None:
  17. super().__init__(parent = parent, visible = visible, name = name)
  18. if not no_setting_override:
  19. self.addDecorator(SettingOverrideDecorator()) # now we always have a getActiveExtruderPosition, unless explicitly disabled
  20. self._outside_buildarea = False
  21. def setOutsideBuildArea(self, new_value: bool) -> None:
  22. self._outside_buildarea = new_value
  23. def isOutsideBuildArea(self) -> bool:
  24. return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
  25. def isVisible(self) -> bool:
  26. return super().isVisible() and self.callDecoration("getBuildPlateNumber") == cura.CuraApplication.CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
  27. def isSelectable(self) -> bool:
  28. return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == cura.CuraApplication.CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
  29. ## Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned
  30. # TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded
  31. def getPrintingExtruder(self) -> Optional[ExtruderStack]:
  32. global_container_stack = Application.getInstance().getGlobalContainerStack()
  33. if global_container_stack is None:
  34. return None
  35. per_mesh_stack = self.callDecoration("getStack")
  36. extruders = list(global_container_stack.extruders.values())
  37. # Use the support extruder instead of the active extruder if this is a support_mesh
  38. if per_mesh_stack:
  39. if per_mesh_stack.getProperty("support_mesh", "value"):
  40. return extruders[int(global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr"))]
  41. # It's only set if you explicitly choose an extruder
  42. extruder_id = self.callDecoration("getActiveExtruder")
  43. for extruder in extruders:
  44. # Find out the extruder if we know the id.
  45. if extruder_id is not None:
  46. if extruder_id == extruder.getId():
  47. return extruder
  48. else: # If the id is unknown, then return the extruder in the position 0
  49. try:
  50. if extruder.getMetaDataEntry("position", default = "0") == "0": # Check if the position is zero
  51. return extruder
  52. except ValueError:
  53. continue
  54. # This point should never be reached
  55. return None
  56. ## Return the color of the material used to print this model
  57. def getDiffuseColor(self) -> List[float]:
  58. printing_extruder = self.getPrintingExtruder()
  59. material_color = "#808080" # Fallback color
  60. if printing_extruder is not None and printing_extruder.material:
  61. material_color = printing_extruder.material.getMetaDataEntry("color_code", default = material_color)
  62. # Colors are passed as rgb hex strings (eg "#ffffff"), and the shader needs
  63. # an rgba list of floats (eg [1.0, 1.0, 1.0, 1.0])
  64. return [
  65. int(material_color[1:3], 16) / 255,
  66. int(material_color[3:5], 16) / 255,
  67. int(material_color[5:7], 16) / 255,
  68. 1.0
  69. ]
  70. ## Return if the provided bbox collides with the bbox of this scene node
  71. def collidesWithBbox(self, check_bbox: AxisAlignedBox) -> bool:
  72. bbox = self.getBoundingBox()
  73. if bbox is not None:
  74. # Mark the node as outside the build volume if the bounding box test fails.
  75. if check_bbox.intersectsBox(bbox) != AxisAlignedBox.IntersectionResult.FullIntersection:
  76. return True
  77. return False
  78. ## Return if any area collides with the convex hull of this scene node
  79. def collidesWithArea(self, areas: List[Polygon]) -> bool:
  80. convex_hull = self.callDecoration("getConvexHull")
  81. if convex_hull:
  82. if not convex_hull.isValid():
  83. return False
  84. # Check for collisions between disallowed areas and the object
  85. for area in areas:
  86. overlap = convex_hull.intersectsPolygon(area)
  87. if overlap is None:
  88. continue
  89. return True
  90. return False
  91. ## Override of SceneNode._calculateAABB to exclude non-printing-meshes from bounding box
  92. def _calculateAABB(self) -> None:
  93. if self._mesh_data:
  94. aabb = self._mesh_data.getExtents(self.getWorldTransformation())
  95. else: # If there is no mesh_data, use a boundingbox that encompasses the local (0,0,0)
  96. position = self.getWorldPosition()
  97. aabb = AxisAlignedBox(minimum = position, maximum = position)
  98. for child in self._children:
  99. if child.callDecoration("isNonPrintingMesh"):
  100. # Non-printing-meshes inside a group should not affect push apart or drop to build plate
  101. continue
  102. if aabb is None:
  103. aabb = child.getBoundingBox()
  104. else:
  105. aabb = aabb + child.getBoundingBox()
  106. self._aabb = aabb
  107. ## Taken from SceneNode, but replaced SceneNode with CuraSceneNode
  108. def __deepcopy__(self, memo: Dict[int, object]) -> "CuraSceneNode":
  109. copy = CuraSceneNode(no_setting_override = True) # Setting override will be added later
  110. copy.setTransformation(self.getLocalTransformation())
  111. copy.setMeshData(self._mesh_data)
  112. copy.setVisible(cast(bool, deepcopy(self._visible, memo)))
  113. copy._selectable = cast(bool, deepcopy(self._selectable, memo))
  114. copy._name = cast(str, deepcopy(self._name, memo))
  115. for decorator in self._decorators:
  116. copy.addDecorator(cast(SceneNodeDecorator, deepcopy(decorator, memo)))
  117. for child in self._children:
  118. copy.addChild(cast(SceneNode, deepcopy(child, memo)))
  119. self.calculateBoundingBoxMesh()
  120. return copy
  121. def transformChanged(self) -> None:
  122. self._transformChanged()