def setX(self, x: str) -> None: """Set the x-location of the selected object(s) by translating relative to the selection bounding box center. :param x: Location in mm. """ parsed_x = self._parseFloat(x) bounding_box = Selection.getBoundingBox() if not Float.fuzzyCompare(parsed_x, float(bounding_box.center.x), DIMENSION_TOLERANCE): selected_nodes = self._getSelectedObjectsWithoutSelectedAncestors() if len(selected_nodes) > 1: op = GroupedOperation() 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() else: 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)) TranslateOperation(selected_node, new_position, set_position = True).push() self._controller.toolOperationStopped.emit(self)
def mergeWith(self, other: Operation) -> GroupedOperation: group = GroupedOperation() group.addOperation(other) group.addOperation(self) return group
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 deleteSelection(self) -> None: """Delete all selected objects.""" if not cura.CuraApplication.CuraApplication.getInstance( ).getController().getToolsEnabled(): return removed_group_nodes = [] #type: List[SceneNode] op = GroupedOperation() nodes = Selection.getAllSelectedObjects() for node in nodes: 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)) # Reset the print information cura.CuraApplication.CuraApplication.getInstance().getController( ).getScene().sceneChanged.emit(node) op.push()
def setBuildPlateForSelection(self, build_plate_nr: int) -> None: Logger.log("d", "Setting build plate number... %d" % build_plate_nr) operation = GroupedOperation() root = cura.CuraApplication.CuraApplication.getInstance( ).getController().getScene().getRoot() nodes_to_change = [] # type: List[SceneNode] for node in Selection.getAllSelectedObjects(): parent_node = node # Find the parent node to change instead while parent_node.getParent() != root: parent_node = cast(SceneNode, parent_node.getParent()) for single_node in BreadthFirstIterator( parent_node ): # type: ignore #Ignore type error because iter() should get called automatically by Python syntax. nodes_to_change.append(single_node) if not nodes_to_change: Logger.log("d", "Nothing to change.") return for node in nodes_to_change: operation.addOperation( SetBuildPlateNumberOperation(node, build_plate_nr)) operation.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() vector = current_node._position print_mode_enabled = Application.getInstance( ).getGlobalContainerStack().getProperty("print_mode", "enabled") if print_mode_enabled: print_mode = Application.getInstance().getGlobalContainerStack( ).getProperty("print_mode", "value") if print_mode != "regular": machine_width = Application.getInstance( ).getGlobalContainerStack().getProperty( "machine_width", "value") center = -machine_width / 4 if print_mode == "mirror": machine_head_with_fans_polygon = Application.getInstance( ).getGlobalContainerStack().getProperty( "machine_head_with_fans_polygon", "value") machine_head_size = math.fabs( machine_head_with_fans_polygon[0][0] - machine_head_with_fans_polygon[2][0]) center -= machine_head_size / 4 vector = Vector(current_node._position.x - center, current_node._position.y, current_node._position.z) center_operation = TranslateOperation(current_node, -vector) operation.addOperation(center_operation) # node.setPosition(vector) operation.push()
def mergeWith(self, other): group = GroupedOperation() group.addOperation(self) group.addOperation(other) return group
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 _replaceSceneNode(self, existing_node, trimeshes): 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) op = GroupedOperation() op.addOperation(RemoveSceneNodeOperation(existing_node)) for i, tri_node in enumerate(trimeshes): mesh_data = self._toMeshData(tri_node) 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)) if selected: Selection.add(new_node) 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) 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 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. 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) 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 _onSelectedFaceChanged(self): 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 rotation_point, face_normal = meshdata.getFacePlane(face_id) rotation_point_vector = Vector(rotation_point[0], rotation_point[1], rotation_point[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) operation.addOperation(rotate_operation) operation.push()
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: node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator) op.addOperation( SetTransformOperation( node, Vector( 0, node.getWorldPosition().y - node.getBoundingBox().bottom, 0))) op.push()
def applyOperation(cls, operation, *args, **kwargs): """Apply an operation to the entire selection This will create and push an operation onto the operation stack. Dependent on whether there is one item selected or multiple it will be just the operation or a grouped operation containing the operation for each selected node. :param operation: :type{Class} The operation to create and push. It should take a SceneNode as first positional parameter. :param args: The additional positional arguments passed along to the operation constructor. :param kwargs: The additional keyword arguments that will be passed along to the operation constructor. :return: list of instantiated operations """ 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
def multiplySelection(self, count: int) -> None: application = cura.CuraApplication.CuraApplication.getInstance() global_container_stack = application.getGlobalContainerStack() if not global_container_stack: return definition_container = global_container_stack.getBottom() if definition_container and definition_container.getId() not in [ "blackbelt", "blackbeltvd" ]: # 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 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 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 setBuildPlateForSelection(self, build_plate_nr: int) -> None: Logger.log("d", "Setting build plate number... %d" % build_plate_nr) operation = GroupedOperation() root = Application.getInstance().getController().getScene().getRoot() nodes_to_change = [] for node in Selection.getAllSelectedObjects(): parent_node = node # Find the parent node to change instead while parent_node.getParent() != root: parent_node = parent_node.getParent() for single_node in BreadthFirstIterator(parent_node): nodes_to_change.append(single_node) if not nodes_to_change: Logger.log("d", "Nothing to change.") return for node in nodes_to_change: operation.addOperation( SetBuildPlateNumberOperation(node, build_plate_nr)) operation.push() Selection.clear()
def setZ(self, z: str) -> None: """Set the y-location of the selected object(s) by translating relative to the selection bounding box bottom. :param z: Location in mm. """ parsed_z = self._parseFloat(z) bounding_box = Selection.getBoundingBox() if not Float.fuzzyCompare(parsed_z, float(bounding_box.bottom), DIMENSION_TOLERANCE): selected_nodes = self._getSelectedObjectsWithoutSelectedAncestors() if len(selected_nodes) > 1: op = GroupedOperation() for selected_node in selected_nodes: # 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() else: for selected_node in selected_nodes: world_position = selected_node.getWorldPosition() new_position = world_position.set( y=parsed_z + (world_position.y - bounding_box.bottom)) TranslateOperation(selected_node, new_position, set_position=True).push() self._controller.toolOperationStopped.emit(self)
def deleteAllNodesWithMeshData(self, only_selectable:bool = True) -> None: Logger.log("i", "Clearing scene") if not self.getToolsEnabled(): return nodes = [] for node in DepthFirstIterator(self.getScene().getRoot()): 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: from UM.Operations.GroupedOperation import GroupedOperation op = GroupedOperation() for node in nodes: from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation op.addOperation(RemoveSceneNodeOperation(node)) # Reset the print information self.getScene().sceneChanged.emit(node) op.push() from UM.Scene.Selection import Selection Selection.clear()
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)) # Reset the print information Application.getInstance().getController().getScene( ).sceneChanged.emit(node) op.push()
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 event(self, event): super().event(event) if event.type == Event.MousePressEvent and self._controller.getToolsEnabled(): # Initialise a mirror operation if MouseEvent.LeftButton not in event.buttons: return False id = self._selection_pass.getIdAtPosition(event.x, event.y) if not id: return False if ToolHandle.isAxis(id): self.setLockedAxis(id) self._operation_started = True self.operationStarted.emit(self) return True if event.type == Event.MouseReleaseEvent: if self._operation_started: self._operation_started = False self.operationStopped.emit(self) # Perform a mirror operation if self.getLockedAxis(): op = None if Selection.getCount() == 1: node = Selection.getSelectedObject(0) if self.getLockedAxis() == ToolHandle.XAxis: mirror = Vector(-1, 1, 1) elif self.getLockedAxis() == ToolHandle.YAxis: mirror = Vector(1, -1, 1) elif self.getLockedAxis() == ToolHandle.ZAxis: mirror = Vector(1, 1, -1) else: mirror = Vector(1, 1, 1) op = MirrorOperation(node, mirror, mirror_around_center = True) else: op = GroupedOperation() for node in Selection.getAllSelectedObjects(): if self.getLockedAxis() == ToolHandle.XAxis: mirror = Vector(-1, 1, 1) elif self.getLockedAxis() == ToolHandle.YAxis: mirror = Vector(1, -1, 1) elif self.getLockedAxis() == ToolHandle.ZAxis: mirror = Vector(1, 1, -1) else: mirror = Vector(1, 1, 1) op.addOperation(MirrorOperation(node, mirror, mirror_around_center = True)) op.push() self.setLockedAxis(None) return True return False
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=1) 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 _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 removeSelection(self): if not Selection.hasSelection(): return op = GroupedOperation() for node in Selection.getAllSelectedObjects(): op.addOperation(RemoveSceneNodeOperation(node)) op.push() Selection.clear()
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 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 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 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 arrange(nodes_to_arrange: List["SceneNode"], build_volume: "BuildVolume", fixed_nodes: Optional[List["SceneNode"]] = None, factor=10000, add_new_nodes_in_scene: bool = False) -> bool: """ Find placement for a set of scene nodes, and move them by using a single grouped operation. :param nodes_to_arrange: The list of nodes that need to be moved. :param build_volume: The build volume that we want to place the nodes in. It gets size & disallowed areas from this. :param fixed_nodes: List of nods that should not be moved, but should be used when deciding where the others nodes are placed. :param factor: The library that we use is int based. This factor defines how accuracte we want it to be. :param add_new_nodes_in_scene: Whether to create new scene nodes before applying the transformations and rotations :return: found_solution_for_all: Whether the algorithm found a place on the buildplate for all the objects """ 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 grouped_operation.push() return found_solution_for_all