Buli Brush Switch

Hi

A new plugin :sweat_smile:

From this topic, I’ve tried to implement something that could fit to different need.

So, you’ll find on github the Buli Brush Switch plugin.

All instructions to download/install it are provided on main plugin page.

Short documentation & screenshots are also provided on github page.

Notes


Beta version & bugs

Provided plugin is a beta version (version 0.2.0b)
Feedback are welcome, but keep in mind this is still a beta version, even if current version seems stable:

  • There’s some bugs!
    – Please also be aware that current version of plugin tweak a little bit the shortcut system as Krita is not aimed to manage dynamic add/remove/update shortcuts information in Settings dialog box.

Requirements

Plugin require Krita 5 and currently, it have only been tested on Krita 5-beta5 Linux appimage
Basic tests made on Windows 10 with Krita 5-beta5
Should work on MacOS too


[2021-12-11] release v0.2.1b

  • Fix bug Invalid plugin initialisation

[2021-12-11] release v0.2.0b

  • Improve Popup brushes list
  • Implement Default behaviour option for brushes with specific values
  • Implement Modification of brush properties from brush settings
  • Implement Specific paint tool for brush
  • Implement Specific background color for brush
  • Fix bug Selecting brush from Popup brushes list
  • Fix bug Error on Krita’s exit
  • Fix bug Shortcut lost on tool selection
  • Fix bug Non modal settings window
  • Fix bug Krita’s brush properties lost
  • Fix bug Invalid selected brush
  • Fix bug Difference according to method used to exit selected plugin brush
  • Fix bug Missing icon on Brushes list settings

For screenshots and detailed functionalities description, you can read the full detailed release content directly on github repository.


Documentation from main Buli Brush Switch page has been updated.


Grum999

16 Likes

Installed per instructions, but as soon as I start beta3 on Windows, I get an error window.

Log:
KeyError
Python 3.8.1: C:\Program Files\Krita (x64)\bin\krita.exe
Thu Dec 2 11:48:03 2021

A problem occurred in a Python script. Here is the sequence of
function calls leading up to the error, in the order they occurred.

C:\Users\Gremr\AppData\Roaming\krita\pykrita\bulibrushswitch\bulibrushswitch.py in createActions(self=<bulibrushswitch.bulibrushswitch.BuliBrushSwitch object>, window=<PyKrita.krita.Window object>)
141 brushes=BBSSettings.get(BBSSettingsKey.CONFIG_BRUSHES_LIST_BRUSHES)
142 for brushNfo in brushes:
143 action=BBSSettings.brushAction(brushNfo[BBSBrush.KEY_UUID], brushNfo[BBSBrush.KEY_NAME], brushNfo[BBSBrush.KEY_COMMENTS], True, window)
144
145
action = <PyQt5.QtWidgets.QWidgetAction object>
global BBSSettings = <class ‘bulibrushswitch.bbs.bbssettings.BBSSettings’>
BBSSettings.brushAction =
brushNfo = {‘blendingMode’: ‘normal’, ‘color’: ‘#000000’, ‘comments’: ‘’, ‘flow’: 1.0, ‘ignoreEraserMode’: True, ‘keepUserModifications’: True, ‘name’: ‘Aura Oil Basic’, ‘opacity’: 1.0, ‘position’: 0, ‘size’: 60.0}
global BBSBrush = <class ‘bulibrushswitch.bbs.bbswbrushes.BBSBrush’>
BBSBrush.KEY_UUID = ‘uuid’
BBSBrush.KEY_NAME = ‘name’
BBSBrush.KEY_COMMENTS = ‘comments’
window = <PyKrita.krita.Window object>
KeyError: ‘uuid’
cause = None
class = <class ‘KeyError’>
context = None
delattr = <method-wrapper ‘delattr’ of KeyError object>
dict = {}
dir =
doc = ‘Mapping key not found.’
eq = <method-wrapper ‘eq’ of KeyError object>
format =
ge = <method-wrapper ‘ge’ of KeyError object>
getattribute = <method-wrapper ‘getattribute’ of KeyError object>
gt = <method-wrapper ‘gt’ of KeyError object>
hash = <method-wrapper ‘hash’ of KeyError object>
init = <method-wrapper ‘init’ of KeyError object>
init_subclass =
le = <method-wrapper ‘le’ of KeyError object>
lt = <method-wrapper ‘lt’ of KeyError object>
ne = <method-wrapper ‘ne’ of KeyError object>
new =
reduce =
reduce_ex =
repr = <method-wrapper ‘repr’ of KeyError object>
setattr = <method-wrapper ‘setattr’ of KeyError object>
setstate =
sizeof =
str = <method-wrapper ‘str’ of KeyError object>
subclasshook =
suppress_context = False
traceback =
args = (‘uuid’,)
with_traceback =

The above is a description of an error in a Python program. Here is
the original traceback:

Traceback (most recent call last):
File “C:\Users\Gremr\AppData\Roaming\krita\pykrita\bulibrushswitch\bulibrushswitch.py”, line 143, in createActions
action=BBSSettings.brushAction(brushNfo[BBSBrush.KEY_UUID], brushNfo[BBSBrush.KEY_NAME], brushNfo[BBSBrush.KEY_COMMENTS], True, window)
KeyError: ‘uuid’

Hi

Thanks for the information about problem
I didn’t downloaded beta3 yet, I’ll try to take a look as soon as possible

If you test with beta2, do you have the problem ?

Grum999

Hi

Release 0.1.1b is published.

It’s a bug fix release:

  • Add missing .action file (install process now works properly)
  • Fix invalid default brush definition from settings when no configuration files exists (@Gremriel no more error now… coding-coding, and I didn’t test installation on an environment without plugin configuration file yet… :sweat_smile:)
  • On Windows, fix main Brushes list window staying over Brush setting window

Grum999

1 Like

is this gonna be part of the Krita Plugin Buli Suite?

2 Likes

Works on beta3 now, thanks :slight_smile:

*edit: Spoke too soon. The installation error is gone, but as soon as I add a brush ( a) Eraser Soft, in this case) I get an error window that takes a few clicks to go away.

TypeError
Python 3.8.1: C:\Program Files\Krita (x64)\bin\krita.exe
Fri Dec 3 08:31:39 2021

A problem occurred in a Python script. Here is the sequence of
function calls leading up to the error, in the order they occurred.

C:\Users\Gremr\AppData\Roaming\krita\pykrita\bulibrushswitch\bbs\bbswbrushes.py in data(self=<bulibrushswitch.bbs.bbswbrushes.BBSBrushesModel object>, index=<PyQt5.QtCore.QModelIndex object>, role=1)
777 if column==BBSBrushesModel.COLNUM_ICON:
778 # QIcon
779 return QIcon(QPixmap.fromImage(item.image()))
780 elif role == Qt.ToolTipRole:
781 id=self.__items[row]
global QIcon = <class ‘PyQt5.QtGui.QIcon’>
global QPixmap = <class ‘PyQt5.QtGui.QPixmap’>
QPixmap.fromImage =
item = <BBSBrush({13475afb-8e52-4643-b05b-9274d4c47193}, a) Eraser Soft)>
item.image = <bound method BBSBrush.image of <BBSBrush({13475afb-8e52-4643-b05b-9274d4c47193}, a) Eraser Soft)>>
TypeError: fromImage(QImage, flags: Union[Qt.ImageConversionFlags, Qt.ImageConversionFlag] = Qt.AutoColor): argument 1 has unexpected type ‘NoneType’
cause = None
class = <class ‘TypeError’>
context = None
delattr = <method-wrapper ‘delattr’ of TypeError object>
dict = {}
dir =
doc = ‘Inappropriate argument type.’
eq = <method-wrapper ‘eq’ of TypeError object>
format =
ge = <method-wrapper ‘ge’ of TypeError object>
getattribute = <method-wrapper ‘getattribute’ of TypeError object>
gt = <method-wrapper ‘gt’ of TypeError object>
hash = <method-wrapper ‘hash’ of TypeError object>
init = <method-wrapper ‘init’ of TypeError object>
init_subclass =
le = <method-wrapper ‘le’ of TypeError object>
lt = <method-wrapper ‘lt’ of TypeError object>
ne = <method-wrapper ‘ne’ of TypeError object>
new =
reduce =
reduce_ex =
repr = <method-wrapper ‘repr’ of TypeError object>
setattr = <method-wrapper ‘setattr’ of TypeError object>
setstate =
sizeof =
str = <method-wrapper ‘str’ of TypeError object>
subclasshook =
suppress_context = False
traceback =
args = (“fromImage(QImage, flags: Union[Qt.ImageConversio…Color): argument 1 has unexpected type ‘NoneType’”,)
with_traceback =

The above is a description of an error in a Python program. Here is
the original traceback:

Traceback (most recent call last):
File “C:\Users\Gremr\AppData\Roaming\krita\pykrita\bulibrushswitch\bbs\bbswbrushes.py”, line 779, in data
return QIcon(QPixmap.fromImage(item.image()))
TypeError: fromImage(QImage, flags: Union[Qt.ImageConversionFlags, Qt.ImageConversionFlag] = Qt.AutoColor): argument 1 has unexpected type ‘NoneType’

After that one goes away, I get a second error:

AttributeError
Python 3.8.1: C:\Program Files\Krita (x64)\bin\krita.exe
Fri Dec 3 08:34:35 2021

A problem occurred in a Python script. Here is the sequence of
function calls leading up to the error, in the order they occurred.

C:\Users\Gremr\AppData\Roaming\krita\pykrita\bulibrushswitch\bbs\bbswbrushes.py in paint(self=<bulibrushswitch.bbs.bbswbrushes.BBSBrushesModelDelegate object>, painter=<PyQt5.QtGui.QPainter object>, option=<PyQt5.QtWidgets.QStyleOptionViewItem object>, index=<PyQt5.QtCore.QModelIndex object>)
1130 painter.fillRect(option.rect, option.palette.color(QPalette.Highlight))
1131
1132 painter.drawPixmap(option.rect.topLeft(), index.data(Qt.DecorationRole).pixmap(option.decorationSize))
1133 painter.restore()
1134 return
painter = <PyQt5.QtGui.QPainter object>
painter.drawPixmap =
option = <PyQt5.QtWidgets.QStyleOptionViewItem object>
option.rect = PyQt5.QtCore.QRect(0, 128, 128, 128)
option.rect.topLeft =
index = <PyQt5.QtCore.QModelIndex object>
index.data =
global Qt = <class ‘PyQt5.QtCore.Qt’>
Qt.DecorationRole = 1
).pixmap undefined
option.decorationSize = PyQt5.QtCore.QSize(128, 128)
AttributeError: ‘NoneType’ object has no attribute ‘pixmap’
cause = None
class = <class ‘AttributeError’>
context = None
delattr = <method-wrapper ‘delattr’ of AttributeError object>
dict = {}
dir =
doc = ‘Attribute not found.’
eq = <method-wrapper ‘eq’ of AttributeError object>
format =
ge = <method-wrapper ‘ge’ of AttributeError object>
getattribute = <method-wrapper ‘getattribute’ of AttributeError object>
gt = <method-wrapper ‘gt’ of AttributeError object>
hash = <method-wrapper ‘hash’ of AttributeError object>
init = <method-wrapper ‘init’ of AttributeError object>
init_subclass =
le = <method-wrapper ‘le’ of AttributeError object>
lt = <method-wrapper ‘lt’ of AttributeError object>
ne = <method-wrapper ‘ne’ of AttributeError object>
new =
reduce =
reduce_ex =
repr = <method-wrapper ‘repr’ of AttributeError object>
setattr = <method-wrapper ‘setattr’ of AttributeError object>
setstate =
sizeof =
str = <method-wrapper ‘str’ of AttributeError object>
subclasshook =
suppress_context = False
traceback =
args = ("‘NoneType’ object has no attribute ‘pixmap’",)
with_traceback =

The above is a description of an error in a Python program. Here is
the original traceback:

Traceback (most recent call last):
File “C:\Users\Gremr\AppData\Roaming\krita\pykrita\bulibrushswitch\bbs\bbswbrushes.py”, line 1132, in paint
painter.drawPixmap(option.rect.topLeft(), index.data(Qt.DecorationRole).pixmap(option.decorationSize))
AttributeError: ‘NoneType’ object has no attribute ‘pixmap’

@Gremriel
The sections that are error messages and such it would be nice if you could mark them as “pre formatted text” so it is not walls of text.

Done.

This is the first time I have seen that shortcut keys can be set elsewhere. Since it will not overwrite but prompts that there are shortcut keys. I think the canvas shortcut keys can be taken into consideration

1 Like

I’m not able to reproduce the problem (testing Windows and Linux)

The strange things is that the origin of error indicate that brush has no image.

If I understand:

  1. You can popup the brushes list

  2. The default a) Eraser circle brush is available in the list (you can see icon, you can select it)

  3. You can click on Settings button, the Settings window is opened

  4. In the Settings window, the default a) Eraser circle brush is available in the list (you can see icon, you can select it)

  5. You can add a new brush (available brush list is visible, you can select it, the dedicated Brush settings is opened)

  6. Once you validate the new brush, you have the errors message?

Grum999

Don’t know, I just have no imagination to find “professional” names to my plugins, and for most of them I put Buli-Thing in name :sweat_smile:

Grum999

2 Likes

Hi,

I could do all the steps the first time I clicked the new button in the toolbar. After that, I only get these error messages.

After I click them away, the window still opens, but clicking the settings button doesn’t do anything.

It was not simple.
Adding a shortcut is not complicated.
Synchronize this shortcut with Krita’s action/shortcut list was not simple.

As I wrote:

– Please also be aware that current version of plugin tweak a little bit the shortcut system as Krita is not aimed to manage dynamic add/remove/update shortcuts information in Settings dialog box.
It works, but I didn’t made strong tests

It works, but I’m pretty sure there might be some case for which you can have problems.

In few words, the tweak is:

  1. Create Action with Krita’s API (this is OK)
  2. Define a shortcut for Action (it works and Krita take it in account)
  3. Update dynamically action file for plugin to set shortcut as default shortcut
  4. On next Krita’s startup, action file is taken in account

The thing is, if you start between steps 2 and 3 to modify manually shortcuts from Krita’s settings, it could have some problem (currently it works, but I don’t want to guarantee that because even if I made test, I didn’t tested all cases)

I’m not really sure.

I didn’t took a look in code, but:

  • Actions use QAction and QKeySequence for shortcuts (using default KDE user interface)
    – An action have a name and can be triggered from anywhere, without knowledge of internal executed method
  • Canvas Input I think it’s a dedicated Krita’s system, probably based on events (mousePressEvent, mouseReleaseEvent, keyPressEvent, keyReleaseEvent)
    – I’m not sure canvas input are named and can be accessed from API

Grum999

1 Like

I’m sorry but it’s not clear enough for me to understand at which point the problem occurs.

If you can take times to write steps by step what you do (like I did), possibly with screenshot, it could help me to understand what happen, or how it happen.

My problem is that I currently not able to reproduce it and then, that’s very hard to understand what happens for you.

Can’t tell if you’re the only one.
If someone else can gave me a feedback (works/do not works) that could also help :slight_smile:

Grum999

Hi,

Ok, as I said in my original post, I installed it per instructions. When Krita launches, the extra button is there. I just did a fresh install of the plugin with the latest version, just in case.

I click it, the window comes up, and I click the Add Brush button. The list of available brushes shows up, and I pick an eraser. I click OK, and then I get all these error messages.

Clicking the close button doesn’t seem to do anything, so I click the X button. More errors show up, I clcik them away, and Krita closes.

I don’t have a clue what the data in the error messages mean, I just report them as I see them.
If you can give me a step by step method to troubleshoot this, I’m happy to test it all out.

One error. It should be because the canvas is not opened

AttributeError
Python 3.8.1: C:\GAMES\STEAM\steamapps\common\Krita\krita\bin\krita.exe
Fri Dec  3 22:02:22 2021

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 C:\Users\123\AppData\Roaming\krita\pykrita\bulibrushswitch\bbs\bbswbrushswitcher.py in __brushesSelectionChanged(self=<bulibrushswitch.bbs.bbswbrushswitcher.BBSWBrushSwitcherUi object>, selected=<PyQt5.QtCore.QItemSelection object>, deselected=<PyQt5.QtCore.QItemSelection object>)
  498         if len(selectedBrushes)==1:
  499             if selectedBrushes[0].found():
  500                 self.__brushSwitcher.setBrushActivated(selectedBrushes[0])
  501                 self.hide()
  502 
self = <bulibrushswitch.bbs.bbswbrushswitcher.BBSWBrushSwitcherUi object>
self.__brushSwitcher undefined
selectedBrushes = [<BBSBrush({bbb4e7a6-67c5-4622-ab47-5da1c281018d}, 1)Basic old classic)>]

 C:\Users\123\AppData\Roaming\krita\pykrita\bulibrushswitch\bbs\bbswbrushswitcher.py in setBrushActivated(self=<bulibrushswitch.bbs.bbswbrushswitcher.BBSWBrushSwitcher object>, value=<BBSBrush({bbb4e7a6-67c5-4622-ab47-5da1c281018d}, 1)Basic old classic)>, restoreKritaBrush=True)
  385                 self.__kritaBrush=BBSBrush()
  386                 self.__kritaBrush.setIgnoreEraserMode(False)
  387                 self.__kritaBrush.fromCurrentKritaBrush(saveColor=True)
  388             else:
  389                 # already using brush activated from plugin
self = <bulibrushswitch.bbs.bbswbrushswitcher.BBSWBrushSwitcher object>
self.__kritaBrush undefined
saveColor undefined

 C:\Users\123\AppData\Roaming\krita\pykrita\bulibrushswitch\bbs\bbswbrushes.py in fromCurrentKritaBrush(self=<BBSBrush({f5d40575-b6ea-44a2-a08d-68c4cc8a103a}, )>, view=<PyKrita.krita.View object>, saveColor=True)
  159         brush=view.currentBrushPreset()
  160 
  161         self.__name=brush.name()
  162         self.__size=view.brushSize()
  163         self.__flow=view.paintingFlow()
self = <BBSBrush({f5d40575-b6ea-44a2-a08d-68c4cc8a103a}, )>
self.__name undefined
brush = None
brush.name undefined
AttributeError: 'NoneType' object has no attribute 'name'
    __cause__ = None
    __class__ = <class 'AttributeError'>
    __context__ = None
    __delattr__ = <method-wrapper '__delattr__' of AttributeError object>
    __dict__ = {}
    __dir__ = <built-in method __dir__ of AttributeError object>
    __doc__ = 'Attribute not found.'
    __eq__ = <method-wrapper '__eq__' of AttributeError object>
    __format__ = <built-in method __format__ of AttributeError object>
    __ge__ = <method-wrapper '__ge__' of AttributeError object>
    __getattribute__ = <method-wrapper '__getattribute__' of AttributeError object>
    __gt__ = <method-wrapper '__gt__' of AttributeError object>
    __hash__ = <method-wrapper '__hash__' of AttributeError object>
    __init__ = <method-wrapper '__init__' of AttributeError object>
    __init_subclass__ = <built-in method __init_subclass__ of type object>
    __le__ = <method-wrapper '__le__' of AttributeError object>
    __lt__ = <method-wrapper '__lt__' of AttributeError object>
    __ne__ = <method-wrapper '__ne__' of AttributeError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of AttributeError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of AttributeError object>
    __repr__ = <method-wrapper '__repr__' of AttributeError object>
    __setattr__ = <method-wrapper '__setattr__' of AttributeError object>
    __setstate__ = <built-in method __setstate__ of AttributeError object>
    __sizeof__ = <built-in method __sizeof__ of AttributeError object>
    __str__ = <method-wrapper '__str__' of AttributeError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __suppress_context__ = False
    __traceback__ = <traceback object>
    args = ("'NoneType' object has no attribute 'name'",)
    with_traceback = <built-in method with_traceback of AttributeError object>

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "C:\Users\123\AppData\Roaming\krita\pykrita\bulibrushswitch\bbs\bbswbrushswitcher.py", line 500, in __brushesSelectionChanged
    self.__brushSwitcher.setBrushActivated(selectedBrushes[0])
  File "C:\Users\123\AppData\Roaming\krita\pykrita\bulibrushswitch\bbs\bbswbrushswitcher.py", line 387, in setBrushActivated
    self.__kritaBrush.fromCurrentKritaBrush(saveColor=True)
  File "C:\Users\123\AppData\Roaming\krita\pykrita\bulibrushswitch\bbs\bbswbrushes.py", line 161, in fromCurrentKritaBrush
    self.__name=brush.name()
AttributeError: 'NoneType' object has no attribute 'name'
1 Like

Yes, that’s the reason of this error.
I can reproduce it, will be fixed for next release.

Thanks.

Grum999

By any chance, are you able to reproduce same error than @Gremriel ?

Grum999

another one. It seems to appear when it is closed. But i can’t reproduce

TypeError
Python 3.8.1: C:\GAMES\STEAM\steamapps\common\Krita\krita\bin\krita.exe
Fri Dec  3 22:14:49 2021

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 C:\Users\123\AppData\Roaming\krita\pykrita\bulibrushswitch\bulibrushswitch.py in __kritaIsClosing(self=<bulibrushswitch.bulibrushswitch.BuliBrushSwitch object>)
  119         """Save configuration before closing"""
  120         if BBSSettings.modified():
  121             BBSSettings.saveConfig()
  122 
  123     def setup(self):
global BBSSettings = <class 'bulibrushswitch.bbs.bbssettings.BBSSettings'>
BBSSettings.saveConfig = <function Settings.saveConfig>
TypeError: saveConfig() missing 1 required positional argument: 'self'
    __cause__ = None
    __class__ = <class 'TypeError'>
    __context__ = None
    __delattr__ = <method-wrapper '__delattr__' of TypeError object>
    __dict__ = {}
    __dir__ = <built-in method __dir__ of TypeError object>
    __doc__ = 'Inappropriate argument type.'
    __eq__ = <method-wrapper '__eq__' of TypeError object>
    __format__ = <built-in method __format__ of TypeError object>
    __ge__ = <method-wrapper '__ge__' of TypeError object>
    __getattribute__ = <method-wrapper '__getattribute__' of TypeError object>
    __gt__ = <method-wrapper '__gt__' of TypeError object>
    __hash__ = <method-wrapper '__hash__' of TypeError object>
    __init__ = <method-wrapper '__init__' of TypeError object>
    __init_subclass__ = <built-in method __init_subclass__ of type object>
    __le__ = <method-wrapper '__le__' of TypeError object>
    __lt__ = <method-wrapper '__lt__' of TypeError object>
    __ne__ = <method-wrapper '__ne__' of TypeError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of TypeError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of TypeError object>
    __repr__ = <method-wrapper '__repr__' of TypeError object>
    __setattr__ = <method-wrapper '__setattr__' of TypeError object>
    __setstate__ = <built-in method __setstate__ of TypeError object>
    __sizeof__ = <built-in method __sizeof__ of TypeError object>
    __str__ = <method-wrapper '__str__' of TypeError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __suppress_context__ = False
    __traceback__ = <traceback object>
    args = ("saveConfig() missing 1 required positional argument: 'self'",)
    with_traceback = <built-in method with_traceback of TypeError object>

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "C:\Users\123\AppData\Roaming\krita\pykrita\bulibrushswitch\bulibrushswitch.py", line 121, in __kritaIsClosing
    BBSSettings.saveConfig()
TypeError: saveConfig() missing 1 required positional argument: 'self'

Yes
I see why it happen: when Krita is closed, if some modified plugin settings hasn’t been saved, they’re saved.

But I not able to reproduce the of “Krita is closing” , the notifier don’t send event as expected, I’m trying to understand why.

But origin of error message is fixed :slight_smile:

Grum999