Extend Python API with vector methods and remove node fix
BUGFIX: Node removal via API then undo caused a crash
If a user adds a shape to a layer, then the API removes the layer and the user hits undo. Krita will crash. This fixes it by adding layer removal to the undo.
Python API: Add export and import SVG strings
Added the addShapesFromSvg and toSvg methods to Vector Layers.
Python API: Expose more Shape methods to the API
This patch adds the following methods:
- setType: So the plugin can take ownership of the shape.
- zIndex, setZIndex: So the plugin can control if the shape should be in front or back.
- selectable, setSelectable, geometryProtected, setGeometryProtected: So the plugin can take control of managing the shapes.
- transformation, setTransformation: So the plugin can get back matrix data and adjust them
- remove: So the plugin can delete shapes
- update and updateAbsolute: So shape changes can be updated (like fixes setVisible)
This patch Modifies the following:
- toSvg: Adds 2 optional parameters. prependStles and stripTextMode this lets the plugin get the data it needs from the shapes. Being optional parameters, it retains backwards compatibility
This patch Modifies the following:
- toSvg: Adds 2 optional parameters. prependStyles and stripTextMode this lets the plugin get the data it needs from the shapes. Being optional parameters, it retains backwards compatibility
Test Plan
Tools->scripter and run this: (It will go through all the methods on a 2 second timer for about 20 seconds. And output details into terminal)
from PyQt5 import QtCore, QtGui, QtWidgets
import re
def matrix_array(mx):
return [mx.m11(),mx.m12(),mx.m13(),mx.m21(),mx.m22(),mx.m23(),mx.m31(),mx.m32(),mx.m33()]
def stepper(i):
print ("STEP", i)
doc = Krita.instance().activeDocument()
node = doc.activeNode()
shapes = [None, None, None, None]
if node.type() == 'vectorlayer':
tempShapes = node.shapes()
for shape in tempShapes:
shapes[int(re.match(r'shape(\d+)', shape.name()).group(1))]=shape
if i == 0:
print ("Creating Vector Layer")
newLayer = doc.createVectorLayer('VLayer')
doc.rootNode().addChildNode(newLayer,None)
doc.setActiveNode(newLayer)
if i == 1:
print ("TEST: import SVG Test")
newShapes=node.addShapesFromSvg('''<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"><!-- Created using Krita: https://krita.org --><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:krita="http://krita.org/namespaces/svg/krita" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" width="307.2pt" height="307.2pt" viewBox="0 0 307.2 307.2"><defs><linearGradient id="gradient0" gradientUnits="objectBoundingBox" x1="0" y1="0" x2="1" y2="1" spreadMethod="pad"><stop stop-color="#9f6c00" offset="0" stop-opacity="1"/><stop stop-color="#6b6a7b" offset="1" stop-opacity="1"/></linearGradient></defs><path id="shape0" transform="translate(21.5999992104248, 215.099992137147)" fill="#2b0000" fill-rule="evenodd" stroke="#d00000" stroke-width="6" stroke-linecap="square" stroke-linejoin="bevel" d="M76.5 64.8L0 64.8L40.5 0Z" sodipodi:nodetypes="cccc"/><rect id="shape1" transform="translate(200.100000230293, 16.799999934202)" fill="url(#gradient0)" fill-rule="evenodd" stroke="#ffb100" stroke-width="24" stroke-linecap="square" stroke-linejoin="bevel" width="90" height="66"/><text id="shape2" krita:useRichText="true" krita:textVersion="2" transform="translate(8.9999996710103, 31.4999995065155)" fill="#0026ff" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" font-size-adjust="0.372962" font-stretch="normal" letter-spacing="0" word-spacing="0"><tspan x="0">Top Shape2</tspan></text><text id="shape3" krita:useRichText="true" krita:textVersion="2" transform="translate(211.499992268743, 273.599990656694)" fill="#0a0a00" stroke-opacity="0" stroke="#000000" stroke-width="0" stroke-linecap="square" stroke-linejoin="bevel" font-size="14" font-size-adjust="0.372962" font-stretch="normal" font-weight="700" letter-spacing="0" word-spacing="0"><tspan x="0">Bottom Shape3</tspan></text></svg>''')
for shape in newShapes:
print ("ADDED SHAPE:", shape.name() )
print ("TEST: Complete")
elif i == 2:
print ("TEST: Remove Layer")
print ("RESPONSE:", node.remove() )
print ("TEST: Complete")
elif i == 3:
print ("TEST: Undo layer removal")
Krita.instance().action('edit_undo').trigger()
print ("TEST: Complete")
elif i == 4:
print ("TEST: Get Shape properties")
for si in range(len(shapes)):
print ("SHAPE #"+str(si),
"type:", shapes[si].type(),
"visible:", shapes[si].visible(),
"zindex:", shapes[si].zIndex(),
"transformation:", matrix_array(shapes[si].transformation()),
"selectable:", shapes[si].selectable(),
"geometryprotected:", shapes[si].geometryProtected()
)
print ("TEST: Complete")
elif i == 5:
print ("TEST: Change Shape properties")
for si in range(len(shapes)):
if si == 0:
print ("Make Shape 0 invisible")
shapes[0].setVisible(False)
shapes[0].update()
elif si == 1:
print ("Transform Shape 1")
rect = shapes[1].boundingBox()
tran = shapes[1].transformation()
tran.rotate(45)
shapes[1].setTransformation(tran)
rect |= shapes[1].boundingBox()
shapes[1].updateAbsolute(rect)
elif si == 2:
print ("Move Shape2 to Shape 1")
rect = shapes[2].boundingBox()
shapes[2].setPosition(shapes[1].position())
rect |= shapes[2].boundingBox()
shapes[2].updateAbsolute(rect)
elif si == 3:
print ("Delete Shape 3")
print ("Deleted:", shapes[3].remove())
break
print ("SHAPE #"+str(si),
"visible:", shapes[si].visible(),
"transformation:", matrix_array(shapes[si].transformation()),
)
print ("TEST: Complete")
elif i == 6:
print ("TEST: Undo delete shape 3 and restore visible shape 0")
Krita.instance().action('edit_undo').trigger()
shapes[0].setVisible(True)
shapes[0].update()
print ("TEST: Complete")
elif i == 7:
print ("TEST: Make Shape 2 go behind Shape 1")
shapes[2].setZIndex(-1)
shapes[2].update()
print ("TEST: Complete")
elif i == 8:
print ("TEST: Block Shapes from user")
print ("Make Shape 0 unselectable")
shapes[0].setSelectable(False)
print ("Make Shape 1 geometry locked")
shapes[1].setGeometryProtected(True)
print ("TEST: Complete")
elif i == 9:
print ("TEST: Output Shape data")
for si in range(len(shapes)):
print ("SVG SHAPE #"+str(si),
shapes[si].toSvg(True, False) );
print ( "SVG LAYER", node.toSvg() )
print ("TEST: Complete")
print ("DONE!")
if i < 10: QtCore.QTimer.singleShot(2000, lambda: stepper(i+1) )
print ("Starting Test")
newdoc = Krita.instance().createDocument(512, 512, "Python test document", "RGBA", "U8", "", 120.0)
Krita.instance().activeWindow().addView(newdoc)
stepper(0)
Formalities Checklist
-
I confirmed this builds. -
I confirmed Krita ran and the relevant functions work. -
I tested the relevant unit tests and can confirm they are not broken. (If not possible, don't hesitate to ask for help!) -
I made sure my commits build individually and have good descriptions as per KDE guidelines. -
I made sure my code conforms to the standards set in the HACKING file. -
I can confirm the code is licensed and attributed appropriately, and that unattributed code is mine, as per KDE Licensing Policy.
Edited by Know Zero