Thanks for the suggestion, and sorry for not being more clear. The code I posted above was just what I got working. Namely, converting document coordinates to widget coordinates and clicking them. I though there wasn’t much value in providing the code of the failed attempts to use the Fill-and-Enclose tool.
But using a TabletMove
event is bringing me a lot closer to my goal! However, for some reason, it does correctly drag the rectangle across the canvas, but when it releases, nothing happens.
In the meanwhile, I’ve also discovered QTest.mousePress
and co., which does actually work as intended, as long as the canvas isn’t so zoomed in that it goes offscreen. See below for a video demonstration of both functions and their code.
from krita import Krita
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtTest import QTest
def get_q_view(view):
window = view.window()
q_window = window.qwindow()
q_stacked_widget = q_window.centralWidget()
q_mdi_area = q_stacked_widget.findChild(QMdiArea)
for v, q_mdi_view in zip(window.views(), q_mdi_area.subWindowList()):
if v == view:
return q_mdi_view.widget()
def get_q_canvas(q_view):
scroll_area = q_view.findChild(QAbstractScrollArea)
viewport = scroll_area.viewport()
for child in viewport.children():
cls_name = child.metaObject().className()
if cls_name.startswith('Kis') and ('Canvas' in cls_name):
return child
def get_transform(view):
def _offset(scroller):
mid = (scroller.minimum() + scroller.maximum()) / 2.0
return -(scroller.value() - mid)
canvas = view.canvas()
document = view.document()
q_view = get_q_view(view)
area = q_view.findChild(QAbstractScrollArea)
zoom = (canvas.zoomLevel() * 72.0) / document.resolution()
transform = QTransform()
transform.translate(
_offset(area.horizontalScrollBar()),
_offset(area.verticalScrollBar()))
transform.rotate(canvas.rotation())
transform.scale(zoom, zoom)
return transform
def get_cursor_in_document_coords():
app = Krita.instance()
view = app.activeWindow().activeView()
document = view.document()
if document:
q_view = get_q_view(view)
q_canvas = get_q_canvas(q_view)
transform = get_transform(view)
transform_inv, _ = transform.inverted()
global_pos = QCursor.pos()
local_pos = q_canvas.mapFromGlobal(global_pos)
center = q_canvas.rect().center()
inv_pos = transform_inv.map(local_pos - QPointF(center))
centerCanvas = QPointF(0.5 * document.width(), 0.5 * document.height())
return inv_pos + centerCanvas
def get_global_from_document_coords(doc_x, doc_y):
app = Krita.instance()
view = app.activeWindow().activeView()
document = view.document()
if document:
q_view = get_q_view(view)
q_canvas = get_q_canvas(q_view)
transform = get_transform(view)
# Calculate the center of the document
centerCanvas = QPointF(0.5 * document.width(), 0.5 * document.height())
#Convert document coordinates to coordinates relative to the center
local_pos = QPointF(doc_x, doc_y) - centerCanvas
# Apply the transformation to convert to canvas coordinates
transformed_pos = transform.map(local_pos)
# Find the center of the canvas
center = q_canvas.rect().center()
# Convert canvas coordinates to local widget coordinates
widget_local_pos = transformed_pos + QPointF(center)
return QPoint(int(widget_local_pos.x()), int(widget_local_pos.y()))
def sleep(value):
loop = QEventLoop()
QTimer.singleShot(value, loop.quit)
loop.exec()
def click_canvas(x0, y0):
"""
https://doc.qt.io/qt-5/qtabletevent.html
"""
pos0 = get_global_from_document_coords(x0, y0)
# pos1 = get_global_from_document_coords(x1, y1)
app = Krita.instance()
view = app.activeWindow().activeView()
document = view.document()
if document is None:
return
q_view = get_q_view(view)
canvas = get_q_canvas(q_view)
device = QTabletEvent.Stylus
pointer_type = QTabletEvent.Pen
pressure = 1
x_tilt = 0
y_tilt = 0
tangential_pressure = 0.0
rotation = 0.0
z_pos = 0
key_state = Qt.NoModifier
unique_id = 1234 # ???
button = Qt.LeftButton
buttons = Qt.LeftButton
canvas.activateWindow()
global_pos = QPointF(canvas.mapToGlobal(pos0))
table_press = QTabletEvent(
QEvent.TabletPress,
pos0,
global_pos,
device,
pointer_type,
pressure,
x_tilt,
y_tilt,
tangential_pressure,
rotation,
z_pos,
key_state,
unique_id,
button,
buttons)
global_pos = QPointF(canvas.mapToGlobal(pos1))
table_release = QTabletEvent(
QEvent.TabletRelease,
pos0,
global_pos,
device,
pointer_type,
pressure,
x_tilt,
y_tilt,
tangential_pressure,
rotation,
z_pos,
key_state,
unique_id,
button,
buttons)
QApplication.sendEvent(canvas, table_press)
QApplication.sendEvent(canvas, table_release)
def drag_canvas_QTest(x0, y0, x1, y1):
def sleep(value):
loop = QEventLoop()
QTimer.singleShot(value, loop.quit)
loop.exec()
pos0 = get_global_from_document_coords(x0, y0)
pos1 = get_global_from_document_coords(x1, y1)
app = Krita.instance()
view = app.activeWindow().activeView()
document = view.document()
if document is None:
return
q_view = get_q_view(view)
canvas = get_q_canvas(q_view)
device = QTabletEvent.Stylus
pointer_type = QTabletEvent.Pen
pressure = 1
x_tilt = 0
y_tilt = 0
tangential_pressure = 0.0
rotation = 0.0
z_pos = 0
key_state = Qt.NoModifier
unique_id = 1234 # ???
button = Qt.LeftButton
buttons = Qt.LeftButton
canvas.activateWindow()
QTest.mousePress(canvas, Qt.LeftButton, Qt.NoModifier, pos0)
sleep(500)
QTest.mouseMove(canvas, pos1)
sleep(500)
QTest.mouseRelease(canvas, Qt.LeftButton, Qt.NoModifier, pos1)
def drag_canvas_TabletEvent(x0, y0, x1, y1):
pos0 = get_global_from_document_coords(x0, y0)
pos1 = get_global_from_document_coords(x1, y1)
app = Krita.instance()
view = app.activeWindow().activeView()
document = view.document()
if document is None:
return
q_view = get_q_view(view)
canvas = get_q_canvas(q_view)
device = QTabletEvent.Stylus
pointer_type = QTabletEvent.Pen
global_pos = QPoint()
pressure = 1
x_tilt = 0
y_tilt = 0
tangential_pressure = 0.0
rotation = 0.0
z_pos = 0
key_state = Qt.NoModifier
unique_id = 1234 # ???
button = Qt.LeftButton
buttons = Qt.LeftButton
canvas.activateWindow()
table_press = QTabletEvent(
QEvent.TabletPress,
pos0,
global_pos,
device,
pointer_type,
pressure,
x_tilt,
y_tilt,
tangential_pressure,
rotation,
z_pos,
key_state,
unique_id,
button,
buttons)
table_move = QTabletEvent(
QEvent.TabletMove,
pos1,
global_pos,
device,
pointer_type,
pressure,
x_tilt,
y_tilt,
tangential_pressure,
rotation,
z_pos,
key_state,
unique_id,
button,
buttons)
table_release = QTabletEvent(
QEvent.TabletRelease,
pos1,
global_pos,
device,
pointer_type,
pressure,
x_tilt,
y_tilt,
tangential_pressure,
rotation,
z_pos,
key_state,
unique_id,
button,
buttons)
QApplication.sendEvent(canvas, table_press)
sleep(500)
QApplication.sendEvent(canvas, table_move)
sleep(500)
QApplication.sendEvent(canvas, table_release)
app = Krita.instance()
view = app.activeWindow().activeView()
document = view.document()
drag_canvas_TabletEvent(0,0,document.width(),document.height())
#drag_canvas_QTest(0,0,document.width(),document.height())
Krita.instance().activeDocument().refreshProjection()
If at all possible, I’d like to use the TabletEvent version, as the QTest function, as shown in the video, only works when the entire canvas is mostly on-screen, and if it isn’t it sends the mouse flying off somewhere, which is quite irritating.
Problem now is that I can’t for the life of me figure out why the TabletEvent function doesn’t trigger the Fill-and-Enclose tool on releasing.