# Copyright (c) 2018 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. import copy from typing import List from PyQt5.QtCore import QCoreApplication from UM.Job import Job from UM.Math.Matrix import Matrix from UM.Math.Quaternion import Quaternion from UM.Math.Vector import Vector from UM.Operations.GroupedOperation import GroupedOperation from UM.Message import Message from UM.Operations.RotateOperation import RotateOperation from UM.Operations.TranslateOperation import TranslateOperation from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.SceneNode import SceneNode from UM.i18n import i18nCatalog from cura.Arranging.Nest2DArrange import findNodePlacement i18n_catalog = i18nCatalog("cura") from cura.Arranging.Arrange import Arrange from cura.Arranging.ShapeArray import ShapeArray from UM.Application import Application from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation class MultiplyObjectsJob(Job): def __init__(self, objects, count, min_offset = 8): super().__init__() self._objects = objects self._count = count self._min_offset = min_offset def run(self) -> None: status_message = Message(i18n_catalog.i18nc("@info:status", "Multiplying and placing objects"), lifetime=0, dismissable=False, progress=0, title = i18n_catalog.i18nc("@info:title", "Placing Objects")) status_message.show() scene = Application.getInstance().getController().getScene() total_progress = len(self._objects) * self._count current_progress = 0 global_container_stack = Application.getInstance().getGlobalContainerStack() if global_container_stack is None: return # We can't do anything in this case. machine_width = global_container_stack.getProperty("machine_width", "value") machine_depth = global_container_stack.getProperty("machine_depth", "value") root = scene.getRoot() processed_nodes = [] # type: List[SceneNode] nodes = [] fixed_nodes = [] for node_ in DepthFirstIterator(root): # Only count sliceable objects if node_.callDecoration("isSliceable"): fixed_nodes.append(node_) not_fit_count = 0 found_solution_for_all = False for node in self._objects: # If object is part of a group, multiply group current_node = node while current_node.getParent() and (current_node.getParent().callDecoration("isGroup") or current_node.getParent().callDecoration("isSliceable")): current_node = current_node.getParent() if current_node in processed_nodes: continue processed_nodes.append(current_node) for _ in range(self._count): new_node = copy.deepcopy(node) # Same build plate build_plate_number = current_node.callDecoration("getBuildPlateNumber") new_node.callDecoration("setBuildPlateNumber", build_plate_number) for child in new_node.getChildren(): child.callDecoration("setBuildPlateNumber", build_plate_number) nodes.append(new_node) factor = 10000 found_solution_for_all, node_items = findNodePlacement(nodes, Application.getInstance().getBuildVolume(), fixed_nodes, factor = 10000) if nodes: operation = GroupedOperation() for new_node, node_item in zip(nodes, node_items): operation.addOperation(AddSceneNodeOperation(new_node, root)) if node_item.binId() == 0: # We found a spot for it rotation_matrix = Matrix() rotation_matrix.setByRotationAxis(node_item.rotation(), Vector(0, -1, 0)) operation.addOperation(RotateOperation(new_node, Quaternion.fromMatrix(rotation_matrix))) operation.addOperation( TranslateOperation(new_node, Vector(node_item.translation().x() / factor, 0, node_item.translation().y() / factor))) else: # We didn't find a spot operation.addOperation( TranslateOperation(new_node, Vector(200, 0, -not_fit_count * 20), set_position=True)) operation.push() status_message.hide() if not found_solution_for_all: no_full_solution_message = Message(i18n_catalog.i18nc("@info:status", "Unable to find a location within the build volume for all objects"), title = i18n_catalog.i18nc("@info:title", "Placing Object")) no_full_solution_message.show()