Help Creating a Script for Filling the White Silhouette of an Artwork

w

I frequently use a process called Trapping for my artwork. This involves making a selection and filling the silhouette of the art with white on a layer below the line art. I attempted to create a script to automate this process, but it’s not working as intended. Here’s what I’ve written so far (it’s a bit messy—I’m clearly not an expert at scripting):

from krita import *

from PyQt5.QtCore import (
        Qt,
        QPoint,
        QPointF,
        QEvent)
from PyQt5.QtGui import (
        QMouseEvent,
        QCursor)
from PyQt5.QtWidgets import (
        QWidget,
        QApplication)

#===============
#=====Functios=====
#===============
def find_current_canvas():
    application = Krita.instance()
    q_window = application.activeWindow().qwindow()
    q_stacked_widget = q_window.centralWidget()
    q_mdi_area = q_stacked_widget.currentWidget()
    q_mdi_sub_window = q_mdi_area.currentSubWindow()
    view = q_mdi_sub_window.widget()
    for c in view.children():
        if c.metaObject().className() == 'KisCanvasController':
            viewport = c.viewport()
            canvas = viewport.findChild(QWidget)
            return canvas

def click_canvas(global_pos=None):

    canvas = find_current_canvas()
    if global_pos is None:
        global_pos = QCursor.pos()# cursor position
        global_posF = QPointF(global_pos)
        posF = QPointF(canvas.mapFromGlobal(global_pos))
    else:
        global_pos = QPoint(global_pos)
        global_posF = QPointF(global_pos)
        posF = QPointF(canvas.mapFromGlobal(global_pos))

    button = Qt.LeftButton
    buttons = Qt.LeftButton
    key_state = Qt.NoModifier

    mouse_press = QMouseEvent(
            QEvent.MouseButtonPress,
            posF,
            button,
            buttons,
            key_state)

    mouse_release = QMouseEvent(
            QEvent.MouseButtonRelease,
            posF,
            button,
            buttons,
            key_state)

    if not canvas.isActiveWindow():
        canvas.activateWindow()
    QApplication.sendEvent(canvas, mouse_press)
    QApplication.sendEvent(canvas, mouse_release)
    
def create_layer_below():
    """Creates a new paint layer below the active layer in Krita and sets it as active."""
    doc = Krita.instance().activeDocument()
    if not doc:
        return None  # No active document, exit function

    currentNode = doc.activeNode()
    if not currentNode:
        return None  # No active layer selected, exit function

    def getNextSibling(node):
        """Finds the previous sibling node to insert the new layer below the current one."""
        previousNode = None
        for foundNode in node.parentNode().childNodes():
            if foundNode == node:
                return previousNode
            previousNode = foundNode
        return previousNode

    # Create new layer and insert it below the current layer
    newNode = doc.createNode("White", "paintlayer")
    currentNode.parentNode().addChildNode(newNode, getNextSibling(currentNode))



    return newNode  # Return the newly created layer in case it's needed

#===============
#=====Code main=====
#===============

# Activate contiguous selection tool
Krita.instance().action('KisToolSelectContiguous').trigger()
click_canvas()  # Click at current cursor position (top-left corner if not moved)

create_layer_below()


Krita.instance().action('invert_selection').trigger()
Krita.instance().action('fill_selection_background_color').trigger()


The script kind of works, but it fills the white area on the line art layer instead of the white layer below it. I tried changing the active layer, but it doesn’t seem to work. I’m probably doing something wrong.

Here’s the process I’m trying to automate:

1 - The line art layer is the active layer.

2 - Use the Magic Wand to select an area outside the line art (e.g., the top-left corner of the image at coordinates (1, 1)).

3 - Grow the selection by 2 pixels. The selection should only consider the active layer (line art).

4 - Invert the selection (Ctrl+Shift+I).

5 - Create a new layer below the line art layer and name it “white”.

6 - With the new layer active, fill the selected area with white.

If anyone could help me improve this script or guide me on how to fix the main issue, I’d greatly appreciate it! Thank you in advance for your time and expertise.

Hello, I found two problem with the code…
I add some plot for debug with using the brush tool instead of selectContiguous tool.
for example Krita.instance().action(‘KritaShape/KisToolBrush’).trigger() and click_canvas()
Because check wether proper positon is clicked by mouse.

The result was painted on canvas wherever you clicked the play-button in the Scripter window.
So,I append to code the forced set coordinate at 1,1 of the canvas.

   # but set 1,1 a following,then fill white color in full layer
   posF = QPointF(1,1)
   
   # so I set to 100,100
   posF = QPointF(100,100)#<-----Click  point

But another problem ‘White’ Layer never activate,and original paint layer filled.
Unfortunatry,I couldn’t solove this problem.

from krita import *
from time import sleep
from PyQt5.QtCore import (
        Qt,
        QPoint,
        QPointF,
        QEvent)
from PyQt5.QtGui import (
        QMouseEvent,
        QCursor)
from PyQt5.QtWidgets import (
        QWidget,
        QApplication)

#===============
#=====Functios=====
#===============
def find_current_canvas():
    application = Krita.instance()
    q_window = application.activeWindow().qwindow()
    q_stacked_widget = q_window.centralWidget()
    q_mdi_area = q_stacked_widget.currentWidget()
    q_mdi_sub_window = q_mdi_area.currentSubWindow()
    view = q_mdi_sub_window.widget()
    for c in view.children():
        if c.metaObject().className() == 'KisCanvasController':
            viewport = c.viewport()
            canvas = viewport.findChild(QWidget)
            return canvas

def click_canvas(global_pos=None):

    canvas = find_current_canvas()
    if global_pos is None:
        global_pos = QCursor.pos()# cursor position
        global_posF = QPointF(global_pos)
        posF = QPointF(canvas.mapFromGlobal(global_pos))

    else:
        global_pos = QPoint(global_pos)
        global_posF = QPointF(global_pos)
        posF = QPointF(canvas.mapFromGlobal(global_pos))

    button = Qt.LeftButton
    buttons = Qt.LeftButton
    key_state = Qt.NoModifier
    posF = QPointF(100,100)#<-----Click  point
    print(posF)
    mouse_press = QMouseEvent(
            QEvent.MouseButtonPress,
            posF,
            button,
            buttons,
            key_state)

    mouse_release = QMouseEvent(
            QEvent.MouseButtonRelease,
            posF,
            button,
            buttons,
            key_state)

    if not canvas.isActiveWindow():
        canvas.activateWindow()
    QApplication.sendEvent(canvas, mouse_press)
    QApplication.sendEvent(canvas, mouse_release)
    
def create_layer_below():
    """Creates a new paint layer below the active layer in Krita and sets it as active."""
    doc = Krita.instance().activeDocument()
    if not doc:
        return None  # No active document, exit function

    currentNode = doc.activeNode()
    if not currentNode:
        return None  # No active layer selected, exit function

    def getNextSibling(node):
        """Finds the previous sibling node to insert the new layer below the current one."""
        previousNode = None
        for foundNode in node.parentNode().childNodes():
            if foundNode == node:
                return previousNode
            previousNode = foundNode
        return previousNode

    # Create new layer and insert it below the current layer
    newNode = doc.createNode("White", "paintlayer")
    currentNode.parentNode().addChildNode(newNode, getNextSibling(currentNode))

    return newNode  # Return the newly created layer in case it's needed

#===============
#=====Code main=====
#===============
doc2 = Krita.instance().activeDocument()

# Activate contiguous selection tool
Krita.instance().action('KisToolSelectContiguous').trigger()

#Krita.instance().action('KritaShape/KisToolBrush').trigger() # Click Test

click_canvas()  # Click at current cursor position (top-left corner if not moved)

targetLayer = create_layer_below()
Krita.instance().action('invert_selection').trigger()
doc2.setActiveNode(targetLayer)

if doc2.activeNode() == targetLayer:
    print(f"{targetLayer.name()} is Active")
else:
    print(f"TargetLayer is {targetLayer.name()} but {doc2.activeNode().name()} is Active")
    print("Failed")

Krita.instance().action("fill_selection_foreground_color").trigger()
Krita.instance().action("deselect").trigger()