Reducing boilerplate code in Python plugins

Proof-of-concept:

A plugin that applies a GEGL Gaussian blur vertically or horizontally after showing this:

image

The whole plugin code (everything else is in a gimphelpers Python module:

#!/usr/bin/env python3

# Demo for gimphelpers

# Demo of the choice arg with values

import gi
gi.require_version('Gimp', '3.0')
from gi.repository import Gimp
gi.require_version('Gegl', '0.4')
from gi.repository import Gegl
from gi.repository import GLib
import sys
from gimphelpers import *

def _(message): return GLib.dgettext(None, message)

# Direction choice. This is both the object used to generate the dialog
# and the one from which values are retrieved. Here we use functions
# just to show how powerful this can be

directions=Choice('direction','Direction','Choice of direction','H',
    [
       Option('H','Horizontal',help='Up and down',value=lambda v: (v,0.)),
       Option('V','Vertical', value=lambda v: (0.,v)),
    ])

#
# The actual plugin code, doest a vertical or horizontal blur, given a size and a dierction
#
def executeChoice(procedure, run_mode, image, drawables, config, data):
    trace(f'entering executeChoice({procedure=!r}, {run_mode=!r}, {image=!r}, {drawables=!r}, {config=!r}, {data=!r})')
    direction=config.get_property('direction')
    size=config.get_property('size')
    sizeX,sizeY=directions[direction](size)
    Gegl.init()
    layer=drawables[0]
    selected,x,y,w,h=layer.mask_intersect()
    if selected:
        applyGeglOnBuffers(layer.get_buffer(),layer.get_shadow_buffer(),'gegl:gaussian-blur',std_dev_x=sizeX,std_dev_y=sizeY)
        layer.merge_shadow(True)
        layer.update(x,y,w,h)
    return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())

#
# The "registration" code
#
class ChoiceDemo(HelpedPlugin):
    def __init__(self):
        super().__init__(
               [
                   ProcedureDescription(
                        'choice-demo',
                        'RGB*, GRAY*',Gimp.ProcedureSensitivityMask.DRAWABLE,
                        executeChoice,
                        menuLabel=_('Choice demo'), # I18N is can be used
                        menuPath='<Image>/'+_('Test'),
                        args= [
                                Double('size',_('Blur size'),'',0.,1500.,5.), # using order
                                directions,
                        ],
                    )
               ])

Gimp.main(ChoiceDemo.__gtype__, sys.argv)

For the curious, the code (pretty much a WIP right now) is here.

Interesting! As a note, ScriptFu scripts can't call GEGL filters since PDB compatibility procedures were obsoleted in rc1 (#12279) · Issues · GNOME / GIMP · GitLab has been marked for 3.0RC2, so we should have a built-in method to easily apply GEGL filters for plug-ins and scripts soon.

First shot at my GimpHelpers module. Registering a script is now down to:

class FullDialog(HelpedPlugin):
    procedures=[
            ProcedureDescription(
                'full-dialog',
                'RGB*, GRAY*', Gimp.ProcedureSensitivityMask.DRAWABLE,
                fullDialog,
                args=[
                    Boolean(    'boolean',  _('Boolean'),   _('Boolean arg'),   True),
                    Brush(      'brush',    _('Brush'),     _('Brush arg'),     _('Confetti')),
                    # This one defined above because it can contain other useful data
                    choiceDemo,
                    Channel(    'channel',  _('Channel'),   _('Channel arg')),
                    Color(      'color',    _('Color'),     _('Color arg'),     Gegl.Color.new('red'), hasAlpha=True),
                    Color(      'color2',    _('Color2'),     _('Color arg2'),     Gegl.Color.new('blue'), hasAlpha=False),
                    Double(     'double',   _('Double'),    _('Double arg'),    0, 100, 50),
                    Drawable(   'drawable', _('Drawable'),  _('Drawable arg')),
                    File(       'file',     _('File'),      _('File arg')),
                    Font(       'font',     _('Font'),      _('Font arg'),      _('Monospace Bold Italic'),noneOK=False),
                    Gradient(   'gradient', _('Gradient'),  _('Gradient arg'),  _('Sunrise')),
                    # 'image-' because 'image' is already used by the standard argument
                    Image(      'image-',   _('Image'),     _('Image arg'),     noneOK=False),
                    Int(        'int',      _('Integer'),   _('Integer arg'),   -10, 10, 0),
                    Layer(      'layer',    _('Layer'),     _('Layer arg'),     noneOK=False),
                    Palette(    'palette',  _('Palette'),   _('Palette arg'),   _('Ega')),
                    Pattern(    'pattern',  _('Pattern'),   _('Pattern arg'),   _('Nops')),
                    String(     'string',   _('String'),    _('String arg'),    _('Déjà vu')),
                ],
                menuLabel=_('Full demo'),
                menuPath=['<Image>/'+_('Test'), '<Layers>'],  # Added to two menus
                descShort=_('Show all widgets for Python dialogs'),
                descLong=_('Show all widgets for Python dialogs, to demonstrate the gimphelpers module'),
                beforeDialog=beforeFullDialog,
                afterDialog=afterFullDialog,
    )]
    domain=getDomain(__file__)
    icon=getIcon(__file__)
    author='Ofnuts'
    year='2024'

Gimp.main(FullDialog.__gtype__, sys.argv)

and for that price your code is wrapped into a some more code that shows the dialog (with optional peek/tweak at args before/after dialog, starts/end an undo group, and runs the code within a try/except block so that the plugin exits nicely in most cases.

The module is here.

This topic was automatically closed 45 days after the last reply. New replies are no longer allowed.