def _createEraserMesh(self, parent: CuraSceneNode, position: Vector): node = CuraSceneNode() node.setName("Eraser") node.setSelectable(True) node.setCalculateBoundingBox(True) mesh = self._createCube(10) node.setMeshData(mesh.build()) node.calculateBoundingBoxMesh() active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate node.addDecorator(BuildPlateDecorator(active_build_plate)) node.addDecorator(SliceableObjectDecorator()) stack = node.callDecoration("getStack") # created by SettingOverrideDecorator that is automatically added to CuraSceneNode settings = stack.getTop() definition = stack.getSettingDefinition("anti_overhang_mesh") new_instance = SettingInstance(definition, settings) new_instance.setProperty("value", True) new_instance.resetState() # Ensure that the state is not seen as a user state. settings.addInstance(new_instance) op = GroupedOperation() # First add node to the scene at the correct position/scale, before parenting, so the eraser mesh does not get scaled with the parent op.addOperation(AddSceneNodeOperation(node, self._controller.getScene().getRoot())) op.addOperation(SetParentOperation(node, parent)) op.push() node.setPosition(position, CuraSceneNode.TransformSpace.World) CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
def deleteAll(self, only_selectable = True) -> None: Logger.log("i", "Clearing scene") if not self.getController().getToolsEnabled(): return nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if not isinstance(node, SceneNode): continue if (not node.getMeshData() and not node.callDecoration("getLayerData")) and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. if only_selectable and not node.isSelectable(): continue if not node.callDecoration("isSliceable") and not node.callDecoration("getLayerData") and not node.callDecoration("isGroup"): continue # Only remove nodes that are selectable. if node.getParent() and cast(SceneNode, node.getParent()).callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: op.addOperation(RemoveSceneNodeOperation(node)) # Reset the print information self.getController().getScene().sceneChanged.emit(node) op.push() Selection.clear()
def resetAll(self): Logger.log("i", "Resetting all scene transformations") nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): if type(node) is not SceneNode: continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. if node.getParent() and node.getParent().callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) if not node.isSelectable(): continue # i.e. node with layer data nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: # Ensure that the object is above the build platform node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) if node.getBoundingBox(): center_y = node.getWorldPosition().y - node.getBoundingBox().bottom else: center_y = 0 op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1))) op.push()
def test_addOperationFinalised(): operation_1 = GroupedOperation() operation_2 = GroupedOperation() operation_1.redo() # The operation is now finalized, so it shouldn't be possible to add operations to it now. with pytest.raises(Exception): operation_1.addOperation(operation_2)
def setExtruderForSelection(self, extruder_id: str) -> None: operation = GroupedOperation() nodes_to_change = [] for node in Selection.getAllSelectedObjects(): # Do not change any nodes that already have the right extruder set. if node.callDecoration("getActiveExtruder") == extruder_id: continue # If the node is a group, apply the active extruder to all children of the group. if node.callDecoration("isGroup"): for grouped_node in BreadthFirstIterator(node): if grouped_node.callDecoration("getActiveExtruder") == extruder_id: continue if grouped_node.callDecoration("isGroup"): continue nodes_to_change.append(grouped_node) continue nodes_to_change.append(node) if not nodes_to_change: # If there are no changes to make, we still need to reset the selected extruders. # This is a workaround for checked menu items being deselected while still being # selected. ExtruderManager.getInstance().resetSelectedObjectExtruders() return for node in nodes_to_change: operation.addOperation(SetObjectExtruderOperation(node, extruder_id)) operation.push()
def mergeWith(self, other): group = GroupedOperation() group.addOperation(self) group.addOperation(other) return group
def run(self): status_message = Message(i18n_catalog.i18nc("@info:status", "Finding new location for objects"), lifetime = 0, dismissable=False, progress = 0) status_message.show() arranger = Arrange.create(fixed_nodes = self._fixed_nodes) # Collect nodes to be placed nodes_arr = [] # fill with (size, node, offset_shape_arr, hull_shape_arr) for node in self._nodes: offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(node, min_offset = self._min_offset) nodes_arr.append((offset_shape_arr.arr.shape[0] * offset_shape_arr.arr.shape[1], node, offset_shape_arr, hull_shape_arr)) # Sort the nodes with the biggest area first. nodes_arr.sort(key=lambda item: item[0]) nodes_arr.reverse() # Place nodes one at a time start_priority = 0 last_priority = start_priority last_size = None grouped_operation = GroupedOperation() found_solution_for_all = True for idx, (size, node, offset_shape_arr, hull_shape_arr) in enumerate(nodes_arr): # For performance reasons, we assume that when a location does not fit, # it will also not fit for the next object (while what can be untrue). # We also skip possibilities by slicing through the possibilities (step = 10) if last_size == size: # This optimization works if many of the objects have the same size start_priority = last_priority else: start_priority = 0 best_spot = arranger.bestSpot(offset_shape_arr, start_prio=start_priority, step=10) x, y = best_spot.x, best_spot.y node.removeDecorator(ZOffsetDecorator) if node.getBoundingBox(): center_y = node.getWorldPosition().y - node.getBoundingBox().bottom else: center_y = 0 if x is not None: # We could find a place last_size = size last_priority = best_spot.priority arranger.place(x, y, hull_shape_arr) # take place before the next one grouped_operation.addOperation(TranslateOperation(node, Vector(x, center_y, y), set_position = True)) else: Logger.log("d", "Arrange all: could not find spot!") found_solution_for_all = False grouped_operation.addOperation(TranslateOperation(node, Vector(200, center_y, - idx * 20), set_position = True)) status_message.setProgress((idx + 1) / len(nodes_arr) * 100) Job.yieldThread() grouped_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")) no_full_solution_message.show()
def removeSelection(self): if not Selection.hasSelection(): return op = GroupedOperation() for node in Selection.getAllSelectedObjects(): op.addOperation(RemoveSceneNodeOperation(node)) op.push() Selection.clear()
def centerSelection(self) -> None: operation = GroupedOperation() for node in Selection.getAllSelectedObjects(): current_node = node while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): current_node = current_node.getParent() center_operation = SetTransformOperation(current_node, Vector()) operation.addOperation(center_operation) operation.push()
def _scaleSelectedNodes(self, scale_vector: Vector) -> None: selected_nodes = self._getSelectedObjectsWithoutSelectedAncestors() if len(selected_nodes) > 1: op = GroupedOperation() for node in selected_nodes: op.addOperation(ScaleOperation(node, scale_vector, scale_around_point=node.getWorldPosition())) op.push() else: for node in selected_nodes: ScaleOperation(node, scale_vector, scale_around_point=node.getWorldPosition()).push()
def setX(self, x): bounding_box = Selection.getBoundingBox() op = GroupedOperation() for selected_node in Selection.getAllSelectedObjects(): world_position = selected_node.getWorldPosition() new_position = world_position.set(x=float(x) + (world_position.x - bounding_box.center.x)) node_op = TranslateOperation(selected_node, new_position, set_position = True) op.addOperation(node_op) op.push() self.operationStopped.emit(self)
def centerSelection(self) -> None: operation = GroupedOperation() for node in Selection.getAllSelectedObjects(): current_node = node while current_node.getParent() and current_node.getParent( ).callDecoration("isGroup"): current_node = current_node.getParent() center_operation = SetTransformOperation(current_node, Vector()) operation.addOperation(center_operation) operation.push()
def run(self): status_message = Message(i18n_catalog.i18nc( "@info:status", "Multiplying and placing objects"), lifetime=0, dismissable=False, progress=0) status_message.show() scene = Application.getInstance().getController().getScene() node = scene.findObject(self._object_id) if not node and self._object_id != 0: # Workaround for tool handles overlapping the selected object node = Selection.getSelectedObject(0) # If object is part of a group, multiply group current_node = node while current_node.getParent() and current_node.getParent( ).callDecoration("isGroup"): current_node = current_node.getParent() root = scene.getRoot() arranger = Arrange.create(scene_root=root) offset_shape_arr, hull_shape_arr = ShapeArray.fromNode( current_node, min_offset=self._min_offset) nodes = [] found_solution_for_all = True for i in range(self._count): # We do place the nodes one by one, as we want to yield in between. node, solution_found = arranger.findNodePlacement( current_node, offset_shape_arr, hull_shape_arr) if not solution_found: found_solution_for_all = False new_location = node.getPosition() new_location = new_location.set(z=100 - i * 20) node.setPosition(new_location) nodes.append(node) Job.yieldThread() status_message.setProgress((i + 1) / self._count * 100) if nodes: op = GroupedOperation() for new_node in nodes: op.addOperation( AddSceneNodeOperation(new_node, current_node.getParent())) op.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" )) no_full_solution_message.show()
def deleteSelection(self): if not self.getController().getToolsEnabled(): return op = GroupedOperation() nodes = Selection.getAllSelectedObjects() for node in nodes: op.addOperation(RemoveSceneNodeOperation(node)) op.push() pass
def run(self): 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 Object")) status_message.show() scene = Application.getInstance().getController().getScene() total_progress = len(self._objects) * self._count current_progress = 0 root = scene.getRoot() arranger = Arrange.create(scene_root=root) nodes = [] 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"): current_node = current_node.getParent() node_too_big = False if node.getBoundingBox().width < 300 or node.getBoundingBox().depth < 300: offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset) else: node_too_big = True found_solution_for_all = True for i in range(self._count): # We do place the nodes one by one, as we want to yield in between. if not node_too_big: node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr) if node_too_big or not solution_found: found_solution_for_all = False new_location = node.getPosition() new_location = new_location.set(z = 100 - i * 20) node.setPosition(new_location) nodes.append(node) current_progress += 1 status_message.setProgress((current_progress / total_progress) * 100) Job.yieldThread() Job.yieldThread() if nodes: op = GroupedOperation() for new_node in nodes: op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent())) op.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()
def setX(self, x): parsed_x = self._parseInt(x) bounding_box = Selection.getBoundingBox() op = GroupedOperation() if not Float.fuzzyCompare(parsed_x, float(bounding_box.center.x), DIMENSION_TOLERANCE): for selected_node in Selection.getAllSelectedObjects(): world_position = selected_node.getWorldPosition() new_position = world_position.set(x=parsed_x + (world_position.x - bounding_box.center.x)) node_op = TranslateOperation(selected_node, new_position, set_position = True) op.addOperation(node_op) op.push() self._controller.toolOperationStopped.emit(self)
def deleteAll(self): nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): if type(node) is not SceneNode or not node.getMeshData(): continue nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: op.addOperation(RemoveSceneNodeOperation(node)) op.push()
def multiplyObject(self, object_id, count): node = self.getController().getScene().findObject(object_id) if node: op = GroupedOperation() for i in range(count): new_node = SceneNode() new_node.setMeshData(node.getMeshData()) new_node.setScale(node.getScale()) new_node.translate(Vector((i + 1) * node.getBoundingBox().width, 0, 0)) new_node.setSelectable(True) op.addOperation(AddSceneNodeOperation(new_node, node.getParent())) op.push()
def setZ(self, z): bounding_box = Selection.getBoundingBox() op = GroupedOperation() for selected_node in Selection.getAllSelectedObjects(): # Note: The switching of z & y is intentional. We display z as up for the user, # But store the data in openGL space. world_position = selected_node.getWorldPosition() new_position = world_position.set(y=float(z) + (world_position.y - bounding_box.bottom)) node_op = TranslateOperation(selected_node, new_position, set_position = True) op.addOperation(node_op) op.push() self.operationStopped.emit(self)
def deleteAll(self): nodes = [] for node in DepthFirstIterator( self.getController().getScene().getRoot()): if type(node) is not SceneNode or not node.getMeshData(): continue nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: op.addOperation(RemoveSceneNodeOperation(node)) op.push()
def createGroupOperationForArrange(nodes_to_arrange: List["SceneNode"], build_volume: "BuildVolume", fixed_nodes: Optional[List["SceneNode"]] = None, factor = 10000, add_new_nodes_in_scene: bool = False) -> Tuple[GroupedOperation, int]: scene_root = Application.getInstance().getController().getScene().getRoot() found_solution_for_all, node_items = findNodePlacement(nodes_to_arrange, build_volume, fixed_nodes, factor) not_fit_count = 0 grouped_operation = GroupedOperation() for node, node_item in zip(nodes_to_arrange, node_items): if add_new_nodes_in_scene: grouped_operation.addOperation(AddSceneNodeOperation(node, scene_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)) grouped_operation.addOperation(RotateOperation(node, Quaternion.fromMatrix(rotation_matrix))) grouped_operation.addOperation(TranslateOperation(node, Vector(node_item.translation().x() / factor, 0, node_item.translation().y() / factor))) else: # We didn't find a spot grouped_operation.addOperation( TranslateOperation(node, Vector(200, node.getWorldPosition().y, -not_fit_count * 20), set_position = True)) not_fit_count += 1 return grouped_operation, not_fit_count
def setX(self, x): Benchmark.start("Moving object in X from {start} to {end}".format(start = self.getX(), end = x)) parsed_x = self._parseInt(x) bounding_box = Selection.getBoundingBox() op = GroupedOperation() if not Float.fuzzyCompare(parsed_x, float(bounding_box.center.x), DIMENSION_TOLERANCE): for selected_node in self._getSelectedObjectsWithoutSelectedAncestors(): world_position = selected_node.getWorldPosition() new_position = world_position.set(x=parsed_x + (world_position.x - bounding_box.center.x)) node_op = TranslateOperation(selected_node, new_position, set_position = True) op.addOperation(node_op) op.push() self._controller.toolOperationStopped.emit(self)
def deleteSelection(self) -> None: if not Application.getInstance().getController().getToolsEnabled(): return removed_group_nodes = [] op = GroupedOperation() nodes = Selection.getAllSelectedObjects() for node in nodes: print_mode_enabled = Application.getInstance( ).getGlobalContainerStack().getProperty("print_mode", "enabled") if print_mode_enabled: node_dup = PrintModeManager.getInstance().getDuplicatedNode( node) op.addOperation(RemoveNodesOperation(node_dup)) else: op.addOperation(RemoveSceneNodeOperation(node)) group_node = node.getParent() if group_node and group_node.callDecoration( "isGroup") and group_node not in removed_group_nodes: remaining_nodes_in_group = list( set(group_node.getChildren()) - set(nodes)) if len(remaining_nodes_in_group) == 1: removed_group_nodes.append(group_node) op.addOperation( SetParentOperation(remaining_nodes_in_group[0], group_node.getParent())) op.addOperation(RemoveSceneNodeOperation(group_node)) op.push()
def resetAll(self): nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): if type(node) is not SceneNode or not node.getMeshData(): continue nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: op.addOperation(SetTransformOperation(node, Vector(), Quaternion(), Vector(1, 1, 1))) op.push()
def multiplySelection(self, count: int) -> None: application = cura.CuraApplication.CuraApplication.getInstance() global_container_stack = application.getGlobalContainerStack() preferences = application.getPreferences() if not global_container_stack: return if not preferences.getValue("BeltPlugin/on_plugin"): # for all other printers do the normal multiply/arrange super().multiplySelection(count) return scene_root = application.getController().getScene().getRoot() current_nodes = [] for node in DepthFirstIterator(scene_root): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if node.callDecoration("isSliceable") or node.callDecoration("isGroup"): current_nodes.append(node) new_nodes = [] # type: List[CuraSceneNode] processed_nodes = [] # type: List[CuraSceneNode] active_build_plate = application.getMultiBuildPlateModel().activeBuildPlate for node in Selection.getAllSelectedObjects()[:]: # If object is part of a group, multiply group while node.getParent() and (node.getParent().callDecoration("isGroup") or node.getParent().callDecoration("isSliceable")): node = node.getParent() if node in processed_nodes: continue processed_nodes.append(node) for i in range(count): new_node = copy.deepcopy(node) new_node.callDecoration("setBuildPlateNumber", active_build_plate) for child in new_node.getChildren(): child.callDecoration("setBuildPlateNumber", active_build_plate) new_nodes.append(new_node) if new_nodes: op = GroupedOperation() for new_node in new_nodes: op.addOperation(AddSceneNodeOperation(new_node, scene_root)) op.push() application.arrange(new_nodes, current_nodes)
def centerSelection(self) -> None: operation = GroupedOperation() for node in Selection.getAllSelectedObjects(): current_node = node while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): current_node = current_node.getParent() # This was formerly done with SetTransformOperation but because of # unpredictable matrix deconstruction it was possible that mirrors # could manifest as rotations. Centering is therefore done by # moving the node to negative whatever its position is: center_operation = TranslateOperation(current_node, -current_node._position) operation.addOperation(center_operation) operation.push()
def removeModMeshes(self, msg, action): """ Associated Action for askToRemoveModMesh() """ self._mod_mesh_removal_msg = None msg.hide() if action == "continue": op = GroupedOperation() for node in getModifierMeshes(): node.addDecorator(SmartSliceRemovedDecorator()) op.addOperation(RemoveSceneNodeOperation(node)) op.push() self.connector.status = SmartSliceCloudStatus.RemoveModMesh self.connector.doOptimization() else: self.connector.prepareOptimization()
def setX(self, x): bounding_box = Selection.getBoundingBox() op = GroupedOperation() for selected_node in Selection.getAllSelectedObjects(): new_position = selected_node.getWorldPosition() new_position.setY( float(y) + (new_position.x - bounding_box.center.x)) node_op = TranslateOperation(selected_node, new_position, set_position=True) op.addOperation(node_op) op.push() self.operationStopped.emit(self)
def setZ(self, z): parsed_z = self._parseInt(z) bounding_box = Selection.getBoundingBox() op = GroupedOperation() if not Float.fuzzyCompare(parsed_z, float(bounding_box.center.y), DIMENSION_TOLERANCE): for selected_node in Selection.getAllSelectedObjects(): # Note: The switching of z & y is intentional. We display z as up for the user, # But store the data in openGL space. world_position = selected_node.getWorldPosition() new_position = world_position.set(y=parsed_z + (world_position.y - bounding_box.bottom)) node_op = TranslateOperation(selected_node, new_position, set_position = True) op.addOperation(node_op) op.push() self._controller.toolOperationStopped.emit(self)
def _replaceSceneNode(self, existing_node, trimeshes) -> None: name = existing_node.getName() file_name = existing_node.getMeshData().getFileName() transformation = existing_node.getWorldTransformation() parent = existing_node.getParent() extruder_id = existing_node.callDecoration("getActiveExtruder") build_plate = existing_node.callDecoration("getBuildPlateNumber") selected = Selection.isSelected(existing_node) children = existing_node.getChildren() new_nodes = [] op = GroupedOperation() op.addOperation(RemoveSceneNodeOperation(existing_node)) for i, tri_node in enumerate(trimeshes): mesh_data = self._toMeshData(tri_node, file_name) new_node = CuraSceneNode() new_node.setSelectable(True) new_node.setMeshData(mesh_data) new_node.setName(name if i==0 else "%s %d" % (name, i)) new_node.callDecoration("setActiveExtruder", extruder_id) new_node.addDecorator(BuildPlateDecorator(build_plate)) new_node.addDecorator(SliceableObjectDecorator()) op.addOperation(AddSceneNodeOperation(new_node, parent)) op.addOperation(SetTransformMatrixOperation(new_node, transformation)) new_nodes.append(new_node) if selected: Selection.add(new_node) for child in children: mesh_data = child.getMeshData() if not mesh_data: continue child_bounding_box = mesh_data.getTransformed(child.getWorldTransformation()).getExtents() if not child_bounding_box: continue new_parent = None for potential_parent in new_nodes: parent_mesh_data = potential_parent.getMeshData() if not parent_mesh_data: continue parent_bounding_box = parent_mesh_data.getTransformed(potential_parent.getWorldTransformation()).getExtents() if not parent_bounding_box: continue intersection = child_bounding_box.intersectsBox(parent_bounding_box) if intersection != AxisAlignedBox.IntersectionResult.NoIntersection: new_parent = potential_parent break if not new_parent: new_parent = new_nodes[0] op.addOperation(SetParentOperationSimplified(child, new_parent)) op.push()
def ungroupSelected(self): selected_objects = Selection.getAllSelectedObjects().copy() for node in selected_objects: if node.callDecoration("isGroup"): op = GroupedOperation() group_parent = node.getParent() children = node.getChildren().copy() for child in children: # Set the parent of the children to the parent of the group-node op.addOperation(SetParentOperation(child, group_parent)) # Add all individual nodes to the selection Selection.add(child) op.push()
def applyOperation(cls, operation, *args, **kwargs): if not cls.__selection: return op = None if len(cls.__selection) == 1: node = cls.__selection[0] op = operation(node, *args, **kwargs) else: op = GroupedOperation() for node in Selection.getAllSelectedObjects(): op.addOperation(operation(node, *args, **kwargs)) op.push()
def multiplyObject(self, object_id, count): node = self.getController().getScene().findObject(object_id) if not node and object_id != 0: #Workaround for tool handles overlapping the selected object node = Selection.getSelectedObject(0) if node: op = GroupedOperation() for i in range(count): new_node = SceneNode() new_node.setMeshData(node.getMeshData()) new_node.setScale(node.getScale()) new_node.translate(Vector((i + 1) * node.getBoundingBox().width, 0, 0)) new_node.setSelectable(True) op.addOperation(AddSceneNodeOperation(new_node, node.getParent())) op.push()
def setZ(self, z): Benchmark.start("Moving object in Z from {start} to {end}".format(start = self.getZ(), end = z)) parsed_z = self._parseInt(z) bounding_box = Selection.getBoundingBox() op = GroupedOperation() if not Float.fuzzyCompare(parsed_z, float(bounding_box.bottom), DIMENSION_TOLERANCE): for selected_node in self._getSelectedObjectsWithoutSelectedAncestors(): # Note: The switching of z & y is intentional. We display z as up for the user, # But store the data in openGL space. world_position = selected_node.getWorldPosition() new_position = world_position.set(y=parsed_z + (world_position.y - bounding_box.bottom)) node_op = TranslateOperation(selected_node, new_position, set_position = True) op.addOperation(node_op) op.push() self._controller.toolOperationStopped.emit(self)
def setY(self, y): bounding_box = Selection.getBoundingBox() op = GroupedOperation() for selected_node in Selection.getAllSelectedObjects(): # Note; The switching of z & y is intentional. We display z as up for the user, # But store the data in openGL space. new_position = selected_node.getWorldPosition() new_position.setY( float(y) + (new_position.z - bounding_box.center.z)) node_op = TranslateOperation(selected_node, new_position, set_position=True) op.addOperation(node_op) op.push() self.operationStopped.emit(self)
def deleteAll(self): nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): if type(node) is not SceneNode: continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. if node.getParent() and node.getParent().callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: op.addOperation(RemoveSceneNodeOperation(node)) op.push()
def multiplyObject(self, object_id, count): node = self.getController().getScene().findObject(object_id) if not node and object_id != 0: # Workaround for tool handles overlapping the selected object node = Selection.getSelectedObject(0) if node: current_node = node # Find the topmost group while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): current_node = current_node.getParent() op = GroupedOperation() for _ in range(count): new_node = copy.deepcopy(current_node) op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent())) op.push()
def deleteAll(self): nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): if type(node) is not SceneNode: continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue #Node that doesnt have a mesh and is not a group. if node.getParent() and node.getParent().callDecoration("isGroup"): continue #Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: op.addOperation(RemoveSceneNodeOperation(node)) op.push()
def multiplyObject(self, object_id, count): node = self.getController().getScene().findObject(object_id) if not node and object_id != 0: #Workaround for tool handles overlapping the selected object node = Selection.getSelectedObject(0) if node: op = GroupedOperation() for i in range(count): new_node = SceneNode() new_node.setMeshData(node.getMeshData()) new_node.translate(Vector((i + 1) * node.getBoundingBox().width, node.getPosition().y, 0)) new_node.setOrientation(node.getOrientation()) new_node.setScale(node.getScale()) new_node.setSelectable(True) op.addOperation(AddSceneNodeOperation(new_node, node.getParent())) op.push()
def _onSelectedFaceChanged(self): if not self._select_face_mode: return self._handle.setEnabled(not Selection.getFaceSelectMode()) selected_face = Selection.getSelectedFace() if not Selection.getSelectedFace() or not ( Selection.hasSelection() and Selection.getFaceSelectMode()): return original_node, face_id = selected_face meshdata = original_node.getMeshDataTransformed() if not meshdata or face_id < 0: return if face_id > (meshdata.getVertexCount() / 3 if not meshdata.hasIndices() else meshdata.getFaceCount()): return face_mid, face_normal = meshdata.getFacePlane(face_id) object_mid = original_node.getBoundingBox().center rotation_point_vector = Vector(object_mid.x, object_mid.y, face_mid[2]) face_normal_vector = Vector(face_normal[0], face_normal[1], face_normal[2]) rotation_quaternion = Quaternion.rotationTo( face_normal_vector.normalized(), Vector(0.0, -1.0, 0.0)) operation = GroupedOperation() current_node = None # type: Optional[SceneNode] for node in Selection.getAllSelectedObjects(): current_node = node parent_node = current_node.getParent() while parent_node and parent_node.callDecoration("isGroup"): current_node = parent_node parent_node = current_node.getParent() if current_node is None: return rotate_operation = RotateOperation(current_node, rotation_quaternion, rotation_point_vector) gravity_operation = GravityOperation(current_node) operation.addOperation(rotate_operation) operation.addOperation(gravity_operation) operation.push()
def resetAllTranslation(self): nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): if type(node) is not SceneNode: continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. if node.getParent() and node.getParent().callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) op.addOperation(SetTransformOperation(node, Vector(0, 0, 0))) op.push()
def resetAll(self): nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): if type(node) is not SceneNode: continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue #Node that doesnt have a mesh and is not a group. if node.getParent() and node.getParent().callDecoration("isGroup"): continue #Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: # Ensure that the object is above the build platform op.addOperation(SetTransformOperation(node, Vector(0,0,0), Quaternion(), Vector(1, 1, 1))) op.push()
def run(self): status_message = Message(i18n_catalog.i18nc("@info:status", "Multiplying and placing objects"), lifetime=0, dismissable=False, progress=0) status_message.show() scene = Application.getInstance().getController().getScene() node = scene.findObject(self._object_id) if not node and self._object_id != 0: # Workaround for tool handles overlapping the selected object node = Selection.getSelectedObject(0) # If object is part of a group, multiply group current_node = node while current_node.getParent() and current_node.getParent().callDecoration("isGroup"): current_node = current_node.getParent() root = scene.getRoot() arranger = Arrange.create(scene_root=root) offset_shape_arr, hull_shape_arr = ShapeArray.fromNode(current_node, min_offset=self._min_offset) nodes = [] found_solution_for_all = True for i in range(self._count): # We do place the nodes one by one, as we want to yield in between. node, solution_found = arranger.findNodePlacement(current_node, offset_shape_arr, hull_shape_arr) if not solution_found: found_solution_for_all = False new_location = node.getPosition() new_location = new_location.set(z = 100 - i * 20) node.setPosition(new_location) nodes.append(node) Job.yieldThread() status_message.setProgress((i + 1) / self._count * 100) if nodes: op = GroupedOperation() for new_node in nodes: op.addOperation(AddSceneNodeOperation(new_node, current_node.getParent())) op.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")) no_full_solution_message.show()
def resetAllTranslation(self): Logger.log("i", "Resetting all scene translations") nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): if type(node) is not SceneNode: continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue # Node that doesnt have a mesh and is not a group. if node.getParent() and node.getParent().callDecoration("isGroup"): continue # Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: # Ensure that the object is above the build platform node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) op.addOperation(SetTransformOperation(node, Vector(0, node.getWorldPosition().y - node.getBoundingBox().bottom, 0))) op.push()
def resetAllTranslation(self): nodes = [] for node in DepthFirstIterator(self.getController().getScene().getRoot()): if type(node) is not SceneNode: continue if not node.getMeshData() and not node.callDecoration("isGroup"): continue #Node that doesnt have a mesh and is not a group. if node.getParent() and node.getParent().callDecoration("isGroup"): continue #Grouped nodes don't need resetting as their parent (the group) is resetted) nodes.append(node) if nodes: op = GroupedOperation() for node in nodes: node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) op.addOperation(SetTransformOperation(node, Vector(0,0,0))) op.push()
def randomiseMeshLocation(self) -> None: nodes_list = self._getAllSelectedNodes() if not nodes_list: return global_container_stack = self._application.getGlobalContainerStack() if not global_container_stack: return disallowed_edge = self._application.getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors max_x_coordinate = (global_container_stack.getProperty("machine_width", "value") / 2) - disallowed_edge max_y_coordinate = (global_container_stack.getProperty("machine_depth", "value") / 2) - disallowed_edge op = GroupedOperation() for node in nodes_list: node_bounds = node.getBoundingBox() position = self._randomLocation(node_bounds, max_x_coordinate, max_y_coordinate) op.addOperation(SetTransformOperation(node, translation=position)) op.push()
def setExtruderForSelection(self, extruder_id: str) -> None: """Set the extruder that should be used to print the selection. :param extruder_id: The ID of the extruder stack to use for the selected objects. """ operation = GroupedOperation() nodes_to_change = [] for node in Selection.getAllSelectedObjects(): # If the node is a group, apply the active extruder to all children of the group. if node.callDecoration("isGroup"): for grouped_node in BreadthFirstIterator( node ): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax. if grouped_node.callDecoration( "getActiveExtruder") == extruder_id: continue if grouped_node.callDecoration("isGroup"): continue nodes_to_change.append(grouped_node) continue # Do not change any nodes that already have the right extruder set. if node.callDecoration("getActiveExtruder") == extruder_id: continue nodes_to_change.append(node) if not nodes_to_change: # If there are no changes to make, we still need to reset the selected extruders. # This is a workaround for checked menu items being deselected while still being # selected. ExtruderManager.getInstance().resetSelectedObjectExtruders() return for node in nodes_to_change: operation.addOperation( SetObjectExtruderOperation(node, extruder_id)) operation.push()
def multiplyObject(self, object_id, count): node = self.getController().getScene().findObject(object_id) if not node and object_id != 0: # Workaround for tool handles overlapping the selected object node = Selection.getSelectedObject(0) if node: op = GroupedOperation() for i in range(count): if node.getParent() and node.getParent().callDecoration("isGroup"): new_node = copy.deepcopy(node.getParent()) # Copy the group node. new_node.callDecoration("setConvexHull", None) op.addOperation(AddSceneNodeOperation(new_node, node.getParent().getParent())) else: new_node = copy.deepcopy(node) new_node.callDecoration("setConvexHull", None) op.addOperation(AddSceneNodeOperation(new_node, node.getParent())) op.push()
def applyOperation(cls, operation, *args, **kwargs): if not cls.__selection: return operations = [] if len(cls.__selection) == 1: node = cls.__selection[0] op = operation(node, *args, **kwargs) operations.append(op) else: op = GroupedOperation() for node in Selection.getAllSelectedObjects(): sub_op = operation(node, *args, **kwargs) op.addOperation(sub_op) operations.append(sub_op) op.push() return operations