def setZ(self, Z): if float(Z) != self._Z_angle: self._angle = ((float(Z) % 360) - (self._Z_angle % 360)) % 360 self._Z_angle = float(Z) #rotation = Quaternion.fromAngleAxis(math.radians( self._angle ), Vector.Unit_Z) rotation = Quaternion() rotation.setByAngleAxis(math.radians(self._angle), Vector.Unit_Z) # Save the current positions of the node, as we want to rotate around their current centres self._saved_node_positions = [] for node in Selection.getAllSelectedObjects(): self._saved_node_positions.append((node, node.getPosition())) node._rotationZ = self._Z_angle # Rate-limit the angle change notification # This is done to prevent the UI from being flooded with property change notifications, # which in turn would trigger constant repaints. new_time = time.monotonic() if not self._angle_update_time or new_time - self._angle_update_time > 0.1: self._angle_update_time = new_time # Rotate around the saved centeres of all selected nodes op = GroupedOperation() for node, position in self._saved_node_positions: op.addOperation( RotateOperation(node, rotation, rotate_around_point=position)) op.push() self._angle = 0 self.propertyChanged.emit()
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 _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 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
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 bottomFaceSelection(self) -> None: selected_face = Selection.getSelectedFace() if not selected_face: Logger.log( "e", "Bottom face operation shouldn't have been called without a selected face." ) return original_node, face_id = selected_face meshdata = original_node.getMeshDataTransformed() if not meshdata or face_id < 0 or face_id > Selection.getMaxFaceSelectionId( ): 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 event(self, event): super().event(event) if event.type == Event.KeyPressEvent and event.key == KeyEvent.ShiftKey: # Snap is toggled when pressing the shift button self.setRotationSnap(not self._snap_rotation) if event.type == Event.KeyReleaseEvent and event.key == KeyEvent.ShiftKey: # Snap is "toggled back" when releasing the shift button self.setRotationSnap(not self._snap_rotation) if event.type == Event.MousePressEvent and self._controller.getToolsEnabled( ): # Start a rotate 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 self._handle.isAxis(id): self.setLockedAxis(id) else: # Not clicked on an axis: do nothing. return False handle_position = self._handle.getWorldPosition() # Save the current positions of the node, as we want to rotate around their current centres self._saved_node_positions = [] for node in self._getSelectedObjectsWithoutSelectedAncestors(): self._saved_node_positions.append((node, node.getPosition())) if id == ToolHandle.XAxis: self.setDragPlane(Plane(Vector(1, 0, 0), handle_position.x)) elif id == ToolHandle.YAxis: self.setDragPlane(Plane(Vector(0, 1, 0), handle_position.y)) elif self._locked_axis == ToolHandle.ZAxis: self.setDragPlane(Plane(Vector(0, 0, 1), handle_position.z)) else: self.setDragPlane(Plane(Vector(0, 1, 0), handle_position.y)) self.setDragStart(event.x, event.y) self._rotating = False self._angle = 0 return True if event.type == Event.MouseMoveEvent: # Perform a rotate operation if not self.getDragPlane(): return False if not self.getDragStart(): self.setDragStart(event.x, event.y) if not self.getDragStart(): #May have set it to None. return False if not self._rotating: self._rotating = True self.operationStarted.emit(self) handle_position = self._handle.getWorldPosition() drag_start = (self.getDragStart() - handle_position).normalized() drag_position = self.getDragPosition(event.x, event.y) if not drag_position: return False drag_end = (drag_position - handle_position).normalized() try: angle = math.acos(drag_start.dot(drag_end)) except ValueError: angle = 0 if self._snap_rotation: angle = int(angle / self._snap_angle) * self._snap_angle if angle == 0: return False rotation = None if self.getLockedAxis() == ToolHandle.XAxis: direction = 1 if Vector.Unit_X.dot( drag_start.cross(drag_end)) > 0 else -1 rotation = Quaternion.fromAngleAxis(direction * angle, Vector.Unit_X) elif self.getLockedAxis() == ToolHandle.YAxis: direction = 1 if Vector.Unit_Y.dot( drag_start.cross(drag_end)) > 0 else -1 rotation = Quaternion.fromAngleAxis(direction * angle, Vector.Unit_Y) elif self.getLockedAxis() == ToolHandle.ZAxis: direction = 1 if Vector.Unit_Z.dot( drag_start.cross(drag_end)) > 0 else -1 rotation = Quaternion.fromAngleAxis(direction * angle, Vector.Unit_Z) else: direction = -1 # Rate-limit the angle change notification # This is done to prevent the UI from being flooded with property change notifications, # which in turn would trigger constant repaints. new_time = time.monotonic() if not self._angle_update_time or new_time - self._angle_update_time > 0.1: self._angle_update_time = new_time self._angle += direction * angle self.propertyChanged.emit() # Rotate around the saved centeres of all selected nodes if len(self._saved_node_positions) > 1: op = GroupedOperation() for node, position in self._saved_node_positions: op.addOperation( RotateOperation(node, rotation, rotate_around_point=position)) op.push() else: for node, position in self._saved_node_positions: RotateOperation(node, rotation, rotate_around_point=position).push() self.setDragStart(event.x, event.y) return True if event.type == Event.MouseReleaseEvent: # Finish a rotate operation if self.getDragPlane(): self.setDragPlane(None) self.setLockedAxis(ToolHandle.NoAxis) self._angle = None self.propertyChanged.emit() if self._rotating: self.operationStopped.emit(self) return True