Nest2DArrange.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import numpy
  2. from pynest2d import Point, Box, Item, NfpConfig, nest
  3. from typing import List, TYPE_CHECKING, Optional, Tuple
  4. from UM.Math.Matrix import Matrix
  5. from UM.Math.Polygon import Polygon
  6. from UM.Math.Quaternion import Quaternion
  7. from UM.Math.Vector import Vector
  8. from UM.Operations.GroupedOperation import GroupedOperation
  9. from UM.Operations.RotateOperation import RotateOperation
  10. from UM.Operations.TranslateOperation import TranslateOperation
  11. if TYPE_CHECKING:
  12. from UM.Scene.SceneNode import SceneNode
  13. from cura.BuildVolume import BuildVolume
  14. def findNodePlacement(nodes_to_arrange: List["SceneNode"], build_volume: "BuildVolume", fixed_nodes: Optional[List["SceneNode"]] = None, factor = 10000) -> Tuple[bool, List[Item]]:
  15. """
  16. Find placement for a set of scene nodes, but don't actually move them just yet.
  17. :param nodes_to_arrange: The list of nodes that need to be moved.
  18. :param build_volume: The build volume that we want to place the nodes in. It gets size & disallowed areas from this.
  19. :param fixed_nodes: List of nods that should not be moved, but should be used when deciding where the others nodes
  20. are placed.
  21. :param factor: The library that we use is int based. This factor defines how accurate we want it to be.
  22. :return:
  23. """
  24. machine_width = build_volume.getWidth()
  25. machine_depth = build_volume.getDepth()
  26. build_plate_bounding_box = Box(machine_width * factor, machine_depth * factor)
  27. if fixed_nodes is None:
  28. fixed_nodes = []
  29. # Add all the items we want to arrange
  30. node_items = []
  31. for node in nodes_to_arrange:
  32. hull_polygon = node.callDecoration("getConvexHull")
  33. converted_points = []
  34. for point in hull_polygon.getPoints():
  35. converted_points.append(Point(point[0] * factor, point[1] * factor))
  36. item = Item(converted_points)
  37. node_items.append(item)
  38. # Use a tiny margin for the build_plate_polygon (the nesting doesn't like overlapping disallowed areas)
  39. half_machine_width = 0.5 * machine_width - 1
  40. half_machine_depth = 0.5 * machine_depth - 1
  41. build_plate_polygon = Polygon(numpy.array([
  42. [half_machine_width, -half_machine_depth],
  43. [-half_machine_width, -half_machine_depth],
  44. [-half_machine_width, half_machine_depth],
  45. [half_machine_width, half_machine_depth]
  46. ], numpy.float32))
  47. disallowed_areas = build_volume.getDisallowedAreas()
  48. num_disallowed_areas_added = 0
  49. for area in disallowed_areas:
  50. converted_points = []
  51. # Clip the disallowed areas so that they don't overlap the bounding box (The arranger chokes otherwise)
  52. clipped_area = area.intersectionConvexHulls(build_plate_polygon)
  53. for point in clipped_area.getPoints():
  54. converted_points.append(Point(point[0] * factor, point[1] * factor))
  55. disallowed_area = Item(converted_points)
  56. disallowed_area.markAsFixedInBin(0)
  57. node_items.append(disallowed_area)
  58. num_disallowed_areas_added += 1
  59. for node in fixed_nodes:
  60. converted_points = []
  61. hull_polygon = node.callDecoration("getConvexHull")
  62. for point in hull_polygon.getPoints():
  63. converted_points.append(Point(point[0] * factor, point[1] * factor))
  64. item = Item(converted_points)
  65. node_items.append(item)
  66. item.markAsFixedInBin(0)
  67. node_items.append(item)
  68. num_disallowed_areas_added += 1
  69. config = NfpConfig()
  70. config.accuracy = 1.0
  71. num_bins = nest(node_items, build_plate_bounding_box, 10000, config)
  72. # Strip the disallowed areas from the results again
  73. if num_disallowed_areas_added != 0:
  74. node_items = node_items[:-num_disallowed_areas_added]
  75. found_solution_for_all = num_bins == 1
  76. return found_solution_for_all, node_items
  77. def arrange(nodes_to_arrange: List["SceneNode"], build_volume: "BuildVolume", fixed_nodes: Optional[List["SceneNode"]] = None, factor = 10000) -> bool:
  78. """
  79. Find placement for a set of scene nodes, and move them by using a single grouped operation.
  80. :param nodes_to_arrange: The list of nodes that need to be moved.
  81. :param build_volume: The build volume that we want to place the nodes in. It gets size & disallowed areas from this.
  82. :param fixed_nodes: List of nods that should not be moved, but should be used when deciding where the others nodes
  83. are placed.
  84. :param factor: The library that we use is int based. This factor defines how accuracte we want it to be.
  85. :return:
  86. """
  87. found_solution_for_all, node_items = findNodePlacement(nodes_to_arrange, build_volume, fixed_nodes, factor)
  88. not_fit_count = 0
  89. grouped_operation = GroupedOperation()
  90. for node, node_item in zip(nodes_to_arrange, node_items):
  91. if node_item.binId() == 0:
  92. # We found a spot for it
  93. rotation_matrix = Matrix()
  94. rotation_matrix.setByRotationAxis(node_item.rotation(), Vector(0, -1, 0))
  95. grouped_operation.addOperation(RotateOperation(node, Quaternion.fromMatrix(rotation_matrix)))
  96. grouped_operation.addOperation(TranslateOperation(node, Vector(node_item.translation().x() / factor, 0,
  97. node_item.translation().y() / factor)))
  98. else:
  99. # We didn't find a spot
  100. grouped_operation.addOperation(
  101. TranslateOperation(node, Vector(200, 0, -not_fit_count * 20), set_position=True))
  102. not_fit_count += 1
  103. grouped_operation.push()
  104. return found_solution_for_all