Proper way to activate new layer?

I use this part of the code to fill the selection with particular color

    layer = doc.createNode("Paint", "paintlayer")
    root.addChildNode(layer, None)
    doc.setActiveNode(layer)

    for i in range(color_count):
        newSelection = Selection()
        newSelection.select(i * s, 0, s, s, 255)    
        doc.setSelection(newSelection)
        app.action('fill_selection_foreground_color').trigger()

However once the operation is finished currently selected layer is filled with colors and my “Paint” layer is empty.

Is there other way to make the layer active such that changes applied there?

Sys Info

Krita

Version: 4.4.8
Languages: en_US, en
Hidpi: true

Qt

Version (compiled): 5.12.11
Version (loaded): 5.12.11

This worked in Win10, Krita 4.4.8

Create empty document and run in scripter.

from krita import (
        Krita,
        Selection,
        ManagedColor)

from PyQt5.QtGui import (
        QColor,)

app = Krita.instance()
doc = app.activeDocument()
root = doc.rootNode()
win = app.activeWindow()
view = win.activeView()
s = 32
colors = [
        QColor(255, 0, 0),
        QColor(0, 255, 0),
        QColor(0, 0, 255),
        QColor(255, 255, 0),
        QColor(0, 255, 255),
        QColor(255, 0, 255)]

layer = doc.createNode("My new layer", "paintlayer")
root.addChildNode(layer, None)
doc.setActiveNode(layer)

for i, color in enumerate(colors):
    newSelection = Selection()
    x, y = i * s, 0
    width, height = s, s
    opacity = 255
    newSelection.select(x, y, width, height, opacity)
    doc.setSelection(newSelection)
    mColor = ManagedColor.fromQColor(color)
    view.setForeGroundColor(mColor)
    app.action('fill_selection_foreground_color').trigger()

result:
“My new layer” has 6 color boxes and last box has current selection.

maybe document also created in script, and view to new document is NOT active?

/AkiR

2 Likes

That is strange
Your code works fine.

And I experimented a bit.
I use QInputDialog.getInt in the script.
And I noticed that this behaviour is actually when I use it.

Any suggestions how to better deal with it?

from random import (
        randint,)

from krita import (
        Krita,
        Selection,
        ManagedColor)

from PyQt5.QtGui import (
        QColor,)

from PyQt5.QtWidgets import (
    QInputDialog,)


def make_blocks(blockCount):
    app = Krita.instance()
    doc = app.activeDocument()
    root = doc.rootNode()
    win = app.activeWindow()
    view = win.activeView()
    s = 32
    colors = [QColor(randint(0, 255), randint(0, 255), randint(0, 255)) for _ in range(blockCount)]

    layer = doc.createNode("My new layer", "paintlayer")
    root.addChildNode(layer, None)
    doc.setActiveNode(layer)

    for i, color in enumerate(colors):
        newSelection = Selection()
        x, y = i * s, 0
        width, height = s, s
        opacity = 255
        newSelection.select(x, y, width, height, opacity)
        doc.setSelection(newSelection)
        mColor = ManagedColor.fromQColor(color)
        view.setForeGroundColor(mColor)
        app.action('fill_selection_foreground_color').trigger()


blockCount, doIt = QInputDialog.getInt(
        None,
        "Make Blocks",
        "give block count",
        value=1,
        min=1,
        max=100)
if doIt:
    make_blocks(blockCount)

still works correctly. :thinking:

/AkiR

2 Likes

Yes. It works on my PC too.
It seems to be the case on a slower PCs.

I tested my script on a Virtual Machine (that is significantly slower).
I think that is the case. When PC is slow operations are finished in a bit different order. On a fast PC it just has enough time to finish it.

The only way to fix it (as an idea) is to crate custom QDialog and use exec_() method (that should block computations enough).

Thanks for validating the code on your side :slight_smile:
I think I will move on by myself now.

1 Like

Yes. Having custom dialog did fix the issue:

class CustomDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.setWindowTitle("HELLO!")

        QBtn = QDialogButtonBox.Ok | QDialogButtonBox.Cancel

        self.buttonBox = QDialogButtonBox(QBtn)
        self.buttonBox.accepted.connect(self.accept)
        self.buttonBox.rejected.connect(self.reject)

        self.lineedit = QLineEdit()
        self.lineedit.setText("10")
        self.onlyint = QIntValidator()
        self.lineedit.setValidator(self.onlyint)

        self.layout = QVBoxLayout()
        message = QLabel("Something happened, is that OK?")
        self.layout.addWidget(message)
        self.layout.addWidget(self.lineedit)
        self.layout.addWidget(self.buttonBox)
        self.setLayout(self.layout)


def dialogGetInt():
    dlg = CustomDialog()
    if dlg.exec():
        print("Success!")
        print(dlg.lineedit.text())
        return int(dlg.lineedit.text())
    else:
        print("Cancel!")
    return 3

P.S. Code is a bit messy, though you can get the idea )
@AkiR maybe it will be helpful to you too :wink:

1 Like

Just want to say that there is no reason to use setActiveLayer when using addChildNode as the new layer will automatically become the active layer. If anything, it seems to sometimes get a bit buggy when you do that.

2nd, what you may want to run is doc.waitForDone() after creating a new layer.

Actually I played a bit longer. And it still acts kinda weirdly. First it applies changes then creates new layer.
With or without doc.saitForDone. And even with custom Dialog window.

Seems like it is some kind of bug. Have no idea.

If you want the most reliable way to know if a layer has switched

layerBox = qwin.findChild(QtWidgets.QDockWidget, "KisLayerBox")
layerList = layerBox.findChild(QtWidgets.QTreeView,"listLayers")

layerList.model().sourceModel().dataChanged.connect(layerChanged)
layerList.model().sourceModel().modelReset.connect(layerChanged)
layerList.model().sourceModel().rowsRemoved.connect(layerChanged)
        

But if you want a more simpler way, you can also use a timer

QtCore.QTimer.singleShot(500, myCommandHere)
1 Like

Interesting. I will try this one out.

Hm… I just checked. Theoretically… Could this one be an issue.
The thing is that I always keep my PC hibernated. And it is indeed can make some application work strangely (I have seen it in other cases). Could be the case with Krita too.

I just checked with virtual machine that started fresh and the code works properly.

heyy :smiley: @yetanotherpainter
based upon @AkiR first codes

doc.setSelection(newSelection)
and
view.setForeGroundColor(mColor)

works perfectly, it does make selection and change the foreground color,
but on my potato laptop sometimes its end up with a blank paint layer like you

after some testing, i guess it was because of the child node didn’t get added first, addChildNode() is likely slower than app.action() and setSelection()

try add QTest.qWait() and setActiveNode() before painting
it’ll be something lke this :

    from PyQt5 import QtTest
    
    app = Krita.instance()
    doc = app.activeDocument()
    root = doc.rootNode()
    view = app.activeWindow().activeView()
    
    layer = doc.createNode("Paint", "paintlayer")
    root.addChildNode(layer, None)
    QtTest.QTest.qWait(100) #___wait 100ms for the child node to be added first

    newSelection = Selection()
    newSelection.select(100, 100, 32, 32, 255)    
    doc.setSelection(newSelection)
    mColor = ManagedColor.fromQColor(QColor(255, 0, 0))
    view.setForeGroundColor(mColor)
    doc.setActiveNode(layer) #___prevent interruption from interrupted by clicking/selecting another layer
    app.action('fill_selection_foreground_color').trigger()

or using @ KnowZero suggestion
it’ll be like this, using additional lambda :

    app = Krita.instance()
    doc = app.activeDocument()
    root = doc.rootNode()
    view = app.activeWindow().activeView()
    
    layer = doc.createNode("Paint", "paintlayer")
    root.addChildNode(layer, None)
    QtCore.QTimer.singleShot(100, lambda:self.delayed(app,doc,view,layer)) #___wait 100ms and call func with arguments

    def delayed(self,app,doc,view,layer):
        newSelection = Selection()
        newSelection.select(100, 100, 32, 32, 255)    
        doc.setSelection(newSelection)
        mColor = ManagedColor.fromQColor(QColor(255, 0, 0))
        view.setForeGroundColor(mColor)
        doc.setActiveNode(layer) #___prevent interruption from interrupted by clicking/selecting another layer
        app.action('fill_selection_foreground_color').trigger()

for me it works, thank you guys for sharing this all :smiley: !

4 Likes