Python - Select Tool and Tool options

How do I select a tool and change the tool options using Python? I want to make a script that selects the transform tool, changes the tool option to warp and then changes the subdivisions to 6. So far I only know how to select a tool using

from krita import *
Krita.instance().action("KisToolTransform").trigger()

but I do not know how to change tool options.

Summoning @Kapyia, who is quite experienced in messing with those tool options dockers. :wink:
From what I’m aware of, this may be possible, but not that easy, as krita scripting currently don’t give access to those values.

You can get to the tool options docker with something like:

from krita import *
from PyQt5.QtWidgets import QDockWidget

Krita.instance().action("KisToolTransform").trigger()
qWin = Application.activeWindow().qwindow()
toolOptions = qWin.findChild(QDockWidget, 'sharedtooldocker')

print(toolOptions.children()) 

Application is just the same as Krita.instance() but shorter.
qwin will hold the actual krita window object from qt.
findChild() can be used to get this options docker.

Using print I managed to check that this toolOptions object holds things like buttons and actions - buttons have button.click() option, while actions can be triggered action.trigger().

I guess that to do what you want, you would have to find those button and field in a layout, but I’m quite new to krita scripting, and don’t really know where to search for them.

1 Like

Thank you for the help. I will try searching toolOptions.children() to find the tool option buttons and fields. I will update this topic if I find a solution.

Sorry I can’t give more information than that. I hope somebody with more knowledge will point you where to search for this objects structure, as it can be quite hard to find the correct one just printing all those objects and their children hoping to find the actual buttons.
I’m interested in learning how to look for this kind of information too.

1 Like

Thanks for letting me know that it is not effective to find the buttons by printing the objects and children. Hopefully someone can point me in the right direction. Being able to quickly change tool option settings would greatly increase my workflow.

1 Like

Small script for dumping qt object tree for given qobject.

from krita import Krita


def dump_tree(qobj):
    stack = [(qobj, 0)]
    while stack:
        cursor, depth = stack.pop(-1)  # depth first
        indent = depth * "  "
        cls_name = type(cursor).__name__
        meta_cls_name = cursor.metaObject().className()
        obj_name = cursor.objectName()
        print(f"{indent}cls: {cls_name}, meta_cls_name: {meta_cls_name}, obj_name: {obj_name!r}")
        stack.extend((c, depth +1) for c in cursor.children())


qwindow = Krita.instance().activeWindow().qwindow()
dump_tree(qwindow)

when you find rigth childs cls and name, you can use following.
(replace <cls_name> with cls ie. QWidget)

from krita import Krita


qwindow = Krita.instance().activeWindow().qwindow()
target_qobj = qwindow.findChild(<cls_name>, "obj_name")

happy widget huntting :slight_smile:

2 Likes

That is perfect, I’ll try it soon for sure. Thank you :slight_smile:

Thank You! But I’m getting a syntax error with you solution. The cls_name is QToolButton and obj_name is warpButton. When I put those in

from krita import Krita

qwindow = Krita.instance().activeWindow().qwindow()
target_qobj = qwindow.findChild(<QToolButton>, "warpButton")

I get a syntax error.

hmm… yep, is not correct python syntax. (<something_something> is used in python scripting to express that something is just placeholder)

from krita import Krita
from PyQt5.QtWidgets import QToolButton

qwindow = Krita.instance().activeWindow().qwindow()
target_qobj = qwindow.findChild(QToolButton, "warpButton")
1 Like

There is really no API built around the tool options right now. For some of the tools you can get a bit tricky and use the readSettings() and writeSettings() from the Krita.instance() class

https://api.kde.org/appscomplete-api/krita-apidocs/libs/libkis/html/classKrita.html

That changes the kritarc file. Quite a few tools use that so Krita remembers what the artists last configuration options were for all their tools. Sadly, the transform tool doesn’t save any of its state. It completely resets when you do a new transformation.

We would really have to think about a better way to organize tool options if we would want it to easily be accessible. Every tool option area is manually built right now in C++.

2 Likes

Thanks, the warp button works now. Do you know how to increase the subdivisions in the warp options of the transform tool? From the list of objects in the dump tree you provided, the only object that is called subdivision and is a QSpinBox is “intSubdivision” but when I do

from krita import Krita
from PyQt5.QtWidgets import QSpinBox

qwindow = Krita.instance().activeWindow().qwindow()
target =  qwindow.findChild(QSpinBox, 'intSubdivision')
target.stepUp()

nothing happens.

The stepUp function works for other QSpinBoxes but not for this one. So, I’m guessing ‘intSubdivision’ is not linked to warp subdivisions.

Let’s continue with this walk on a wildside of Krita non-API :slight_smile:

QSpinBox named “intSubdivision” is abit generic thing to look in Kritas widget tree.
So better to first find someparent that is a bit more defined like metaObject().className() == “KisToolTransformConfigWidget”.
At that widget potential children count is much smaller.

and it turns out that name of spinbox widget that controls divisions is “densityBox”

from krita import Krita
from PyQt5.QtWidgets import QWidget, QSpinBox, QToolButton


def dump_tree(qobj):
    stack = [(qobj, 0)]
    while stack:
        cursor, depth = stack.pop(-1)  # depth first
        indent = depth * "  "
        cls_name = type(cursor).__name__
        meta_cls_name = cursor.metaObject().className()
        obj_name = cursor.objectName()
        text = ""
        try:
            text = cursor.text()
        except:
            pass
        print(f"{indent}cls: {cls_name}, meta_cls_name: {meta_cls_name}, obj_name: {obj_name!r}, text: {text!r}")
        stack.extend((c, depth +1) for c in cursor.children())


def find_transform_tool_options_widget():
    qwindow = Krita.instance().activeWindow().qwindow()
    for c in qwindow.findChildren(QWidget):
        if c.metaObject().className() == "KisToolTransformConfigWidget":
            return c


def set_my_warp_settings():
    transform_widget = find_transform_tool_options_widget()
    # dump_tree(transform_widget)
    
    warpButton = transform_widget.findChild(QToolButton, "warpButton")
    warpButton.click()

    warp_widget = transform_widget.findChild(QWidget, "warpTransformWidget")
    densityBox = warp_widget.findChild(QSpinBox, 'densityBox')
    densityBox.setValue(13)


set_my_warp_settings()

Ps. I wonder… how to localization of Krita tools will work in future :smiley:

Thank you. I appreciate the help.

Things like object names and variable names are going to be staying in English. If it isn’t something the artist sees, we usually try to stick to English. Strings that are translatable are usually wrapped with a i18n(‘string to translate’) that is extracted.

I remember a number of months back we did have an idea of better naming some of these objects. We can certainly change the names…or add object names to things that might not have them. There are so many objects sometimes it comes down to laziness when doing naming.

OT; What I was wondering about was localization when QObject.objectName() is empty or none unique or too generic.

Translating locaization texts for QLabel.setText() is hard when object tree contains 100+ unnamed QLabel objects or names are like “label_01”, “label_02”, …

But good to hear that QObject.objectNames are getting better in future :slight_smile:

the object name is a different property than the label text. The label text is what gets translated. There is no reference to object names with translations. They don’t have to be set for translations to work.

If there are certain area areas we could improve the object names let us know to make picking easier. Maybe there are some object names that are also duplicated. We probably can give them more unique names.

I would say if there is any area, just make a post on here…or file a bug report so we can look at it. These changes are usually pretty simple to fix.