mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-04-21 05:09:37 +08:00

if the selection is cleared with this tool active, there is no way to switch to another tool than to re-select an object (by clicking it) because the tool buttons in the toolbar will have been disabled. The mouse-event happens after the selection is changed, so we need to keep track of what the selection was previously by monitoring the selection state.
191 lines
8.0 KiB
Python
191 lines
8.0 KiB
Python
# Copyright (c) 2018 Ultimaker B.V.
|
|
# Cura is released under the terms of the LGPLv3 or higher.
|
|
|
|
import os
|
|
import os.path
|
|
|
|
from PyQt5.QtCore import Qt, QTimer
|
|
|
|
from UM.Math.Vector import Vector
|
|
from UM.Tool import Tool
|
|
from UM.Application import Application
|
|
from UM.Event import Event, MouseEvent
|
|
|
|
from UM.Mesh.MeshBuilder import MeshBuilder
|
|
from UM.Scene.Selection import Selection
|
|
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
|
|
|
from cura.PickingPass import PickingPass
|
|
|
|
from UM.Operations.GroupedOperation import GroupedOperation
|
|
from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation
|
|
from UM.Operations.RemoveSceneNodeOperation import RemoveSceneNodeOperation
|
|
from cura.Operations.SetParentOperation import SetParentOperation
|
|
|
|
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
|
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
|
from UM.Scene.GroupDecorator import GroupDecorator
|
|
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
|
|
|
from UM.Settings.SettingInstance import SettingInstance
|
|
|
|
class SupportEraser(Tool):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._shortcut_key = Qt.Key_G
|
|
self._controller = Application.getInstance().getController()
|
|
|
|
self._selection_pass = None
|
|
Application.getInstance().globalContainerStackChanged.connect(self._updateEnabled)
|
|
|
|
# Note: if the selection is cleared with this tool active, there is no way to switch to
|
|
# another tool than to reselect an object (by clicking it) because the tool buttons in the
|
|
# toolbar will have been disabled. That is why we need to ignore the first press event
|
|
# after the selection has been cleared.
|
|
Selection.selectionChanged.connect(self._onSelectionChanged)
|
|
self._had_selection = False
|
|
self._skip_press = False
|
|
|
|
self._had_selection_timer = QTimer()
|
|
self._had_selection_timer.setInterval(0)
|
|
self._had_selection_timer.setSingleShot(True)
|
|
self._had_selection_timer.timeout.connect(self._selectionChangeDelay)
|
|
|
|
def event(self, event):
|
|
super().event(event)
|
|
|
|
if event.type == Event.MousePressEvent and self._controller.getToolsEnabled():
|
|
if self._skip_press:
|
|
# The selection was previously cleared, do not add/remove an anti-support mesh but
|
|
# use this click for selection and reactivating this tool only.
|
|
self._skip_press = False
|
|
return
|
|
|
|
if self._selection_pass is None:
|
|
# The selection renderpass is used to identify objects in the current view
|
|
self._selection_pass = Application.getInstance().getRenderer().getRenderPass("selection")
|
|
picked_node = self._controller.getScene().findObject(self._selection_pass.getIdAtPosition(event.x, event.y))
|
|
|
|
node_stack = picked_node.callDecoration("getStack")
|
|
if node_stack:
|
|
if node_stack.getProperty("anti_overhang_mesh", "value"):
|
|
self._removeEraserMesh(picked_node)
|
|
return
|
|
|
|
elif node_stack.getProperty("support_mesh", "value") or node_stack.getProperty("infill_mesh", "value") or node_stack.getProperty("cutting_mesh", "value"):
|
|
# Only "normal" meshes can have anti_overhang_meshes added to them
|
|
return
|
|
|
|
# Create a pass for picking a world-space location from the mouse location
|
|
active_camera = self._controller.getScene().getActiveCamera()
|
|
picking_pass = PickingPass(active_camera.getViewportWidth(), active_camera.getViewportHeight())
|
|
picking_pass.render()
|
|
|
|
picked_position = picking_pass.getPickedPosition(event.x, event.y)
|
|
|
|
# Add the anti_overhang_mesh cube at the picked location
|
|
self._createEraserMesh(picked_node, picked_position)
|
|
|
|
def _createEraserMesh(self, parent: CuraSceneNode, position: Vector):
|
|
node = CuraSceneNode()
|
|
|
|
node.setName("Eraser")
|
|
node.setSelectable(True)
|
|
mesh = MeshBuilder()
|
|
mesh.addCube(10,10,10)
|
|
node.setMeshData(mesh.build())
|
|
node.setPosition(position)
|
|
|
|
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
|
|
|
node.addDecorator(SettingOverrideDecorator())
|
|
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
|
node.addDecorator(SliceableObjectDecorator())
|
|
|
|
stack = node.callDecoration("getStack") # created by SettingOverrideDecorator
|
|
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)
|
|
|
|
root = self._controller.getScene().getRoot()
|
|
|
|
op = GroupedOperation()
|
|
# First add the node to the scene, so it gets the expected transform
|
|
op.addOperation(AddSceneNodeOperation(node, root))
|
|
|
|
# Determine the parent group the node should be put in
|
|
if parent.getParent().callDecoration("isGroup"):
|
|
group = parent.getParent()
|
|
else:
|
|
# Create a group-node
|
|
group = CuraSceneNode()
|
|
group.addDecorator(GroupDecorator())
|
|
group.addDecorator(BuildPlateDecorator(active_build_plate))
|
|
group.setParent(root)
|
|
center = parent.getPosition()
|
|
group.setPosition(center)
|
|
group.setCenterPosition(center)
|
|
op.addOperation(SetParentOperation(parent, group))
|
|
|
|
op.addOperation(SetParentOperation(node, group))
|
|
op.push()
|
|
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
|
|
|
# Select the picked node so the group does not get drawn as a wireframe (yet)
|
|
if not Selection.isSelected(parent):
|
|
Selection.add(parent)
|
|
if Selection.isSelected(group):
|
|
Selection.remove(group)
|
|
|
|
def _removeEraserMesh(self, node: CuraSceneNode):
|
|
group = node.getParent()
|
|
if group.callDecoration("isGroup"):
|
|
parent = group.getChildren()[0]
|
|
|
|
op = GroupedOperation()
|
|
op.addOperation(RemoveSceneNodeOperation(node))
|
|
if len(group.getChildren()) == 2:
|
|
op.addOperation(SetParentOperation(parent, group.getParent()))
|
|
|
|
op.push()
|
|
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
|
|
|
# Select the picked node so the group does not get drawn as a wireframe (yet)
|
|
if parent and not Selection.isSelected(parent):
|
|
Selection.add(parent)
|
|
if Selection.isSelected(group):
|
|
Selection.remove(group)
|
|
|
|
def _updateEnabled(self):
|
|
plugin_enabled = False
|
|
|
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
if global_container_stack:
|
|
plugin_enabled = global_container_stack.getProperty("anti_overhang_mesh", "enabled")
|
|
|
|
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, plugin_enabled)
|
|
|
|
|
|
|
|
def _onSelectionChanged(self):
|
|
# When selection is passed from one object to another object, first the selection is cleared
|
|
# and then it is set to the new object. We are only interested in the change from no selection
|
|
# to a selection or vice-versa, not in a change from one object to another. A timer is used to
|
|
# "merge" a possible clear/select action in a single frame
|
|
if Selection.hasSelection() != self._had_selection:
|
|
self._had_selection_timer.start()
|
|
|
|
def _selectionChangeDelay(self):
|
|
has_selection = Selection.hasSelection()
|
|
if not has_selection and self._had_selection:
|
|
self._skip_press = True
|
|
else:
|
|
self._skip_press = False
|
|
|
|
self._had_selection = has_selection
|