setChildNodes misorders nodes, and a separate crash-bug

Hello! I have what I think is a bug on my hands, and could use some input both on if it is and how to work through what I’m experiencing either way.

I’m writing a plugin for animations, and I’m working with what the API can currently do by keeping a group with my animation frames. To add frame(s), I append more layers to the child nodes of the group.

However, when using setChildNodes() to append nodes to a list I’ve received using childNodes(), it seems to get the ordering wrong. The results in Krita’s layers are ‘2, 3, 1, 4, 5’ despite the list of nodes being logged to the screen as ‘1, 2, 3, 4, 5’!
I would say I can reproduce this reliably, but it seems necessary to mess with the layer tool first; Sometimes Krita simply crashes between ‘C’ and ‘D’. I’m not sure why that happens either!

Using: Windows 10, Krita 5.0.0-beta2

Any help, to tell me if this is a bug, how to do what I’m doing correctly, or work around this is appreciated!

A reproduction using the scripter:

from krita import Krita, Document

class MyBug:
    @staticmethod
    def log_info(text: str) -> None:
        print(text)

    @staticmethod
    def remove_layer_if_exists(document: Document, layer_name: str) -> None:
        layer = document.nodeByName(layer_name)
        if layer:
            layer.remove()

    def do_a_thing(self) -> None:
        self.log_info("Doing the thing!")
        active_document = Krita.instance().activeDocument()
    
        temp_layer_name = "MY_LAYER"
        cloned_layer_name = f"{temp_layer_name}_CLONE"
        if active_document:
            self.log_info("A")
            self.remove_layer_if_exists(active_document, temp_layer_name)
            self.remove_layer_if_exists(active_document, cloned_layer_name)
            self.log_info("B")
            temp_layer = active_document.createGroupLayer(temp_layer_name)
            self.log_info("C")
            active_document.rootNode().addChildNode(temp_layer, None)
            self.log_info("D")
            child_nodes = [active_document.createNode("01", "paintLayer"),
                           active_document.createNode("02", "paintLayer"),
                           active_document.createNode("03", "paintLayer")]
            self.log_info("E")
            temp_layer.setChildNodes(child_nodes)
            self.log_info("F")
    
            cloned_layer = temp_layer.clone()
            self.log_info("G")
            cloned_layer.setName(cloned_layer_name)
            additional_child_nodes = [active_document.createNode("04", "paintLayer"),
                                      active_document.createNode("05", "paintLayer")]
            nodes = cloned_layer.childNodes() + additional_child_nodes
            self.log_info(str([n.name() for n in nodes]))
            self.log_info("H")
            cloned_layer.setChildNodes(nodes)
            self.log_info("I")
            active_document.rootNode().addChildNode(cloned_layer, None)
        self.log_info("Exiting Doing the thing function.")

MyBug().do_a_thing()

You can always do this:

from krita import Krita, Document

class MyBug:
    @staticmethod
    def log_info(text: str) -> None:
        print(text)

    @staticmethod
    def remove_layer_if_exists(document: Document, layer_name: str) -> None:
        layer = document.nodeByName(layer_name)
        if layer:
            layer.remove()

    def do_a_thing(self) -> None:
        self.log_info("Doing the thing!")
        active_document = Krita.instance().activeDocument()
    
        temp_layer_name = "MY_LAYER"
        cloned_layer_name = f"{temp_layer_name}_CLONE"
        if active_document:
            self.log_info("A")
            self.remove_layer_if_exists(active_document, temp_layer_name)
            self.remove_layer_if_exists(active_document, cloned_layer_name)
            self.log_info("B")
            temp_layer = active_document.createGroupLayer(temp_layer_name)
            self.log_info("C")
            active_document.rootNode().addChildNode(temp_layer, None)
            self.log_info("D")
            child_nodes = [active_document.createNode("01", "paintLayer"),
                           active_document.createNode("02", "paintLayer"),
                           active_document.createNode("03", "paintLayer")]
            self.log_info("E")
            for node in child_nodes:
                temp_layer.addChildNode(node, None)
 
            self.log_info("F")
            cloned_layer = temp_layer.clone()
            self.log_info("G")
            cloned_layer.setName(cloned_layer_name)
            additional_child_nodes = [active_document.createNode("04", "paintLayer"),
                                      active_document.createNode("05", "paintLayer")] 
            for node in additional_child_nodes:
                cloned_layer.addChildNode(node, None)
            active_document.rootNode().addChildNode(cloned_layer, None)    
        self.log_info("Exiting Doing the thing function.")

MyBug().do_a_thing()
1 Like

Thank you! This most certainly is a working solution.
I still have concerns about whether the two bugs are indeed bugs. Do you think they should be posted?

You can open a bug report. I suspect the issue is that when the nodes are removed, then added back in setChildNodes it starts acting weirdly. It generally isn’t a good idea to set the nodes of existing nodes as the intention is mostly for setting new nodes.

In general, I wouldn’t use setChildNodes at all since it doesn’t happen within a transaction, and that has often caused crashes. So it should be redone.

But it doesn’t hurt to open a bug report.