[gimp] ScriptFu: script-fu-register-filter for GimpImageProcedure.



commit 12c0c180366eefcf767dcd4b18695a13b219dcbf
Author: lloyd konneker <konnekerl gmail com>
Date:   Fri Jul 15 13:50:36 2022 -0400

    ScriptFu:  script-fu-register-filter for GimpImageProcedure.
    
    Resolves #8382
    
    Also v2 scripts infer and set sensitivity to drawables
    
    Add two test plugins clothify-v3.scm and test-sphere-v3.scm.
    Temporary, to be removed when 3.0 ships.
    
    Some refactoring (extracting methods, moving functions to new files).
    
    Some drive-by fixes to script-fu-arg.c revealed by using GimpProcedureDialog.

 plug-ins/script-fu/libscriptfu/Makefile.am         |  10 +-
 plug-ins/script-fu/libscriptfu/meson.build         |   6 +-
 plug-ins/script-fu/libscriptfu/scheme-wrapper.c    | 135 +++--
 plug-ins/script-fu/libscriptfu/scheme-wrapper.h    |   1 +
 plug-ins/script-fu/libscriptfu/script-fu-arg.c     |  47 +-
 plug-ins/script-fu/libscriptfu/script-fu-command.c | 150 +++++
 plug-ins/script-fu/libscriptfu/script-fu-command.h |  31 ++
 plug-ins/script-fu/libscriptfu/script-fu-dialog.c  | 179 ++++++
 plug-ins/script-fu/libscriptfu/script-fu-dialog.h  |  31 ++
 plug-ins/script-fu/libscriptfu/script-fu-enums.h   |  38 +-
 .../script-fu/libscriptfu/script-fu-interface.c    |   1 -
 .../script-fu/libscriptfu/script-fu-proc-factory.c |   1 -
 .../script-fu/libscriptfu/script-fu-register.c     | 462 ++++++++++++++++
 .../script-fu/libscriptfu/script-fu-register.h     |  30 +
 .../script-fu/libscriptfu/script-fu-run-func.c     | 217 ++++++++
 .../script-fu/libscriptfu/script-fu-run-func.h     |  33 ++
 plug-ins/script-fu/libscriptfu/script-fu-script.c  | 294 ++++++++--
 plug-ins/script-fu/libscriptfu/script-fu-script.h  |  12 +-
 plug-ins/script-fu/libscriptfu/script-fu-scripts.c | 610 ++++-----------------
 plug-ins/script-fu/libscriptfu/script-fu-scripts.h |  11 +-
 plug-ins/script-fu/libscriptfu/script-fu-types.h   |   2 +
 plug-ins/script-fu/scripts/Makefile.am             |  14 +-
 plug-ins/script-fu/scripts/clothify-v3.scm         |  84 +++
 plug-ins/script-fu/scripts/meson.build             |   2 +
 plug-ins/script-fu/scripts/plug-in-compat.init     |  13 +
 plug-ins/script-fu/scripts/test-sphere-v3.scm      | 174 ++++++
 .../scripts/test/always-fail/always-fail.scm       |  30 +
 .../test/call-always-fail/call-always-fail.scm     |  29 +
 po-script-fu/POTFILES.skip                         |   1 +
 29 files changed, 2022 insertions(+), 626 deletions(-)
---
diff --git a/plug-ins/script-fu/libscriptfu/Makefile.am b/plug-ins/script-fu/libscriptfu/Makefile.am
index 748f0a7f82..277564708c 100644
--- a/plug-ins/script-fu/libscriptfu/Makefile.am
+++ b/plug-ins/script-fu/libscriptfu/Makefile.am
@@ -123,7 +123,15 @@ libgimp_scriptfu_@GIMP_API_VERSION@_la_SOURCES = \
        script-fu-proc-factory.h \
        script-fu-proc-factory.c \
        script-fu-arg.c     \
-       script-fu-arg.h
+       script-fu-arg.h     \
+       script-fu-command.h  \
+       script-fu-command.c  \
+       script-fu-dialog.h   \
+       script-fu-dialog.c   \
+       script-fu-register.h \
+       script-fu-register.c \
+       script-fu-run-func.h \
+       script-fu-run-func.c
 
 EXTRA_libgimp_scriptfu_@GIMP_API_VERSION@_la_DEPENDENCIES = $(scriptfu_def)
 
diff --git a/plug-ins/script-fu/libscriptfu/meson.build b/plug-ins/script-fu/libscriptfu/meson.build
index 74f5df5b97..7f5708956f 100644
--- a/plug-ins/script-fu/libscriptfu/meson.build
+++ b/plug-ins/script-fu/libscriptfu/meson.build
@@ -15,7 +15,11 @@ libscriptfu_sources = [
   'script-fu-compat.c',
   'script-fu-lib.c',
   'script-fu-proc-factory.c',
-  'script-fu-arg.c'
+  'script-fu-arg.c',
+  'script-fu-register.c',
+  'script-fu-dialog.c',
+  'script-fu-run-func.c',
+  'script-fu-command.c'
 ]
 
 # !! just "library(...)" which means shared versus static depends on configuration of project.
diff --git a/plug-ins/script-fu/libscriptfu/scheme-wrapper.c b/plug-ins/script-fu/libscriptfu/scheme-wrapper.c
index 6c2a7d236b..c3f0256ff0 100644
--- a/plug-ins/script-fu/libscriptfu/scheme-wrapper.c
+++ b/plug-ins/script-fu/libscriptfu/scheme-wrapper.c
@@ -51,6 +51,10 @@
 static void     ts_init_constants                           (scheme    *sc);
 static void     ts_init_enum                                (scheme    *sc,
                                                              GType      enum_type);
+
+static void     ts_define_procedure                         (scheme       *sc,
+                                                             const gchar  *symbol_name,
+                                                             TsWrapperFunc func);
 static void     ts_init_procedures                          (scheme    *sc,
                                                              gboolean   register_scipts);
 static void     convert_string                              (gchar     *str);
@@ -67,6 +71,8 @@ static pointer  script_fu_marshal_procedure_call_deprecated (scheme    *sc,
 
 static pointer  script_fu_register_call                     (scheme    *sc,
                                                              pointer    a);
+static pointer  script_fu_register_call_filter              (scheme    *sc,
+                                                             pointer    a);
 static pointer  script_fu_menu_register_call                (scheme    *sc,
                                                              pointer    a);
 static pointer  script_fu_quit_call                         (scheme    *sc,
@@ -83,6 +89,7 @@ typedef struct
   gint         value;
 } NamedConstant;
 
+/* LHS is text in a script, RHS is constant defined in C. */
 static const NamedConstant script_constants[] =
 {
   /* Useful values from libgimpbase/gimplimits.h */
@@ -103,6 +110,8 @@ static const NamedConstant script_constants[] =
   { "UNIT-PICA",      GIMP_UNIT_PICA  },
 
   /* Script-Fu types */
+
+  /* Arg types. */
   { "SF-IMAGE",       SF_IMAGE      },
   { "SF-DRAWABLE",    SF_DRAWABLE   },
   { "SF-LAYER",       SF_LAYER      },
@@ -125,6 +134,36 @@ static const NamedConstant script_constants[] =
   { "SF-ENUM",        SF_ENUM       },
   { "SF-DISPLAY",     SF_DISPLAY    },
 
+  /* PDB procedure drawable_arity, i.e. sensitivity.
+   * Used with script-fu-register-filter.
+   *
+   * This declares the arity of the algorithm,
+   * and not the signature of the PDB procedure.
+   * Since v3, PDB procedures that are image procedures,
+   * take a container of drawables.
+   * This only describes how many drawables the container *should* hold.
+   *
+   * For calls invoked by a user, this describes
+   * how many drawables the user is expected to select,
+   * which disables/enables the menu item for the procedure.
+   *
+   * Procedures are also called from other procedures.
+   * A call from another procedure may in fact
+   * pass more drawables than declared for drawable_arity.
+   * That is a programming error on behalf of the caller.
+   * A well-written callee that is passed more drawables than declared
+   * should return an error instead of processing any of the drawables.
+   *
+   * Similarly for fewer than declared.
+   */
+
+  /* Requires two drawables. Often an operation between them, yielding a new drawable */
+  { "SF-TWO-OR-MORE-DRAWABLE",   SF_TWO_OR_MORE_DRAWABLE  },
+  /* Often processed independently, sequentially, with side effects on the drawables. */
+  { "SF-ONE-OR-MORE-DRAWABLE",   SF_ONE_OR_MORE_DRAWABLE  },
+  /* Requires exactly one drawable. */
+  { "SF-ONE-DRAWABLE",     SF_ONE_DRAWABLE    },
+
   /* For SF-ADJUSTMENT */
   { "SF-SLIDER",      SF_SLIDER     },
   { "SF-SPINNER",     SF_SPINNER    },
@@ -242,6 +281,8 @@ ts_interpret_stdin (void)
 gint
 ts_interpret_string (const gchar *expr)
 {
+  gint result;
+
 #if DEBUG_SCRIPTS
   sc.print_output = 1;
   sc.tracing = 1;
@@ -249,7 +290,10 @@ ts_interpret_string (const gchar *expr)
 
   sc.vptr->load_string (&sc, (char *) expr);
 
-  return sc.retcode;
+  result = sc.retcode;
+
+  g_debug ("ts_interpret_string returns: %i", result);
+  return result;
 }
 
 const gchar *
@@ -421,6 +465,27 @@ ts_init_enum (scheme *sc,
   g_type_class_unref (enum_class);
 }
 
+/* Define a symbol into interpreter state,
+ * bound to a foreign function, language C, defined here in ScriptFu source.
+ */
+static void
+ts_define_procedure (scheme       *sc,
+                     const gchar  *symbol_name,
+                     TsWrapperFunc func)
+{
+  pointer   symbol;
+
+  symbol = sc->vptr->mk_symbol (sc, symbol_name);
+  sc->vptr->scheme_define (sc, sc->global_env, symbol,
+                           sc->vptr->mk_foreign_func (sc, func));
+  sc->vptr->setimmutable (symbol);
+}
+
+
+/* Define, into interpreter state,
+ * 1) Scheme functions that call wrapper functions in C here in ScriptFu.
+ * 2) Scheme functions wrapping every procedure in the PDB.
+ */
 static void
 ts_init_procedures (scheme   *sc,
                     gboolean  register_scripts)
@@ -428,55 +493,30 @@ ts_init_procedures (scheme   *sc,
   gchar   **proc_list;
   gint      num_procs;
   gint      i;
-  pointer   symbol;
 
 #if USE_DL
-  symbol = sc->vptr->mk_symbol (sc,"load-extension");
-  sc->vptr->scheme_define (sc, sc->global_env, symbol,
-                           sc->vptr->mk_foreign_func (sc, scm_load_ext));
-  sc->vptr->setimmutable (symbol);
+/* scm_load_ext not same as other wrappers, defined in tinyscheme/dynload */
+ts_define_procedure (sc, "load-extension", scm_load_ext);
 #endif
 
-  symbol = sc->vptr->mk_symbol (sc, "script-fu-register");
-  sc->vptr->scheme_define (sc, sc->global_env, symbol,
-                           sc->vptr->mk_foreign_func (sc,
-                                                      register_scripts ?
-                                                      script_fu_register_call :
-                                                      script_fu_nil_call));
-  sc->vptr->setimmutable (symbol);
-
-  symbol = sc->vptr->mk_symbol (sc, "script-fu-menu-register");
-  sc->vptr->scheme_define (sc, sc->global_env, symbol,
-                           sc->vptr->mk_foreign_func (sc,
-                                                      register_scripts ?
-                                                      script_fu_menu_register_call :
-                                                      script_fu_nil_call));
-  sc->vptr->setimmutable (symbol);
-
-  symbol = sc->vptr->mk_symbol (sc, "script-fu-quit");
-  sc->vptr->scheme_define (sc, sc->global_env, symbol,
-                           sc->vptr->mk_foreign_func (sc, script_fu_quit_call));
-  sc->vptr->setimmutable (symbol);
-
-  /*  register normal database execution procedure  */
-  symbol = sc->vptr->mk_symbol (sc, "gimp-proc-db-call");
-  sc->vptr->scheme_define (sc, sc->global_env, symbol,
-                           sc->vptr->mk_foreign_func (sc,
-                                                      script_fu_marshal_procedure_call_strict));
-  sc->vptr->setimmutable (symbol);
+  if (register_scripts)
+    {
+      ts_define_procedure (sc, "script-fu-register",        script_fu_register_call);
+      ts_define_procedure (sc, "script-fu-register-filter", script_fu_register_call_filter);
+      ts_define_procedure (sc, "script-fu-menu-register",   script_fu_menu_register_call);
+    }
+  else
+    {
+      ts_define_procedure (sc, "script-fu-register",        script_fu_nil_call);
+      ts_define_procedure (sc, "script-fu-register-filter", script_fu_nil_call);
+      ts_define_procedure (sc, "script-fu-menu-register",   script_fu_nil_call);
+    }
 
-  /*  register permissive and deprecated db execution procedure; see comment below  */
-  symbol = sc->vptr->mk_symbol (sc, "-gimp-proc-db-call");
-  sc->vptr->scheme_define (sc, sc->global_env, symbol,
-                           sc->vptr->mk_foreign_func (sc,
-                                                      script_fu_marshal_procedure_call_permissive));
-  sc->vptr->setimmutable (symbol);
+  ts_define_procedure (sc, "script-fu-quit",      script_fu_quit_call);
 
-  symbol = sc->vptr->mk_symbol (sc, "--gimp-proc-db-call");
-  sc->vptr->scheme_define (sc, sc->global_env, symbol,
-                           sc->vptr->mk_foreign_func (sc,
-                                                      script_fu_marshal_procedure_call_deprecated));
-  sc->vptr->setimmutable (symbol);
+  ts_define_procedure (sc, "gimp-proc-db-call",   script_fu_marshal_procedure_call_strict);
+  ts_define_procedure (sc, "-gimp-proc-db-call",  script_fu_marshal_procedure_call_permissive);
+  ts_define_procedure (sc, "--gimp-proc-db-call", script_fu_marshal_procedure_call_deprecated);
 
   proc_list = gimp_pdb_query_procedures (gimp_get_pdb (),
                                          ".*", ".*", ".*", ".*",
@@ -1598,6 +1638,13 @@ script_fu_register_call (scheme  *sc,
   return script_fu_add_script (sc, a);
 }
 
+static pointer
+script_fu_register_call_filter (scheme  *sc,
+                                pointer  a)
+{
+  return script_fu_add_script_filter (sc, a);
+}
+
 static pointer
 script_fu_menu_register_call (scheme  *sc,
                               pointer  a)
diff --git a/plug-ins/script-fu/libscriptfu/scheme-wrapper.h b/plug-ins/script-fu/libscriptfu/scheme-wrapper.h
index 46bb9d28f7..53645bd2fd 100644
--- a/plug-ins/script-fu/libscriptfu/scheme-wrapper.h
+++ b/plug-ins/script-fu/libscriptfu/scheme-wrapper.h
@@ -21,6 +21,7 @@
 #include "tinyscheme/scheme.h"
 
 typedef void (*TsCallbackFunc) (void);
+typedef pointer (*TsWrapperFunc) (scheme*, pointer);
 
 
 void          tinyscheme_init         (GList        *path,
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-arg.c b/plug-ins/script-fu/libscriptfu/script-fu-arg.c
index d24c9e586e..9d11d2bf20 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-arg.c
+++ b/plug-ins/script-fu/libscriptfu/script-fu-arg.c
@@ -450,6 +450,7 @@ script_fu_arg_append_repr_from_gvalue (SFArg       *arg,
                                        GString     *result_string,
                                        GValue      *gvalue)
 {
+  g_debug("script_fu_arg_append_repr_from_gvalue %s", arg->label);
   switch (arg->type)
     {
     case SF_IMAGE:
@@ -504,16 +505,24 @@ script_fu_arg_append_repr_from_gvalue (SFArg       *arg,
     case SF_FILENAME:
     case SF_DIRNAME:
       {
-        gchar * filepath = NULL;
+        gchar * filepath = "error in file arg";
 
-        if (g_value_get_gtype (gvalue) == G_TYPE_FILE)
+        /* sanity: GValue initialized. */
+        if (G_VALUE_HOLDS_GTYPE(gvalue))
           {
-            filepath = g_file_get_path (g_value_get_object (gvalue));
-            /* Not escape special chars for whitespace or double quote. */
+            if (g_value_get_gtype (gvalue) == G_TYPE_FILE)
+              {
+                filepath = g_file_get_path (g_value_get_object (gvalue));
+                /* Not escape special chars for whitespace or double quote. */
+              }
+            else
+              {
+                g_warning ("Expecting GFile in gvalue.");
+              }
           }
         else
           {
-            g_warning ("Expecting GFile in gvalue.");
+            g_warning ("Expecting GValue holding a GType");
           }
 
         g_string_append_printf (result_string, "\"%s\"", filepath);
@@ -539,7 +548,19 @@ script_fu_arg_append_repr_from_gvalue (SFArg       *arg,
 
     case SF_OPTION:
     case SF_ENUM:
-      g_string_append_printf (result_string, "%d", g_value_get_int (gvalue));
+      /* When sanity test fails, return an arbitrary int.
+       * Fails when GimpConfig or GimpProcedureDialog does not support GParamEnum.
+       */
+      if (G_VALUE_HOLDS_INT (gvalue))
+        {
+          g_string_append_printf (result_string, "%d", g_value_get_int (gvalue));
+        }
+      else
+        {
+          g_warning ("Expecting GValue holding an int.");
+          g_string_append (result_string, "1");   /* Arbitrarily a common int. */
+        }
+
       break;
     }
 }
@@ -684,12 +705,16 @@ script_fu_arg_reset_name_generator (void)
  * It is unique among all names returned between resets of the generator.
  * Thus name meets uniquity for names of properties of one object.
  *
+ * !!! GimpImageProcedures already have properties for convenience arguments,
+ * e.g. a property named "image" "n_drawables" and "drawables"
+ * So we avoid that name clash by starting with "otherImage"
+ *
  * The name means nothing to human readers of the spec.
- * The nick is descriptive.
+ * Instead, the nick is descriptive for human readers.
  *
- * The returned string is owned by the generator
- * and is only stable until the next call to the generator.
- * That is, the caller should copy it (usually by creating a GParamSpec.)
+ * The returned string is owned by the generator, a constant.
+ * The caller need not copy it,
+ * but usually does by creating a GParamSpec.
  */
 void
 script_fu_arg_generate_name_and_nick (SFArg        *arg,
@@ -702,7 +727,7 @@ script_fu_arg_generate_name_and_nick (SFArg        *arg,
   switch (arg->type)
     {
     case SF_IMAGE:
-      name = "image";
+      name = "otherImage";  /* !!! Avoid name clash. */
       break;
 
     case SF_DRAWABLE:
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-command.c 
b/plug-ins/script-fu/libscriptfu/script-fu-command.c
new file mode 100644
index 0000000000..296e25d88a
--- /dev/null
+++ b/plug-ins/script-fu/libscriptfu/script-fu-command.c
@@ -0,0 +1,150 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimpui.h>
+
+#include "script-fu-types.h"    /* SFScript */
+#include "script-fu-lib.h"
+#include "script-fu-script.h"
+
+#include "script-fu-command.h"
+
+
+/* Methods for interpreting commands.
+ *
+ * Usually there is a stack of calls similar to:
+ *       script_fu_run_image_procedure (outer run func)
+ * calls script_fu_interpret_image_proc
+ * calls script_fu_run_command
+ * calls ts_interpret_string
+ * calls the inner run func in Scheme
+ *
+ * but script_fu_run_command is also called directly for loading scripts.
+ *
+ * FUTURE: see also similar code in script-fu-interface.c
+ * which could be migrated here.
+ */
+
+
+/* Interpret a command.
+ *
+ * When errors during interpretation:
+ * 1) set the error message from tinyscheme into GError at given handle.
+ * 2) return FALSE
+ * otherwise, return TRUE and discard any result of interpretation
+ * ScriptFu return values only have a GimpPDBStatus,
+ * since ScriptFu plugin scripts can only be declared returning void.
+ *
+ * While interpreting, any errors from further calls to the PDB
+ * can show error dialogs in any GIMP gui,
+ * unless the caller has taken responsibility with a prior call to
+ * gimp_plug_in_set_pdb_error_handler
+ *
+ * FIXME: see script_fu_run_procedure.
+ * It does not call gimp_plug_in_set_pdb_error_handler for NON-INTERACTIVE mode.
+ */
+gboolean
+script_fu_run_command (const gchar  *command,
+                       GError      **error)
+{
+  GString  *output;
+  gboolean  success = FALSE;
+
+  g_debug ("script_fu_run_command: %s", command);
+  output = g_string_new (NULL);
+  script_fu_redirect_output_to_gstr (output);
+
+  if (script_fu_interpret_string (command))
+    {
+      g_set_error (error, GIMP_PLUG_IN_ERROR, 0, "%s", output->str);
+    }
+  else
+    {
+      success = TRUE;
+    }
+
+  g_string_free (output, TRUE);
+
+  return success;
+}
+
+
+
+/* Interpret a script that defines a GimpImageProcedure.
+ *
+ * Similar to v2 code in script-fu-interface.c, except:
+ * 1) builds a command from a GValueArray from a GimpConfig,
+ *    instead of from local array of SFArg.
+ * 2) adds actual args image, drawable, etc. for GimpImageProcedure
+ */
+GimpValueArray *
+script_fu_interpret_image_proc (
+                            GimpProcedure        *procedure,
+                            SFScript             *script,
+                            GimpImage            *image,
+                            guint                 n_drawables,
+                            GimpDrawable        **drawables,
+                            const GimpValueArray *args)
+{
+  gchar          *command;
+  GimpValueArray *result = NULL;
+  gboolean        interpretation_result;
+  GError         *error = NULL;
+
+  command = script_fu_script_get_command_for_image_proc (script, image, n_drawables, drawables, args);
+
+  /* Take responsibility for handling errors from the scripts further calls to PDB.
+   * ScriptFu does not show an error dialog, but forwards errors back to GIMP.
+   * This only tells GIMP that ScriptFu itself will forward GimpPDBStatus errors from
+   * this scripts calls to the PDB.
+   * The onus is on this script's called PDB procedures to return errors in the GimpPDBStatus.
+   * Any that do not, but for example only call gimp-message, are breaching contract.
+   */
+  gimp_plug_in_set_pdb_error_handler (gimp_get_plug_in (),
+                                      GIMP_PDB_ERROR_HANDLER_PLUGIN);
+
+  interpretation_result = script_fu_run_command (command, &error);
+  g_free (command);
+  if (! interpretation_result)
+    {
+      /* This is to the console.
+       * script->name not localized.
+       * error->message expected to be localized.
+       * GIMP will later display "PDB procedure failed: <message>" localized.
+       */
+      g_warning ("While executing %s: %s",
+                 script->name,
+                 error->message);
+      /* A GError was allocated and this will take ownership. */
+      result = gimp_procedure_new_return_values (procedure,
+                                                 GIMP_PDB_EXECUTION_ERROR,
+                                                 error);
+    }
+  else
+    {
+      result = gimp_procedure_new_return_values (procedure,
+                                                 GIMP_PDB_SUCCESS,
+                                                 NULL);
+    }
+
+  gimp_plug_in_set_pdb_error_handler (gimp_get_plug_in (),
+                                      GIMP_PDB_ERROR_HANDLER_INTERNAL);
+
+  return result;
+}
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-command.h 
b/plug-ins/script-fu/libscriptfu/script-fu-command.h
new file mode 100644
index 0000000000..08a39ba8b6
--- /dev/null
+++ b/plug-ins/script-fu/libscriptfu/script-fu-command.h
@@ -0,0 +1,31 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+ #ifndef __SCRIPT_FU_COMMAND_H__
+ #define __SCRIPT_FU_COMMAND_H__
+
+gboolean        script_fu_run_command          (const gchar  *command,
+                                                GError      **error);
+
+GimpValueArray *script_fu_interpret_image_proc (GimpProcedure        *procedure,
+                                                SFScript             *script,
+                                                GimpImage            *image,
+                                                guint                 n_drawables,
+                                                GimpDrawable        **drawables,
+                                                const GimpValueArray *args);
+
+#endif /* __SCRIPT_FU_COMMAND_H__ */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-dialog.c 
b/plug-ins/script-fu/libscriptfu/script-fu-dialog.c
new file mode 100644
index 0000000000..af11c37eac
--- /dev/null
+++ b/plug-ins/script-fu/libscriptfu/script-fu-dialog.c
@@ -0,0 +1,179 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * script-fu-dialog.c
+ * Copyright (C) 2022 Lloyd Konneker
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libgimp/gimpui.h>
+
+#include "script-fu-types.h"    /* SFScript */
+#include "script-fu-script.h"   /* get_title */
+#include "script-fu-command.h"
+
+#include "script-fu-dialog.h"
+
+
+/* An informal class that shows a dialog for a script then runs the script.
+ * It is internal to libscriptfu.
+ *
+ * The dialog is modal for the script:
+ * OK button hides the dialog then runs the script once.
+ *
+ * The dialog is non-modal with respect to the GIMP app GUI, which remains responsive.
+ *
+ * When called from plugin extension-script-fu, the dialog is modal on the extension:
+ * although GIMP app continues responsive, a user choosing a menu item
+ * that is also implemented by a script and extension-script-fu
+ * will not show a dialog until the first called script finishes.
+ */
+
+/* FUTURE: delete this after v3 is stable. */
+#define DEBUG_CONFIG_PROPERTIES  FALSE
+
+#if DEBUG_CONFIG_PROPERTIES
+static void
+dump_properties (GimpProcedureConfig  *config)
+{
+  GParamSpec **pspecs;
+  guint        n_pspecs;
+
+  pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (config),
+                                           &n_pspecs);
+  for (guint i = 1; i < n_pspecs; i++)
+    g_printerr ("%s %s\n", pspecs[i]->name, G_PARAM_SPEC_TYPE_NAME (pspecs[i]));
+  g_free (pspecs);
+}
+#endif
+
+/* Run a dialog for a procedure, then interpret the script.
+ *
+ * Run dialog: create config, create dialog for config, show dialog, and return a config.
+ * Interpret: marshal config into Scheme text for function call, then interpret script.
+ *
+ * One widget per param of the procedure.
+ * Require the procedure registered with params of GTypes
+ * corresponding to SFType the author declared in script-fu-register call.
+ *
+ * Require initial_args is not NULL or empty.
+ * A caller must ensure a dialog is needed because args is not empty.
+ */
+GimpValueArray*
+script_fu_dialog_run (GimpProcedure        *procedure,
+                      SFScript             *script,
+                      GimpImage            *image,
+                      guint                 n_drawables,
+                      GimpDrawable        **drawables,
+                      const GimpValueArray *initial_args)
+
+{
+  GimpValueArray      *result = NULL;
+  GimpProcedureDialog *dialog = NULL;
+  GimpProcedureConfig *config = NULL;
+  gboolean             not_canceled;
+
+  if ( (! G_IS_OBJECT (procedure)) || script == NULL)
+    return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR, NULL);
+
+  if ( gimp_value_array_length (initial_args) < 1)
+    return gimp_procedure_new_return_values (procedure, GIMP_PDB_EXECUTION_ERROR, NULL);
+
+  /* We don't prevent concurrent dialogs as in script-fu-interface.c.
+   * For extension-script-fu, Gimp is already preventing concurrent dialogs.
+   * For gimp-script-fu-interpreter, each plugin is a separate process
+   * so concurrent dialogs CAN occur.
+   */
+  /* There is no progress widget in GimpProcedureDialog.
+   * Also, we don't need to update the progress in Gimp UI,
+   * because Gimp shows progress: the name of all called PDB procedures.
+   */
+
+  /* Script's menu label */
+  gimp_ui_init (script_fu_script_get_title (script));
+
+  config = gimp_procedure_create_config (procedure);
+#if DEBUG_CONFIG_PROPERTIES
+  dump_properties (config);
+  g_debug ("Len  of initial_args %i", gimp_value_array_length (initial_args) );
+#endif
+
+  /* Get saved settings (last values) into the config.
+   * Since run mode is INTERACTIVE, initial_args is moot.
+   * Instead, last used values or default values populate the config.
+   */
+  gimp_procedure_config_begin_run (config, NULL, GIMP_RUN_INTERACTIVE, initial_args);
+
+  /* Create a dialog having properties (describing arguments of the procedure)
+   * taken from the config.
+   *
+   * Title dialog with the menu item, not the procedure name.
+   * Assert menu item is localized.
+   */
+  dialog = (GimpProcedureDialog*) gimp_procedure_dialog_new (
+                                      procedure,
+                                      config,
+                                      script_fu_script_get_title (script));
+  /* dialog has no widgets except standard buttons. */
+
+  /* It is possible to create custom widget where the provided widget is not adequate.
+   * Then gimp_procedure_dialog_fill_list will create the rest.
+   * For now, the provided widgets should be adequate.
+   */
+
+  /* NULL means create widgets for all properties of the procedure
+   * that we have not already created widgets for.
+   */
+  gimp_procedure_dialog_fill_list (dialog, NULL);
+
+  not_canceled = gimp_procedure_dialog_run (dialog);
+  /* Assert config holds validated arg values from a user interaction. */
+  if (not_canceled)
+    {
+      /* initial_args is declared const.
+       * To avoid compiler warning "discarding const"
+       * copy initial_args to a writeable copy.
+       */
+      GimpValueArray *final_args = (GimpValueArray*) g_value_array_copy ((GValueArray*) initial_args);
+      /* FIXME the above is deprecated.
+       * Non-deprecated, but doesn't work:
+       * GimpValueArray *final_args = (GimpValueArray*) g_array_copy ((GArray*) initial_args);
+       * Maybe we need a gimp_value_array_copy method?
+       */
+
+      /* Store config's values into final_args. */
+      gimp_procedure_config_get_values (config, final_args);
+
+      result = script_fu_interpret_image_proc (procedure, script, image, n_drawables, drawables, final_args);
+    }
+  else
+    {
+      result = gimp_procedure_new_return_values (procedure, GIMP_PDB_CANCEL, NULL);
+    }
+
+  gtk_widget_destroy ((GtkWidget*) dialog);
+
+  /* Persist config aka settings for the next run of the plugin.
+   * Passing the GimpPDBStatus from result[0].
+   * We must have a matching end_run for the begin_run, regardless of status.
+   */
+  gimp_procedure_config_end_run (config, g_value_get_enum (gimp_value_array_index (result, 0)));
+
+  g_object_unref (config);
+
+  return result;
+}
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-dialog.h 
b/plug-ins/script-fu/libscriptfu/script-fu-dialog.h
new file mode 100644
index 0000000000..69f33ba768
--- /dev/null
+++ b/plug-ins/script-fu/libscriptfu/script-fu-dialog.h
@@ -0,0 +1,31 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * script-fu-dialog.h
+ * Copyright (C) 2022 Lloyd Konneker
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+ #ifndef __SCRIPT_FU_DIALOG_H__
+ #define __SCRIPT_FU_DIALOG_H__
+
+GimpValueArray *script_fu_dialog_run (GimpProcedure        *procedure,
+                                      SFScript             *script,
+                                      GimpImage            *image,
+                                      guint                 n_drawables,
+                                      GimpDrawable        **drawables,
+                                      const GimpValueArray *args);
+
+#endif /* __SCRIPT_FU_DIALOG_H__ */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-enums.h 
b/plug-ins/script-fu/libscriptfu/script-fu-enums.h
index 581441fb41..120f7a921f 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-enums.h
+++ b/plug-ins/script-fu/libscriptfu/script-fu-enums.h
@@ -18,8 +18,11 @@
 #ifndef __SCRIPT_FU_ENUMS_H__
 #define __SCRIPT_FU_ENUMS_H__
 
-/*  Typedefs for script-fu argument types  */
+/* Note these are C names with underbar.
+ * The Scheme names are usually the same with hyphen substituted for underbar.
+ */
 
+/*  script-fu argument types  */
 typedef enum
 {
   SF_IMAGE = 0,
@@ -51,4 +54,37 @@ typedef enum
   SF_SPINNER
 } SFAdjustmentType;
 
+/* This enum is local to ScriptFu
+ * but the notion is general to other plugins.
+ *
+ * A GimpImageProcedure has drawable  arity > 1.
+ * A GimpProcedure often does not take any drawables, i.e. arity zero.
+ * Some GimpProcedure may take drawables i.e. arity > 0,
+ * but the procedure's menu item is always sensitive,
+ * and the drawable can be chosen in the plugin's dialog.
+ *
+ * Script author does not use SF-NO-DRAWABLE, for now.
+ *
+ * Scripts of class GimpProcedure are declared by script-fu-register.
+ * Their GUI is handled by ScriptFu, script-fu-interface.c
+ * An author does not declare drawable_arity.
+ *
+ * Scripts of class GimpImageProcedure are declared by script-fu-register-filter.
+ * Their GUI is handled by libgimpui, GimpProcedureDialog.
+ * Their drawable_arity is declared by the author of the script.
+ *
+ * For backward compatibility, GIMP deprecates but allows PDB procedures
+ * to take a single drawable, and sets their sensitivity automatically.
+ * Their drawable_arity is inferred by ScriptFu.
+ * FUTURE insist that an author use script-fu-register-filter (not script-fu-register)
+ * for GimpImageProcedure taking image and one or more drawables.
+ */
+typedef enum
+{
+  SF_NO_DRAWABLE = 0,       /* GimpProcedure. */
+  SF_ONE_DRAWABLE,          /* GimpImageProcedure, but only process one layer */
+  SF_ONE_OR_MORE_DRAWABLE,  /* GimpImageProcedure, multilayer capable */
+  SF_TWO_OR_MORE_DRAWABLE,  /* GimpImageProcedure, requires at least two drawables. */
+} SFDrawableArity;
+
 #endif /*  __SCRIPT_FU_ENUMS__  */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-interface.c 
b/plug-ins/script-fu/libscriptfu/script-fu-interface.c
index 4bfa3a7803..7f3e1e698b 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-interface.c
+++ b/plug-ins/script-fu/libscriptfu/script-fu-interface.c
@@ -28,7 +28,6 @@
 #include <windows.h>
 #endif
 
-#include "tinyscheme/scheme-private.h"
 #include "scheme-wrapper.h"
 
 #include "script-fu-types.h"
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.c 
b/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.c
index 5c74a755ae..614f837fe4 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.c
+++ b/plug-ins/script-fu/libscriptfu/script-fu-proc-factory.c
@@ -88,7 +88,6 @@ script_fu_proc_factory_make_PLUGIN (GimpPlugIn  *plug_in,
       procedure = script_fu_script_create_PDB_procedure (
         plug_in,
         script,
-        script_fu_script_proc,     /* run_func */
         GIMP_PDB_PROC_TYPE_PLUGIN);
       script_fu_add_menu_to_procedure (procedure, script);
     }
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-register.c 
b/plug-ins/script-fu/libscriptfu/script-fu-register.c
new file mode 100644
index 0000000000..42a2b5fc1f
--- /dev/null
+++ b/plug-ins/script-fu/libscriptfu/script-fu-register.c
@@ -0,0 +1,462 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <glib.h>
+
+#ifdef G_OS_WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "tinyscheme/scheme-private.h"
+
+#include "script-fu-types.h"
+#include "script-fu-script.h"
+#include "script-fu-register.h"
+
+/* Methods for a script's call to script-fu-register or script-fu-register-filter.
+ * Such calls declare a PDB procedure, that ScriptFu will register in the PDB,
+ * that the script implements by its inner run func.
+ * These methods are only creating structs local to ScriptFu, used later to register.
+ */
+
+
+
+/* Traverse Scheme argument list
+ * creating a new SFScript with metadata, but empty SFArgs (formal arg specs)
+ *
+ * Takes a handle to a pointer into the argument list.
+ * Advances the pointer past the metadata args.
+ *
+ * Returns new SFScript.
+ */
+SFScript*
+script_fu_script_new_from_metadata_args (scheme  *sc,
+                                         pointer *handle)
+{
+  SFScript    *script;
+  const gchar *name;
+  const gchar *menu_label;
+  const gchar *blurb;
+  const gchar *author;
+  const gchar *copyright;
+  const gchar *date;
+  const gchar *image_types;
+  guint        n_args;
+
+  /* dereference handle into local pointer. */
+  pointer a = *handle;
+
+  g_debug ("script_fu_script_new_from_metadata_args");
+
+  /* Require list_length starting at a is >=7
+   * else strange parsing errors at plugin query time.
+   */
+
+  name = sc->vptr->string_value (sc->vptr->pair_car (a));
+  a = sc->vptr->pair_cdr (a);
+  menu_label = sc->vptr->string_value (sc->vptr->pair_car (a));
+  a = sc->vptr->pair_cdr (a);
+  blurb = sc->vptr->string_value (sc->vptr->pair_car (a));
+  a = sc->vptr->pair_cdr (a);
+  author = sc->vptr->string_value (sc->vptr->pair_car (a));
+  a = sc->vptr->pair_cdr (a);
+  copyright = sc->vptr->string_value (sc->vptr->pair_car (a));
+  a = sc->vptr->pair_cdr (a);
+  date = sc->vptr->string_value (sc->vptr->pair_car (a));
+  a = sc->vptr->pair_cdr (a);
+
+  if (sc->vptr->is_pair (a))
+    {
+      image_types = sc->vptr->string_value (sc->vptr->pair_car (a));
+      a = sc->vptr->pair_cdr (a);
+    }
+  else
+    {
+      image_types = sc->vptr->string_value (a);
+      a = sc->NIL;
+    }
+
+  /* Store local, advanced pointer at handle from caller. */
+  *handle = a;
+
+  /* Calculate supplied number of formal arguments of the PDB procedure,
+   * each takes three actual args from Scheme call.
+   */
+  n_args = sc->vptr->list_length (sc, a) / 3;
+
+  /* This allocates empty array of SFArg. Hereafter, script knows its n_args. */
+  script = script_fu_script_new (name,
+                                 menu_label,
+                                 blurb,
+                                 author,
+                                 copyright,
+                                 date,
+                                 image_types,
+                                 n_args);
+  return script;
+}
+
+/* Traverse suffix of Scheme argument list,
+ * creating SFArgs (formal arg specs) from triplets.
+ *
+ * Takes a handle to a pointer into the argument list.
+ * Advances the pointer past the triplets.
+ * Changes state of SFScript.args[]
+ *
+ * Returns a foreign_error or NIL.
+ */
+pointer
+script_fu_script_create_formal_args (scheme   *sc,
+                                     pointer  *handle,
+                                     SFScript *script)
+{
+  /* dereference handle into local pointer. */
+  pointer a = *handle;
+
+  g_debug ("script_fu_script_create_formal_args");
+
+  for (guint i = 0; i < script->n_args; i++)
+    {
+      SFArg *arg = &script->args[i];
+
+      if (a != sc->NIL)
+        {
+          if (!sc->vptr->is_integer (sc->vptr->pair_car (a)))
+            return foreign_error (sc, "script-fu-register: argument types must be integer values", 0);
+
+          arg->type = sc->vptr->ivalue (sc->vptr->pair_car (a));
+          a = sc->vptr->pair_cdr (a);
+        }
+      else
+        return foreign_error (sc, "script-fu-register: missing type specifier", 0);
+
+      if (a != sc->NIL)
+        {
+          if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
+            return foreign_error (sc, "script-fu-register: argument labels must be strings", 0);
+
+          arg->label = g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
+          a = sc->vptr->pair_cdr (a);
+        }
+      else
+        return foreign_error (sc, "script-fu-register: missing arguments label", 0);
+
+      if (a != sc->NIL)
+        {
+          switch (arg->type)
+            {
+            case SF_IMAGE:
+            case SF_DRAWABLE:
+            case SF_LAYER:
+            case SF_CHANNEL:
+            case SF_VECTORS:
+            case SF_DISPLAY:
+              if (!sc->vptr->is_integer (sc->vptr->pair_car (a)))
+                return foreign_error (sc, "script-fu-register: default IDs must be integer values", 0);
+
+              arg->default_value.sfa_image =
+                sc->vptr->ivalue (sc->vptr->pair_car (a));
+              break;
+
+            case SF_COLOR:
+              if (sc->vptr->is_string (sc->vptr->pair_car (a)))
+                {
+                  if (! gimp_rgb_parse_css (&arg->default_value.sfa_color,
+                                            sc->vptr->string_value (sc->vptr->pair_car (a)),
+                                            -1))
+                    return foreign_error (sc, "script-fu-register: invalid default color name", 0);
+
+                  gimp_rgb_set_alpha (&arg->default_value.sfa_color, 1.0);
+                }
+              else if (sc->vptr->is_list (sc, sc->vptr->pair_car (a)) &&
+                       sc->vptr->list_length(sc, sc->vptr->pair_car (a)) == 3)
+                {
+                  pointer color_list;
+                  guchar  r, g, b;
+
+                  color_list = sc->vptr->pair_car (a);
+                  r = CLAMP (sc->vptr->ivalue (sc->vptr->pair_car (color_list)), 0, 255);
+                  color_list = sc->vptr->pair_cdr (color_list);
+                  g = CLAMP (sc->vptr->ivalue (sc->vptr->pair_car (color_list)), 0, 255);
+                  color_list = sc->vptr->pair_cdr (color_list);
+                  b = CLAMP (sc->vptr->ivalue (sc->vptr->pair_car (color_list)), 0, 255);
+
+                  gimp_rgb_set_uchar (&arg->default_value.sfa_color, r, g, b);
+                }
+              else
+                {
+                  return foreign_error (sc, "script-fu-register: color defaults must be a list of 3 integers 
or a color name", 0);
+                }
+              break;
+
+            case SF_TOGGLE:
+              if (!sc->vptr->is_integer (sc->vptr->pair_car (a)))
+                return foreign_error (sc, "script-fu-register: toggle default must be an integer value", 0);
+
+              arg->default_value.sfa_toggle =
+                (sc->vptr->ivalue (sc->vptr->pair_car (a))) ? TRUE : FALSE;
+              break;
+
+            case SF_VALUE:
+              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
+                return foreign_error (sc, "script-fu-register: value defaults must be string values", 0);
+
+              arg->default_value.sfa_value =
+                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
+              break;
+
+            case SF_STRING:
+            case SF_TEXT:
+              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
+                return foreign_error (sc, "script-fu-register: string defaults must be string values", 0);
+
+              arg->default_value.sfa_value =
+                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
+              break;
+
+            case SF_ADJUSTMENT:
+              {
+                pointer adj_list;
+
+                if (!sc->vptr->is_list (sc, a))
+                  return foreign_error (sc, "script-fu-register: adjustment defaults must be a list", 0);
+
+                adj_list = sc->vptr->pair_car (a);
+                arg->default_value.sfa_adjustment.value =
+                  sc->vptr->rvalue (sc->vptr->pair_car (adj_list));
+
+                adj_list = sc->vptr->pair_cdr (adj_list);
+                arg->default_value.sfa_adjustment.lower =
+                  sc->vptr->rvalue (sc->vptr->pair_car (adj_list));
+
+                adj_list = sc->vptr->pair_cdr (adj_list);
+                arg->default_value.sfa_adjustment.upper =
+                  sc->vptr->rvalue (sc->vptr->pair_car (adj_list));
+
+                adj_list = sc->vptr->pair_cdr (adj_list);
+                arg->default_value.sfa_adjustment.step =
+                  sc->vptr->rvalue (sc->vptr->pair_car (adj_list));
+
+                adj_list = sc->vptr->pair_cdr (adj_list);
+                arg->default_value.sfa_adjustment.page =
+                  sc->vptr->rvalue (sc->vptr->pair_car (adj_list));
+
+                adj_list = sc->vptr->pair_cdr (adj_list);
+                arg->default_value.sfa_adjustment.digits =
+                  sc->vptr->ivalue (sc->vptr->pair_car (adj_list));
+
+                adj_list = sc->vptr->pair_cdr (adj_list);
+                arg->default_value.sfa_adjustment.type =
+                  sc->vptr->ivalue (sc->vptr->pair_car (adj_list));
+              }
+              break;
+
+            case SF_FILENAME:
+              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
+                return foreign_error (sc, "script-fu-register: filename defaults must be string values", 0);
+              /* fallthrough */
+
+            case SF_DIRNAME:
+              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
+                return foreign_error (sc, "script-fu-register: dirname defaults must be string values", 0);
+
+              arg->default_value.sfa_file.filename =
+                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
+
+#ifdef G_OS_WIN32
+              {
+                /* Replace POSIX slashes with Win32 backslashes. This
+                 * is just so script-fus can be written with only
+                 * POSIX directory separators.
+                 */
+                gchar *filename = arg->default_value.sfa_file.filename;
+
+                while (*filename)
+                  {
+                    if (*filename == '/')
+                      *filename = G_DIR_SEPARATOR;
+
+                    filename++;
+                  }
+              }
+#endif
+              break;
+
+            case SF_FONT:
+              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
+                return foreign_error (sc, "script-fu-register: font defaults must be string values", 0);
+
+              arg->default_value.sfa_font =
+                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
+              break;
+
+            case SF_PALETTE:
+              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
+                return foreign_error (sc, "script-fu-register: palette defaults must be string values", 0);
+
+              arg->default_value.sfa_palette =
+                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
+              break;
+
+            case SF_PATTERN:
+              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
+                return foreign_error (sc, "script-fu-register: pattern defaults must be string values", 0);
+
+              arg->default_value.sfa_pattern =
+                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
+              break;
+
+            case SF_BRUSH:
+              {
+                pointer brush_list;
+
+                if (!sc->vptr->is_list (sc, a))
+                  return foreign_error (sc, "script-fu-register: brush defaults must be a list", 0);
+
+                brush_list = sc->vptr->pair_car (a);
+                arg->default_value.sfa_brush.name =
+                  g_strdup (sc->vptr->string_value (sc->vptr->pair_car (brush_list)));
+
+                brush_list = sc->vptr->pair_cdr (brush_list);
+                arg->default_value.sfa_brush.opacity =
+                  sc->vptr->rvalue (sc->vptr->pair_car (brush_list));
+
+                brush_list = sc->vptr->pair_cdr (brush_list);
+                arg->default_value.sfa_brush.spacing =
+                  sc->vptr->ivalue (sc->vptr->pair_car (brush_list));
+
+                brush_list = sc->vptr->pair_cdr (brush_list);
+                arg->default_value.sfa_brush.paint_mode =
+                  sc->vptr->ivalue (sc->vptr->pair_car (brush_list));
+              }
+              break;
+
+            case SF_GRADIENT:
+              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
+                return foreign_error (sc, "script-fu-register: gradient defaults must be string values", 0);
+
+              arg->default_value.sfa_gradient =
+                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
+              break;
+
+            case SF_OPTION:
+              {
+                pointer option_list;
+
+                if (!sc->vptr->is_list (sc, a))
+                  return foreign_error (sc, "script-fu-register: option defaults must be a list", 0);
+
+                for (option_list = sc->vptr->pair_car (a);
+                     option_list != sc->NIL;
+                     option_list = sc->vptr->pair_cdr (option_list))
+                  {
+                    arg->default_value.sfa_option.list =
+                      g_slist_append (arg->default_value.sfa_option.list,
+                                      g_strdup (sc->vptr->string_value
+                                                (sc->vptr->pair_car (option_list))));
+                  }
+              }
+              break;
+
+            case SF_ENUM:
+              {
+                pointer      option_list;
+                const gchar *val;
+                gchar       *type_name;
+                GEnumValue  *enum_value;
+                GType        enum_type;
+
+                if (!sc->vptr->is_list (sc, a))
+                  return foreign_error (sc, "script-fu-register: enum defaults must be a list", 0);
+
+                option_list = sc->vptr->pair_car (a);
+                if (!sc->vptr->is_string (sc->vptr->pair_car (option_list)))
+                  return foreign_error (sc, "script-fu-register: first element in enum defaults must be a 
type-name", 0);
+
+                val = sc->vptr->string_value (sc->vptr->pair_car (option_list));
+
+                if (g_str_has_prefix (val, "Gimp"))
+                  type_name = g_strdup (val);
+                else
+                  type_name = g_strconcat ("Gimp", val, NULL);
+
+                enum_type = g_type_from_name (type_name);
+                if (! G_TYPE_IS_ENUM (enum_type))
+                  {
+                    g_free (type_name);
+                    return foreign_error (sc, "script-fu-register: first element in enum defaults must be 
the name of a registered type", 0);
+                  }
+
+                arg->default_value.sfa_enum.type_name = type_name;
+
+                option_list = sc->vptr->pair_cdr (option_list);
+                if (!sc->vptr->is_string (sc->vptr->pair_car (option_list)))
+                  return foreign_error (sc, "script-fu-register: second element in enum defaults must be a 
string", 0);
+
+                enum_value =
+                  g_enum_get_value_by_nick (g_type_class_peek (enum_type),
+                                            sc->vptr->string_value (sc->vptr->pair_car (option_list)));
+                if (enum_value)
+                  arg->default_value.sfa_enum.history = enum_value->value;
+              }
+              break;
+            }
+
+          a = sc->vptr->pair_cdr (a);
+        }
+      else
+        {
+          return foreign_error (sc, "script-fu-register: missing default argument", 0);
+        }
+    } /* end for */
+
+  /* Store local, advanced pointer at handle from caller. */
+  *handle = a;
+
+  return sc->NIL;
+}
+
+/* Traverse next arg in Scheme argument list.
+ * Set SFScript.drawable_arity from the argument.
+ * Used only by script-fu-register-filter.
+ *
+ * Return foreign_error or NIL.
+ */
+pointer
+script_fu_script_parse_drawable_arity_arg (scheme   *sc,
+                                           pointer  *handle,
+                                           SFScript *script)
+{
+  /* dereference handle into local pointer. */
+  pointer a = *handle;
+
+  /* argument must be an int, usually a symbol from enum e.g. SF-MULTIPLE-DRAWABLE */
+  if (!sc->vptr->is_integer (sc->vptr->pair_car (a)))
+    return foreign_error (sc, "script-fu-register-filter: drawable arity must be integer value", 0);
+  script->drawable_arity = sc->vptr->ivalue (sc->vptr->pair_car (a));
+
+  /* Advance the pointer into script. */
+  a = sc->vptr->pair_cdr (a);
+  *handle = a;
+  return sc->NIL;
+}
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-register.h 
b/plug-ins/script-fu/libscriptfu/script-fu-register.h
new file mode 100644
index 0000000000..1773a55982
--- /dev/null
+++ b/plug-ins/script-fu/libscriptfu/script-fu-register.h
@@ -0,0 +1,30 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __SCRIPT_FU_REGISTER_H__
+#define __SCRIPT_FU_REGISTER_H__
+
+pointer   script_fu_script_create_formal_args       (scheme   *sc,
+                                                     pointer  *handle,
+                                                     SFScript *script);
+SFScript *script_fu_script_new_from_metadata_args   (scheme   *sc,
+                                                     pointer  *handle);
+pointer   script_fu_script_parse_drawable_arity_arg (scheme   *sc,
+                                                     pointer  *handle,
+                                                     SFScript *script);
+
+#endif /* __SCRIPT_FU_REGISTER_H__ */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-run-func.c 
b/plug-ins/script-fu/libscriptfu/script-fu-run-func.c
new file mode 100644
index 0000000000..b2e6cbc02b
--- /dev/null
+++ b/plug-ins/script-fu/libscriptfu/script-fu-run-func.c
@@ -0,0 +1,217 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <glib.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "scheme-wrapper.h"       /* type "pointer" */
+
+#include "script-fu-types.h"
+#include "script-fu-interface.h"  /* ScriptFu's GUI implementation. */
+#include "script-fu-dialog.h"     /* Gimp's GUI implementation. */
+#include "script-fu-script.h"
+#include "script-fu-scripts.h"    /* script_fu_find_script */
+#include "script-fu-command.h"
+
+#include "script-fu-run-func.h"
+
+/* Outer run_funcs
+ * One each for GimpProcedure and GimpImageProcedure.
+ * These are called from Gimp, with two different signatures.
+ * These form and interpret "commands" which are calls to inner run_funcs
+ * defined in Scheme by a script.
+
+ * These return the result of interpretation,
+ * in a GimpValueArray whose only element is a status.
+ * !!! ScriptFu does not let authors define procedures that return values.
+ */
+
+/* run_func for a GimpImageProcedure
+ *
+ * Type is GimpRunImageFunc.
+ *
+ * Uses Gimp's config and gui.
+ *
+ * Since 3.0
+ */
+GimpValueArray *
+script_fu_run_image_procedure ( GimpProcedure         *procedure, /* GimpImageProcedure */
+                                GimpRunMode           run_mode,
+                                GimpImage            *image,
+                                guint                 n_drawables,
+                                GimpDrawable        **drawables,
+                                const GimpValueArray *other_args,
+                                gpointer              data)
+{
+
+  GimpValueArray    *result = NULL;
+  SFScript          *script;
+
+  g_debug ("script_fu_run_image_procedure");
+  script = script_fu_find_script (gimp_procedure_get_name (procedure));
+
+  if (! script)
+    return gimp_procedure_new_return_values (procedure, GIMP_PDB_CALLING_ERROR, NULL);
+
+  ts_set_run_mode (run_mode);
+
+  switch (run_mode)
+    {
+    case GIMP_RUN_INTERACTIVE:
+      {
+        if (gimp_value_array_length (other_args) > 0)
+          {
+            /* Let user choose "other" args in a dialog, then interpret. Maintain a config. */
+            result = script_fu_dialog_run (procedure, script, image, n_drawables, drawables, other_args);
+          }
+        else
+          {
+            /* No "other" args for user to choose. No config to maintain. */
+            result = script_fu_interpret_image_proc (procedure, script, image, n_drawables, drawables, 
other_args);
+          }
+        break;
+      }
+    case GIMP_RUN_NONINTERACTIVE:
+      {
+        /* A call from another PDB procedure.
+         * Use the given other_args, without interacting with user.
+         * Since no user interaction, no config to maintain.
+         */
+        result = script_fu_interpret_image_proc (procedure, script, image, n_drawables, drawables, 
other_args);
+        break;
+      }
+    case GIMP_RUN_WITH_LAST_VALS:
+      {
+        /* User invoked from a menu "Filter>Run with last values".
+         * Do not show dialog. other_args are already last values, from a config.
+         */
+        result = script_fu_interpret_image_proc (procedure, script, image, n_drawables, drawables, 
other_args);
+        break;
+      }
+    default:
+      {
+        result = gimp_procedure_new_return_values (procedure, GIMP_PDB_CALLING_ERROR, NULL);
+      }
+    }
+  return result;
+}
+
+
+/* run_func for a GimpProcedure.
+ *
+ * Type is GimpRunFunc
+ *
+ * Uses ScriptFu's own GUI implementation, and retains settings locally.
+ *
+ * Since prior to 3.0 but formerly named script_fu_script_proc
+ */
+GimpValueArray *
+script_fu_run_procedure (GimpProcedure        *procedure,
+                         const GimpValueArray *args,
+                         gpointer              data)
+{
+  GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
+  SFScript          *script;
+  GimpRunMode        run_mode;
+  GError            *error = NULL;
+
+  script = script_fu_find_script (gimp_procedure_get_name (procedure));
+
+  if (! script)
+    return gimp_procedure_new_return_values (procedure,
+                                             GIMP_PDB_CALLING_ERROR,
+                                             NULL);
+
+  run_mode = GIMP_VALUES_GET_ENUM (args, 0);
+
+  ts_set_run_mode (run_mode);
+
+  switch (run_mode)
+    {
+    case GIMP_RUN_INTERACTIVE:
+      {
+        gint min_args = 0;
+
+        /*  First, try to collect the standard script arguments...  */
+        min_args = script_fu_script_collect_standard_args (script, args);
+
+        /*  ...then acquire the rest of arguments (if any) with a dialog  */
+        if (script->n_args > min_args)
+          {
+            status = script_fu_interface (script, min_args);
+            break;
+          }
+        /*  otherwise (if the script takes no more arguments), skip
+         *  this part and run the script directly (fallthrough)
+         */
+      }
+
+    case GIMP_RUN_NONINTERACTIVE:
+      /*  Make sure all the arguments are there  */
+      if (gimp_value_array_length (args) != (script->n_args + 1))
+        status = GIMP_PDB_CALLING_ERROR;
+
+      if (status == GIMP_PDB_SUCCESS)
+        {
+          gchar *command;
+
+          command = script_fu_script_get_command_from_params (script, args);
+
+          /*  run the command through the interpreter  */
+          if (! script_fu_run_command (command, &error))
+            {
+              return gimp_procedure_new_return_values (procedure,
+                                                       GIMP_PDB_EXECUTION_ERROR,
+                                                       error);
+            }
+
+          g_free (command);
+        }
+      break;
+
+    case GIMP_RUN_WITH_LAST_VALS:
+      {
+        gchar *command;
+
+        /*  First, try to collect the standard script arguments  */
+        script_fu_script_collect_standard_args (script, args);
+
+        command = script_fu_script_get_command (script);
+
+        /*  run the command through the interpreter  */
+        if (! script_fu_run_command (command, &error))
+          {
+            return gimp_procedure_new_return_values (procedure,
+                                                     GIMP_PDB_EXECUTION_ERROR,
+                                                     error);
+          }
+
+        g_free (command);
+      }
+      break;
+
+    default:
+      break;
+    }
+
+  return gimp_procedure_new_return_values (procedure, status, NULL);
+}
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-run-func.h 
b/plug-ins/script-fu/libscriptfu/script-fu-run-func.h
new file mode 100644
index 0000000000..86b6380c07
--- /dev/null
+++ b/plug-ins/script-fu/libscriptfu/script-fu-run-func.h
@@ -0,0 +1,33 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef __SCRIPT_FU_RUN_FUNC_H__
+#define __SCRIPT_FU_RUN_FUNC_H__
+
+GimpValueArray *script_fu_run_procedure       (GimpProcedure        *procedure,
+                                               const GimpValueArray *args,
+                                               gpointer              data);
+
+GimpValueArray *script_fu_run_image_procedure (GimpProcedure        *procedure,
+                                               GimpRunMode           run_mode,
+                                               GimpImage            *image,
+                                               guint                 n_drawables,
+                                               GimpDrawable        **drawables,
+                                               const GimpValueArray *args,
+                                               gpointer              data);
+
+#endif /*  __SCRIPT_FU_RUN_FUNC__  */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-script.c 
b/plug-ins/script-fu/libscriptfu/script-fu-script.c
index 5a93cdbc27..97e956278d 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-script.c
+++ b/plug-ins/script-fu/libscriptfu/script-fu-script.c
@@ -27,6 +27,8 @@
 #include "script-fu-types.h"
 #include "script-fu-arg.h"
 #include "script-fu-script.h"
+#include "script-fu-run-func.h"
+
 #include "script-fu-intl.h"
 
 
@@ -34,14 +36,25 @@
  *  Local Functions
  */
 
-static gboolean   script_fu_script_param_init (SFScript             *script,
-                                               const GimpValueArray *args,
-                                               SFArgType             type,
-                                               gint                  n);
-
-
-
-
+static gboolean script_fu_script_param_init (SFScript             *script,
+                                             const GimpValueArray *args,
+                                             SFArgType             type,
+                                             gint                  n);
+static void     script_fu_script_set_proc_metadata (
+                                             GimpProcedure        *procedure,
+                                             SFScript             *script);
+static void     script_fu_script_set_proc_args (
+                                             GimpProcedure        *procedure,
+                                             SFScript             *script,
+                                             guint                 first_conveyed_arg);
+static void     script_fu_script_set_drawable_sensitivity (
+                                             GimpProcedure        *procedure,
+                                             SFScript             *script);
+
+static void     script_fu_command_append_drawables (
+                                             GString              *s,
+                                             guint                 n_drawables,
+                                             GimpDrawable        **drawables);
 /*
  *  Function definitions
  */
@@ -71,6 +84,8 @@ script_fu_script_new (const gchar *name,
   script->n_args = n_args;
   script->args   = g_new0 (SFArg, script->n_args);
 
+  script->drawable_arity = SF_NO_DRAWABLE; /* default */
+
   return script;
 }
 
@@ -106,18 +121,15 @@ script_fu_script_free (SFScript *script)
  */
 void
 script_fu_script_install_proc (GimpPlugIn  *plug_in,
-                               SFScript    *script,
-                               GimpRunFunc  run_func)
+                               SFScript    *script)
 {
   GimpProcedure *procedure;
 
   g_return_if_fail (GIMP_IS_PLUG_IN (plug_in));
   g_return_if_fail (script != NULL);
-  g_return_if_fail (run_func != NULL);
 
   procedure = script_fu_script_create_PDB_procedure (plug_in,
                                                      script,
-                                                     run_func,
                                                      GIMP_PDB_PROC_TYPE_TEMPORARY);
 
   gimp_plug_in_add_temp_procedure (plug_in, procedure);
@@ -126,67 +138,74 @@ script_fu_script_install_proc (GimpPlugIn  *plug_in,
 
 
 /*
- * Create and return a GimpProcedure.
+ * Create and return a GimpProcedure or its subclass GimpImageProcedure.
  * Caller typically either:
  *    install it owned by self as TEMPORARY type procedure
  *    OR return it as the result of a create_procedure callback from GIMP (PLUGIN type procedure.)
  *
  * Caller must unref the procedure.
+ *
+ * Understands ScriptFu's internal run funcs for GimpProcedure and GimpImageProcedure
  */
 GimpProcedure *
 script_fu_script_create_PDB_procedure (GimpPlugIn     *plug_in,
                                        SFScript       *script,
-                                       GimpRunFunc     run_func,
                                        GimpPDBProcType plug_in_type)
 {
   GimpProcedure *procedure;
-  const gchar   *menu_label            = NULL;
 
-  g_debug ("script_fu_script_create_PDB_procedure: %s of type %i", script->name, plug_in_type);
+  if (script->proc_class == GIMP_TYPE_IMAGE_PROCEDURE)
+    {
+      g_debug ("script_fu_script_create_PDB_procedure: %s, plugin type %i, image_proc",
+               script->name, plug_in_type);
+
+      procedure = gimp_image_procedure_new (
+                                      plug_in, script->name,
+                                      plug_in_type,
+                                      (GimpRunImageFunc) script_fu_run_image_procedure,
+                                      script, /* user_data, pointer in extension-script-fu process */
+                                      NULL);
+
+      script_fu_script_set_proc_metadata (procedure, script);
+
+      /* Script author does not declare image, drawable in script-fu-register-filter,
+       * and we don't add to formal args in PDB.
+       * The convenience class GimpImageProcedure already has formal args:
+       * run_mode, image, n_drawables, drawables.
+       * "0" means not skip any arguments declared in the script.
+       */
+      script_fu_script_set_proc_args (procedure, script, 0);
 
-  /* Allow scripts with no menus */
-  if (strncmp (script->menu_label, "<None>", 6) != 0)
-    menu_label = script->menu_label;
+      script_fu_script_set_drawable_sensitivity (procedure, script);
+    }
+  else
+    {
+      g_assert (script->proc_class == GIMP_TYPE_PROCEDURE);
+      g_debug ("script_fu_script_create_PDB_procedure: %s, plugin type %i, ordinary proc",
+               script->name, plug_in_type);
 
-  procedure = gimp_procedure_new (plug_in, script->name,
-                                  plug_in_type,
-                                  run_func, script, NULL);
+      procedure = gimp_procedure_new (plug_in, script->name,
+                                      plug_in_type,
+                                      script_fu_run_procedure,
+                                      script, NULL);
 
-  gimp_procedure_set_image_types (procedure, script->image_types);
+      script_fu_script_set_proc_metadata (procedure, script);
 
-  if (menu_label && strlen (menu_label))
-    gimp_procedure_set_menu_label (procedure, menu_label);
+      gimp_procedure_add_argument (procedure,
+                                   g_param_spec_enum ("run-mode",
+                                                      "Run mode",
+                                                      "The run mode",
+                                                      GIMP_TYPE_RUN_MODE,
+                                                      GIMP_RUN_INTERACTIVE,
+                                                      G_PARAM_READWRITE));
 
-  gimp_procedure_set_documentation (procedure,
-                                    script->blurb,
-                                    NULL,
-                                    script->name);
-  gimp_procedure_set_attribution (procedure,
-                                  script->author,
-                                  script->copyright,
-                                  script->date);
+      script_fu_script_set_proc_args (procedure, script, 0);
 
-  gimp_procedure_add_argument (procedure,
-                               g_param_spec_enum ("run-mode",
-                                                  "Run mode",
-                                                  "The run mode",
-                                                  GIMP_TYPE_RUN_MODE,
-                                                  GIMP_RUN_INTERACTIVE,
-                                                  G_PARAM_READWRITE));
+      /* !!! Author did not declare drawable arity, it was inferred. */
+      script_fu_script_set_drawable_sensitivity (procedure, script);
+    }
 
-  script_fu_arg_reset_name_generator ();
-  for (gint i = 0; i < script->n_args; i++)
-    {
-      GParamSpec  *pspec = NULL;
-      const gchar *name  = NULL;
-      const gchar *nick  = NULL;
 
-      script_fu_arg_generate_name_and_nick (&script->args[i], &name, &nick);
-      pspec = script_fu_arg_get_param_spec (&script->args[i],
-                                            name,
-                                            nick);
-      gimp_procedure_add_argument (procedure, pspec);
-    }
   return procedure;
 }
 
@@ -292,6 +311,10 @@ script_fu_script_collect_standard_args (SFScript             *script,
   return params_consumed;
 }
 
+/* Methods that form "commands" i.e. texts in Scheme language
+ * that represent calls to the inner run func defined in a script.
+ */
+
 gchar *
 script_fu_script_get_command (SFScript *script)
 {
@@ -343,6 +366,96 @@ script_fu_script_get_command_from_params (SFScript             *script,
   return g_string_free (s, FALSE);
 }
 
+/* Append a literal representing a Scheme container of numerics
+ * where the numerics are the ID's of the given drawables.
+ * Container is scheme vector, meaning its elements are all the same type.
+ */
+static void
+script_fu_command_append_drawables (GString       *s,
+                                    guint          n_drawables,
+                                    GimpDrawable **drawables)
+{
+  /* Require non-empty array of drawables. */
+  g_assert (n_drawables > 0);
+
+  /* !!! leading space to separate from prior args.
+   * #() is scheme syntax for a vector.
+   */
+  g_string_append (s, " #(" );
+  for (guint i=0; i < n_drawables; i++)
+    {
+      g_string_append_printf (s, " %d", gimp_item_get_id ((GimpItem*) drawables[i]));
+    }
+  g_string_append (s, ")" );
+  /* Ensure string is like: " #( 1 2 3)" */
+}
+
+
+gchar *
+script_fu_script_get_command_for_image_proc (SFScript            *script,
+                                             GimpImage            *image,
+                                             guint                 n_drawables,
+                                             GimpDrawable        **drawables,
+                                             const GimpValueArray *args)
+{
+  GString *s;
+
+  g_return_val_if_fail (script != NULL, NULL);
+
+  s = g_string_new ("(");
+  g_string_append (s, script->name);
+
+  /* The command has no run mode. */
+
+  /* scripts use integer ID's for Gimp objects. */
+  g_string_append_printf (s, " %d", gimp_image_get_id (image));
+
+  /* Not pass n_drawables.
+   * An author must use Scheme functions for length of container of drawables.
+   */
+
+  /* Append text repr for a container of all drawable ID's.
+   * Even if script->drawable_arity = SF_PROC_IMAGE_SINGLE_DRAWABLE
+   * since that means the inner run func takes many but will only process one.
+   * We are not adapting to an inner run func that expects a single numeric.
+   */
+  script_fu_command_append_drawables (s, n_drawables, drawables);
+
+  /* args contains the "other" args
+   * Iterate over the GimpValueArray.
+   * But script->args should be the same length, and types should match.
+   */
+  for (guint i = 0; i < gimp_value_array_length (args); i++)
+    {
+      GValue *value = gimp_value_array_index (args, i);
+      g_string_append_c (s, ' ');
+      script_fu_arg_append_repr_from_gvalue (&script->args[i],
+                                             s,
+                                             value);
+    }
+
+  g_string_append_c (s, ')');
+
+  return g_string_free (s, FALSE);
+}
+
+/* Infer whether the script, defined using v2 script-fu-register,
+ * which does not specify the arity for drawables,
+ * is actually a script that takes one and only one drawable.
+ * Such plugins are deprecated in v3: each plugin must take container of drawables
+ * and declare its drawable arity via gimp_procedure_set_sensitivity_mask.
+ */
+void
+script_fu_script_infer_drawable_arity (SFScript *script)
+{
+  if ((script->n_args > 1) &&
+      script->args[0].type == SF_IMAGE &&
+      script->args[1].type == SF_DRAWABLE)
+    {
+      g_debug ("Inferring drawable arity one.");
+      script->drawable_arity = SF_ONE_DRAWABLE;
+    }
+}
 
 /*
  *  Local Functions
@@ -431,3 +544,80 @@ script_fu_script_param_init (SFScript             *script,
 
   return FALSE;
 }
+
+
+static void
+script_fu_script_set_proc_metadata (GimpProcedure *procedure,
+                                    SFScript      *script)
+{
+  const gchar *menu_label = NULL;
+
+  /* Allow scripts with no menus */
+  if (strncmp (script->menu_label, "<None>", 6) != 0)
+    menu_label = script->menu_label;
+
+  gimp_procedure_set_image_types (procedure, script->image_types);
+
+  if (menu_label && strlen (menu_label))
+    gimp_procedure_set_menu_label (procedure, menu_label);
+
+  gimp_procedure_set_documentation (procedure,
+                                    script->blurb,
+                                    NULL,
+                                    script->name);
+  gimp_procedure_set_attribution (procedure,
+                                  script->author,
+                                  script->copyright,
+                                  script->date);
+}
+
+/* Convey formal arguments from SFArg to the PDB. */
+static void
+script_fu_script_set_proc_args (GimpProcedure *procedure,
+                                SFScript      *script,
+                                guint          first_conveyed_arg)
+{
+  script_fu_arg_reset_name_generator ();
+  for (gint i = first_conveyed_arg; i < script->n_args; i++)
+    {
+      GParamSpec  *pspec = NULL;
+      const gchar *name  = NULL;
+      const gchar *nick  = NULL;
+
+      script_fu_arg_generate_name_and_nick (&script->args[i], &name, &nick);
+      pspec = script_fu_arg_get_param_spec (&script->args[i],
+                                            name,
+                                            nick);
+      gimp_procedure_add_argument (procedure, pspec);
+    }
+}
+
+/* Convey drawable arity to the PDB.
+ * !!! Unless set, sensitivity defaults to drawable arity 1.
+ * See libgimp/gimpprocedure.c gimp_procedure_set_sensitivity_mask
+ */
+static void
+script_fu_script_set_drawable_sensitivity (GimpProcedure *procedure, SFScript *script)
+{
+  switch (script->drawable_arity)
+    {
+    case SF_TWO_OR_MORE_DRAWABLE:
+      gimp_procedure_set_sensitivity_mask (procedure,
+                                           GIMP_PROCEDURE_SENSITIVE_DRAWABLES );
+      break;
+    case SF_ONE_OR_MORE_DRAWABLE:
+      gimp_procedure_set_sensitivity_mask (procedure,
+                                           GIMP_PROCEDURE_SENSITIVE_DRAWABLES |
+                                           GIMP_PROCEDURE_SENSITIVE_DRAWABLE );
+      break;
+    case SF_ONE_DRAWABLE:
+      gimp_procedure_set_sensitivity_mask (procedure, GIMP_PROCEDURE_SENSITIVE_DRAWABLE);
+      break;
+    case SF_NO_DRAWABLE:
+      /* menu item always sensitive. */
+      break;
+    default:
+      /* Fail to set sensitivy mask. */
+      g_warning ("Unhandled case for SFDrawableArity");
+    }
+}
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-script.h 
b/plug-ins/script-fu/libscriptfu/script-fu-script.h
index a9901cd200..0547685f01 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-script.h
+++ b/plug-ins/script-fu/libscriptfu/script-fu-script.h
@@ -30,8 +30,7 @@ SFScript * script_fu_script_new                     (const gchar          *name,
 void       script_fu_script_free                    (SFScript             *script);
 
 void       script_fu_script_install_proc            (GimpPlugIn           *plug_in,
-                                                     SFScript             *script,
-                                                     GimpRunFunc           run_func);
+                                                     SFScript             *script);
 void       script_fu_script_uninstall_proc          (GimpPlugIn           *plug_in,
                                                      SFScript             *script);
 
@@ -45,12 +44,17 @@ gint       script_fu_script_collect_standard_args   (SFScript             *scrip
 gchar    * script_fu_script_get_command             (SFScript             *script);
 gchar    * script_fu_script_get_command_from_params (SFScript             *script,
                                                      const GimpValueArray *args);
+gchar    * script_fu_script_get_command_for_image_proc (
+                                                     SFScript             *script,
+                                                     GimpImage            *image,
+                                                     guint                 n_drawables,
+                                                     GimpDrawable        **drawables,
+                                                     const GimpValueArray *args);
 
 GimpProcedure * script_fu_script_create_PDB_procedure (GimpPlugIn         *plug_in,
                                                        SFScript           *script,
-                                                       GimpRunFunc         run_func,
                                                        GimpPDBProcType     plug_in_type);
 
-
+void            script_fu_script_infer_drawable_arity (SFScript           *script);
 
 #endif /*  __SCRIPT_FU_SCRIPT__  */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-scripts.c 
b/plug-ins/script-fu/libscriptfu/script-fu-scripts.c
index 914ea1a6c6..cbb54f3a48 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-scripts.c
+++ b/plug-ins/script-fu/libscriptfu/script-fu-scripts.c
@@ -18,7 +18,6 @@
 #include "config.h"
 
 #include <string.h>
-
 #include <glib.h>
 
 #ifdef G_OS_WIN32
@@ -31,14 +30,12 @@
 
 #include "tinyscheme/scheme-private.h"
 
-#include "scheme-wrapper.h"
-
 #include "script-fu-types.h"
-
-#include "script-fu-interface.h"
 #include "script-fu-script.h"
 #include "script-fu-scripts.h"
 #include "script-fu-utils.h"
+#include "script-fu-register.h"
+#include "script-fu-command.h"
 
 #include "script-fu-intl.h"
 
@@ -47,8 +44,6 @@
  *  Local Functions
  */
 
-static gboolean         script_fu_run_command    (const gchar          *command,
-                                                  GError              **error);
 static void             script_fu_load_directory (GFile                *directory);
 static void             script_fu_load_script    (GFile                *file);
 static gboolean         script_fu_install_script (gpointer              foo,
@@ -63,6 +58,8 @@ static gchar          * script_fu_menu_map       (const gchar          *menu_pat
 static gint             script_fu_menu_compare   (gconstpointer         a,
                                                   gconstpointer         b);
 
+static void             script_fu_try_map_menu           (SFScript     *script);
+static void             script_fu_append_script_to_tree  (SFScript     *script);
 
 /*
  *  Local variables
@@ -78,14 +75,14 @@ static GList *script_menu_list = NULL;
 
 /* Traverse list of paths, finding .scm files.
  * Load and eval any found script texts.
- * Script texts will call Scheme functions script-fu-register()
- * and script-fu-menu-register(),
+ * Script texts will call Scheme functions script-fu-register
+ * and script-fu-menu-register,
  * which insert a SFScript record into script_tree,
  * and insert a SFMenu record into script_menu_list.
  * These are side effects on the state of the outer (SF) interpreter.
  *
  * Return the tree of scripts, as well as keeping a local pointer to the tree.
- * The other result (script_menu_list) is not returned, see script_fu_get_menu_list().
+ * The other result (script_menu_list) is not returned, see script_fu_get_menu_list.
  *
  * Caller should free script_tree and script_menu_list,
  * This should only be called once.
@@ -126,7 +123,7 @@ script_fu_find_scripts_into_tree ( GimpPlugIn *plug_in,
 
 /*
  * Return list of SFMenu for recently loaded scripts.
- * List is non-empty only after a call to script_fu_find_scripts_into_tree().
+ * List is non-empty only after a call to script_fu_find_scripts_into_tree.
  */
 GList *
 script_fu_get_menu_list (void)
@@ -157,393 +154,99 @@ script_fu_find_scripts (GimpPlugIn *plug_in,
   script_menu_list = NULL;
 }
 
+
+
+/* For a script's call to script-fu-register.
+ * Traverse Scheme argument list creating a new SFScript
+ * whose drawable_arity is SF_PROC_ORDINARY.
+ *
+ * Return NIL or a foreign_error
+ */
 pointer
 script_fu_add_script (scheme  *sc,
                       pointer  a)
 {
   SFScript    *script;
-  const gchar *name;
-  const gchar *menu_label;
-  const gchar *blurb;
-  const gchar *author;
-  const gchar *copyright;
-  const gchar *date;
-  const gchar *image_types;
-  gint         n_args;
-  gint         i;
+  pointer      args_error;
 
-  /*  Check the length of a  */
+  /*  Check metadata args args are present */
   if (sc->vptr->list_length (sc, a) < 7)
-    {
-      g_message (_("Too few arguments to 'script-fu-register' call"));
-      return sc->NIL;
-    }
-
-  /*  Find the script name  */
-  name = sc->vptr->string_value (sc->vptr->pair_car (a));
-  a = sc->vptr->pair_cdr (a);
-
-  /*  Find the script menu_label  */
-  menu_label = sc->vptr->string_value (sc->vptr->pair_car (a));
-  a = sc->vptr->pair_cdr (a);
-
-  /*  Find the script blurb  */
-  blurb = sc->vptr->string_value (sc->vptr->pair_car (a));
-  a = sc->vptr->pair_cdr (a);
-
-  /*  Find the script author  */
-  author = sc->vptr->string_value (sc->vptr->pair_car (a));
-  a = sc->vptr->pair_cdr (a);
-
-  /*  Find the script copyright  */
-  copyright = sc->vptr->string_value (sc->vptr->pair_car (a));
-  a = sc->vptr->pair_cdr (a);
-
-  /*  Find the script date  */
-  date = sc->vptr->string_value (sc->vptr->pair_car (a));
-  a = sc->vptr->pair_cdr (a);
-
-  /*  Find the script image types  */
-  if (sc->vptr->is_pair (a))
-    {
-      image_types = sc->vptr->string_value (sc->vptr->pair_car (a));
-      a = sc->vptr->pair_cdr (a);
-    }
-  else
-    {
-      image_types = sc->vptr->string_value (a);
-      a = sc->NIL;
-    }
-
-  /*  Check the supplied number of arguments  */
-  n_args = sc->vptr->list_length (sc, a) / 3;
-
-  /*  Create a new script  */
-  script = script_fu_script_new (name,
-                                 menu_label,
-                                 blurb,
-                                 author,
-                                 copyright,
-                                 date,
-                                 image_types,
-                                 n_args);
-
-  for (i = 0; i < script->n_args; i++)
-    {
-      SFArg *arg = &script->args[i];
-
-      if (a != sc->NIL)
-        {
-          if (!sc->vptr->is_integer (sc->vptr->pair_car (a)))
-            return foreign_error (sc, "script-fu-register: argument types must be integer values", 0);
-
-          arg->type = sc->vptr->ivalue (sc->vptr->pair_car (a));
-          a = sc->vptr->pair_cdr (a);
-        }
-      else
-        return foreign_error (sc, "script-fu-register: missing type specifier", 0);
-
-      if (a != sc->NIL)
-        {
-          if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
-            return foreign_error (sc, "script-fu-register: argument labels must be strings", 0);
-
-          arg->label = g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
-          a = sc->vptr->pair_cdr (a);
-        }
-      else
-        return foreign_error (sc, "script-fu-register: missing arguments label", 0);
-
-      if (a != sc->NIL)
-        {
-          switch (arg->type)
-            {
-            case SF_IMAGE:
-            case SF_DRAWABLE:
-            case SF_LAYER:
-            case SF_CHANNEL:
-            case SF_VECTORS:
-            case SF_DISPLAY:
-              if (!sc->vptr->is_integer (sc->vptr->pair_car (a)))
-                return foreign_error (sc, "script-fu-register: default IDs must be integer values", 0);
-
-              arg->default_value.sfa_image =
-                sc->vptr->ivalue (sc->vptr->pair_car (a));
-              break;
-
-            case SF_COLOR:
-              if (sc->vptr->is_string (sc->vptr->pair_car (a)))
-                {
-                  if (! gimp_rgb_parse_css (&arg->default_value.sfa_color,
-                                            sc->vptr->string_value (sc->vptr->pair_car (a)),
-                                            -1))
-                    return foreign_error (sc, "script-fu-register: invalid default color name", 0);
-
-                  gimp_rgb_set_alpha (&arg->default_value.sfa_color, 1.0);
-                }
-              else if (sc->vptr->is_list (sc, sc->vptr->pair_car (a)) &&
-                       sc->vptr->list_length(sc, sc->vptr->pair_car (a)) == 3)
-                {
-                  pointer color_list;
-                  guchar  r, g, b;
-
-                  color_list = sc->vptr->pair_car (a);
-                  r = CLAMP (sc->vptr->ivalue (sc->vptr->pair_car (color_list)), 0, 255);
-                  color_list = sc->vptr->pair_cdr (color_list);
-                  g = CLAMP (sc->vptr->ivalue (sc->vptr->pair_car (color_list)), 0, 255);
-                  color_list = sc->vptr->pair_cdr (color_list);
-                  b = CLAMP (sc->vptr->ivalue (sc->vptr->pair_car (color_list)), 0, 255);
-
-                  gimp_rgb_set_uchar (&arg->default_value.sfa_color, r, g, b);
-                }
-              else
-                {
-                  return foreign_error (sc, "script-fu-register: color defaults must be a list of 3 integers 
or a color name", 0);
-                }
-              break;
-
-            case SF_TOGGLE:
-              if (!sc->vptr->is_integer (sc->vptr->pair_car (a)))
-                return foreign_error (sc, "script-fu-register: toggle default must be an integer value", 0);
-
-              arg->default_value.sfa_toggle =
-                (sc->vptr->ivalue (sc->vptr->pair_car (a))) ? TRUE : FALSE;
-              break;
-
-            case SF_VALUE:
-              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
-                return foreign_error (sc, "script-fu-register: value defaults must be string values", 0);
-
-              arg->default_value.sfa_value =
-                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
-              break;
-
-            case SF_STRING:
-            case SF_TEXT:
-              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
-                return foreign_error (sc, "script-fu-register: string defaults must be string values", 0);
-
-              arg->default_value.sfa_value =
-                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
-              break;
-
-            case SF_ADJUSTMENT:
-              {
-                pointer adj_list;
-
-                if (!sc->vptr->is_list (sc, a))
-                  return foreign_error (sc, "script-fu-register: adjustment defaults must be a list", 0);
-
-                adj_list = sc->vptr->pair_car (a);
-                arg->default_value.sfa_adjustment.value =
-                  sc->vptr->rvalue (sc->vptr->pair_car (adj_list));
+    return foreign_error (sc, "script-fu-register: Not enough arguments", 0);
 
-                adj_list = sc->vptr->pair_cdr (adj_list);
-                arg->default_value.sfa_adjustment.lower =
-                  sc->vptr->rvalue (sc->vptr->pair_car (adj_list));
+  /* pass handle to pointer into script (on the stack) */
+  script = script_fu_script_new_from_metadata_args (sc, &a);
 
-                adj_list = sc->vptr->pair_cdr (adj_list);
-                arg->default_value.sfa_adjustment.upper =
-                  sc->vptr->rvalue (sc->vptr->pair_car (adj_list));
-
-                adj_list = sc->vptr->pair_cdr (adj_list);
-                arg->default_value.sfa_adjustment.step =
-                  sc->vptr->rvalue (sc->vptr->pair_car (adj_list));
-
-                adj_list = sc->vptr->pair_cdr (adj_list);
-                arg->default_value.sfa_adjustment.page =
-                  sc->vptr->rvalue (sc->vptr->pair_car (adj_list));
+  /* Require drawable_arity defaults to SF_PROC_ORDINARY.
+   * script-fu-register specifies an ordinary GimpProcedure.
+   * We may go on to infer a different arity.
+   */
+  g_assert (script->drawable_arity == SF_NO_DRAWABLE);
 
-                adj_list = sc->vptr->pair_cdr (adj_list);
-                arg->default_value.sfa_adjustment.digits =
-                  sc->vptr->ivalue (sc->vptr->pair_car (adj_list));
+  args_error = script_fu_script_create_formal_args (sc, &a, script);
+  if (args_error != sc->NIL)
+    return args_error;
 
-                adj_list = sc->vptr->pair_cdr (adj_list);
-                arg->default_value.sfa_adjustment.type =
-                  sc->vptr->ivalue (sc->vptr->pair_car (adj_list));
-              }
-              break;
+  /*  fill all values from defaults  */
+  script_fu_script_reset (script, TRUE);
 
-            case SF_FILENAME:
-              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
-                return foreign_error (sc, "script-fu-register: filename defaults must be string values", 0);
-              /* fallthrough */
+  /* Infer whether the script really requires one drawable,
+   * so that later we can set the sensitivity.
+   * For backward compatibility:
+   * v2 script-fu-register does not require author to declare drawable arity.
+   */
+  script_fu_script_infer_drawable_arity (script);
 
-            case SF_DIRNAME:
-              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
-                return foreign_error (sc, "script-fu-register: dirname defaults must be string values", 0);
+  script->proc_class = GIMP_TYPE_PROCEDURE;
 
-              arg->default_value.sfa_file.filename =
-                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
+  script_fu_try_map_menu (script);
+  script_fu_append_script_to_tree (script);
+  return sc->NIL;
+}
 
-#ifdef G_OS_WIN32
-              {
-                /* Replace POSIX slashes with Win32 backslashes. This
-                 * is just so script-fus can be written with only
-                 * POSIX directory separators.
-                 */
-                gchar *filename = arg->default_value.sfa_file.filename;
-
-                while (*filename)
-                  {
-                    if (*filename == '/')
-                      *filename = G_DIR_SEPARATOR;
-
-                    filename++;
-                  }
-              }
-#endif
-              break;
-
-            case SF_FONT:
-              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
-                return foreign_error (sc, "script-fu-register: font defaults must be string values", 0);
-
-              arg->default_value.sfa_font =
-                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
-              break;
-
-            case SF_PALETTE:
-              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
-                return foreign_error (sc, "script-fu-register: palette defaults must be string values", 0);
-
-              arg->default_value.sfa_palette =
-                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
-              break;
-
-            case SF_PATTERN:
-              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
-                return foreign_error (sc, "script-fu-register: pattern defaults must be string values", 0);
-
-              arg->default_value.sfa_pattern =
-                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
-              break;
-
-            case SF_BRUSH:
-              {
-                pointer brush_list;
-
-                if (!sc->vptr->is_list (sc, a))
-                  return foreign_error (sc, "script-fu-register: brush defaults must be a list", 0);
-
-                brush_list = sc->vptr->pair_car (a);
-                arg->default_value.sfa_brush.name =
-                  g_strdup (sc->vptr->string_value (sc->vptr->pair_car (brush_list)));
-
-                brush_list = sc->vptr->pair_cdr (brush_list);
-                arg->default_value.sfa_brush.opacity =
-                  sc->vptr->rvalue (sc->vptr->pair_car (brush_list));
-
-                brush_list = sc->vptr->pair_cdr (brush_list);
-                arg->default_value.sfa_brush.spacing =
-                  sc->vptr->ivalue (sc->vptr->pair_car (brush_list));
-
-                brush_list = sc->vptr->pair_cdr (brush_list);
-                arg->default_value.sfa_brush.paint_mode =
-                  sc->vptr->ivalue (sc->vptr->pair_car (brush_list));
-              }
-              break;
-
-            case SF_GRADIENT:
-              if (!sc->vptr->is_string (sc->vptr->pair_car (a)))
-                return foreign_error (sc, "script-fu-register: gradient defaults must be string values", 0);
-
-              arg->default_value.sfa_gradient =
-                g_strdup (sc->vptr->string_value (sc->vptr->pair_car (a)));
-              break;
-
-            case SF_OPTION:
-              {
-                pointer option_list;
-
-                if (!sc->vptr->is_list (sc, a))
-                  return foreign_error (sc, "script-fu-register: option defaults must be a list", 0);
-
-                for (option_list = sc->vptr->pair_car (a);
-                     option_list != sc->NIL;
-                     option_list = sc->vptr->pair_cdr (option_list))
-                  {
-                    arg->default_value.sfa_option.list =
-                      g_slist_append (arg->default_value.sfa_option.list,
-                                      g_strdup (sc->vptr->string_value
-                                                (sc->vptr->pair_car (option_list))));
-                  }
-              }
-              break;
-
-            case SF_ENUM:
-              {
-                pointer      option_list;
-                const gchar *val;
-                gchar       *type_name;
-                GEnumValue  *enum_value;
-                GType        enum_type;
-
-                if (!sc->vptr->is_list (sc, a))
-                  return foreign_error (sc, "script-fu-register: enum defaults must be a list", 0);
-
-                option_list = sc->vptr->pair_car (a);
-                if (!sc->vptr->is_string (sc->vptr->pair_car (option_list)))
-                  return foreign_error (sc, "script-fu-register: first element in enum defaults must be a 
type-name", 0);
-
-                val = sc->vptr->string_value (sc->vptr->pair_car (option_list));
-
-                if (g_str_has_prefix (val, "Gimp"))
-                  type_name = g_strdup (val);
-                else
-                  type_name = g_strconcat ("Gimp", val, NULL);
-
-                enum_type = g_type_from_name (type_name);
-                if (! G_TYPE_IS_ENUM (enum_type))
-                  {
-                    g_free (type_name);
-                    return foreign_error (sc, "script-fu-register: first element in enum defaults must be 
the name of a registered type", 0);
-                  }
-
-                arg->default_value.sfa_enum.type_name = type_name;
-
-                option_list = sc->vptr->pair_cdr (option_list);
-                if (!sc->vptr->is_string (sc->vptr->pair_car (option_list)))
-                  return foreign_error (sc, "script-fu-register: second element in enum defaults must be a 
string", 0);
-
-                enum_value =
-                  g_enum_get_value_by_nick (g_type_class_peek (enum_type),
-                                            sc->vptr->string_value (sc->vptr->pair_car (option_list)));
-                if (enum_value)
-                  arg->default_value.sfa_enum.history = enum_value->value;
-              }
-              break;
-            }
+/* For a script's call to script-fu-register-filter.
+ * Traverse Scheme argument list creating a new SFScript
+ * whose drawable_arity is SF_PROC_IMAGE_MULTIPLE_DRAWABLE or
+ * SF_PROC_IMAGE_SINGLE_DRAWABLE
+ *
+ * Same as script-fu-register, except one more arg for drawable_arity.
+ *
+ * Return NIL or a foreign_error
+ */
+pointer
+script_fu_add_script_filter (scheme  *sc,
+                             pointer  a)
+{
+  SFScript    *script;
+  pointer      args_error;  /* a foreign_error or NIL. */
 
-          a = sc->vptr->pair_cdr (a);
-        }
-      else
-        {
-          return foreign_error (sc, "script-fu-register: missing default argument", 0);
-        }
-    }
+  /* Check metadata args args are present.
+   * Has one more arg than script-fu-register.
+   */
+  if (sc->vptr->list_length (sc, a) < 8)
+    return foreign_error (sc, "script-fu-register-filter: Not enough arguments", 0);
 
-  /*  fill all values from defaults  */
-  script_fu_script_reset (script, TRUE);
+  /* pass handle i.e. "&a" ("a" of type "pointer" is on the stack) */
+  script = script_fu_script_new_from_metadata_args (sc, &a);
 
-  if (script->menu_label[0] == '<')
-    {
-      gchar *mapped = script_fu_menu_map (script->menu_label);
+  /* Check semantic error: a script declaring it takes an image must specify
+   * image types.  Otherwise the script's menu item will be enabled
+   * even when no images exist.
+   */
+  if (g_strcmp0(script->image_types, "")==0)
+    return foreign_error (sc, "script-fu-register-filter: A filter must declare image types.", 0);
 
-      if (mapped)
-        {
-          g_free (script->menu_label);
-          script->menu_label = mapped;
-        }
-    }
+  args_error = script_fu_script_parse_drawable_arity_arg (sc, &a, script);
+  if (args_error != sc->NIL)
+      return args_error;
 
-  {
-    GList *list = g_tree_lookup (script_tree, script->menu_label);
+  args_error = script_fu_script_create_formal_args (sc, &a, script);
+  if (args_error != sc->NIL)
+      return args_error;
 
-    g_tree_insert (script_tree, (gpointer) script->menu_label,
-                    g_list_append (list, script));
-  }
+  script->proc_class = GIMP_TYPE_IMAGE_PROCEDURE;
 
+  script_fu_try_map_menu (script);
+  script_fu_append_script_to_tree (script);
   return sc->NIL;
 }
 
@@ -594,31 +297,6 @@ script_fu_add_menu (scheme  *sc,
 
 /*  private functions  */
 
-static gboolean
-script_fu_run_command (const gchar  *command,
-                       GError      **error)
-{
-  GString  *output;
-  gboolean  success = FALSE;
-
-  g_debug ("script_fu_run_command: %s", command);
-  output = g_string_new (NULL);
-  ts_register_output_func (ts_gstring_output_func, output);
-
-  if (ts_interpret_string (command))
-    {
-      g_set_error (error, GIMP_PLUG_IN_ERROR, 0, "%s", output->str);
-    }
-  else
-    {
-      success = TRUE;
-    }
-
-  g_string_free (output, TRUE);
-
-  return success;
-}
-
 static void
 script_fu_load_directory (GFile *directory)
 {
@@ -717,8 +395,7 @@ script_fu_install_script (gpointer  foo G_GNUC_UNUSED,
 
       const gchar* name = script->name;
       if (script_fu_is_defined (name))
-        script_fu_script_install_proc (plug_in, script,
-                                       script_fu_script_proc);
+        script_fu_script_install_proc (plug_in, script);
       else
         g_warning ("Run function not defined, or does not match PDB procedure name: %s", name);
     }
@@ -766,105 +443,7 @@ script_fu_remove_script (gpointer  foo G_GNUC_UNUSED,
   return FALSE;
 }
 
-/* This is the outer "run func" for this plugin.
- * When called, the name of the inner run func (code in Scheme language)
- * is the first element of the value array.
- * Form a command (text in Scheme language) that is a call to the the inner run func,
- * evaluate it, and return the result, marshalled into a GimpValueArray.
- *
- * In the name 'script_fu_script_proc',  'proc' is a verb meaning 'process the script'
- */
-GimpValueArray *
-script_fu_script_proc (GimpProcedure        *procedure,
-                       const GimpValueArray *args,
-                       gpointer              data)
-{
-  GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
-  SFScript          *script;
-  GimpRunMode        run_mode;
-  GError            *error = NULL;
 
-  script = script_fu_find_script (gimp_procedure_get_name (procedure));
-
-  if (! script)
-    return gimp_procedure_new_return_values (procedure,
-                                             GIMP_PDB_CALLING_ERROR,
-                                             NULL);
-
-  run_mode = GIMP_VALUES_GET_ENUM (args, 0);
-
-  ts_set_run_mode (run_mode);
-
-  switch (run_mode)
-    {
-    case GIMP_RUN_INTERACTIVE:
-      {
-        gint min_args = 0;
-
-        /*  First, try to collect the standard script arguments...  */
-        min_args = script_fu_script_collect_standard_args (script, args);
-
-        /*  ...then acquire the rest of arguments (if any) with a dialog  */
-        if (script->n_args > min_args)
-          {
-            status = script_fu_interface (script, min_args);
-            break;
-          }
-        /*  otherwise (if the script takes no more arguments), skip
-         *  this part and run the script directly (fallthrough)
-         */
-      }
-
-    case GIMP_RUN_NONINTERACTIVE:
-      /*  Make sure all the arguments are there  */
-      if (gimp_value_array_length (args) != (script->n_args + 1))
-        status = GIMP_PDB_CALLING_ERROR;
-
-      if (status == GIMP_PDB_SUCCESS)
-        {
-          gchar *command;
-
-          command = script_fu_script_get_command_from_params (script, args);
-
-          /*  run the command through the interpreter  */
-          if (! script_fu_run_command (command, &error))
-            {
-              return gimp_procedure_new_return_values (procedure,
-                                                       GIMP_PDB_EXECUTION_ERROR,
-                                                       error);
-            }
-
-          g_free (command);
-        }
-      break;
-
-    case GIMP_RUN_WITH_LAST_VALS:
-      {
-        gchar *command;
-
-        /*  First, try to collect the standard script arguments  */
-        script_fu_script_collect_standard_args (script, args);
-
-        command = script_fu_script_get_command (script);
-
-        /*  run the command through the interpreter  */
-        if (! script_fu_run_command (command, &error))
-          {
-            return gimp_procedure_new_return_values (procedure,
-                                                     GIMP_PDB_EXECUTION_ERROR,
-                                                     error);
-          }
-
-        g_free (command);
-      }
-      break;
-
-    default:
-      break;
-    }
-
-  return gimp_procedure_new_return_values (procedure, status, NULL);
-}
 
 /* this is a GTraverseFunction */
 static gboolean
@@ -1004,3 +583,32 @@ script_fu_is_defined (const gchar * name)
     }
   return result;
 }
+
+
+/* Side effects on script. */
+static void
+script_fu_try_map_menu (SFScript *script)
+{
+  if (script->menu_label[0] == '<')
+    {
+      gchar *mapped = script_fu_menu_map (script->menu_label);
+
+      if (mapped)
+        {
+          g_free (script->menu_label);
+          script->menu_label = mapped;
+        }
+    }
+}
+
+/* Append to ordered tree.
+ * Side effects on script_tree.
+ */
+static void
+script_fu_append_script_to_tree (SFScript *script)
+{
+  GList *list = g_tree_lookup (script_tree, script->menu_label);
+
+  g_tree_insert (script_tree, (gpointer) script->menu_label,
+                  g_list_append (list, script));
+}
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-scripts.h 
b/plug-ins/script-fu/libscriptfu/script-fu-scripts.h
index b81e779ab1..7441c2424b 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-scripts.h
+++ b/plug-ins/script-fu/libscriptfu/script-fu-scripts.h
@@ -18,11 +18,12 @@
 #ifndef __SCRIPT_FU_SCRIPTS_H__
 #define __SCRIPT_FU_SCRIPTS_H__
 
-
 void      script_fu_find_scripts  (GimpPlugIn *plug_in,
                                    GList      *path);
-pointer   script_fu_add_script    (scheme     *sc,
-                                   pointer     a);
+pointer   script_fu_add_script        (scheme     *sc,
+                                       pointer     a);
+pointer   script_fu_add_script_filter (scheme     *sc,
+                                       pointer     a);
 pointer   script_fu_add_menu      (scheme     *sc,
                                    pointer     a);
 
@@ -30,9 +31,7 @@ GTree          * script_fu_find_scripts_into_tree (GimpPlugIn  *plug_in,
                                                    GList       *path);
 SFScript       * script_fu_find_script            (const gchar *name);
 GList          * script_fu_get_menu_list          (void);
-GimpValueArray * script_fu_script_proc            (GimpProcedure        *procedure,
-                                                   const GimpValueArray *args,
-                                                   gpointer              data);
+
 gboolean         script_fu_is_defined             (const gchar          *name);
 
 #endif /*  __SCRIPT_FU_SCRIPTS__  */
diff --git a/plug-ins/script-fu/libscriptfu/script-fu-types.h 
b/plug-ins/script-fu/libscriptfu/script-fu-types.h
index 981e450e0e..d74c5e6b40 100644
--- a/plug-ins/script-fu/libscriptfu/script-fu-types.h
+++ b/plug-ins/script-fu/libscriptfu/script-fu-types.h
@@ -101,6 +101,8 @@ typedef struct
 
   gint          n_args;
   SFArg        *args;
+  SFDrawableArity drawable_arity;
+  GType           proc_class; /* GimpProcedure or GimpImageProcedure. */
 } SFScript;
 
 typedef struct
diff --git a/plug-ins/script-fu/scripts/Makefile.am b/plug-ins/script-fu/scripts/Makefile.am
index c9564d8f78..1f0ce2be29 100644
--- a/plug-ins/script-fu/scripts/Makefile.am
+++ b/plug-ins/script-fu/scripts/Makefile.am
@@ -57,18 +57,26 @@ scripts = \
        weave.scm                       \
        xach-effect.scm
 
+# FIXME: when 3.0 ships, ship only the v3 versions
+# of clothify.scm and test-sphere.scm
+# During dev of 2.99, install old and new so devs can compare their GUIs.
 test_scripts = \
-       contactsheet.scm                \
-       test-sphere.scm
+       contactsheet.scm   \
+       test-sphere.scm    \
+       clothify-v3.scm
+
 
 # scripts interpreted by gimp-script-fu-interpreter
 # Each installs to a subdir of /plug-ins
 # Each should have a shebang and execute permission
 independent_scripts = \
-       ts-helloworld.scm
+       ts-helloworld.scm   \
+       test-sphere-v3.scm
 
 ts_helloworlddir = $(gimpplugindir)/plug-ins/ts-helloworld
 ts_helloworld_SCRIPTS = ts-helloworld.scm
+test_sphere_v3dir = $(gimpplugindir)/plug-ins/test-sphere-v3
+test_sphere_v3_SCRIPTS = test-sphere-v3.scm
 
 
 scriptdata_DATA = $(scripts)
diff --git a/plug-ins/script-fu/scripts/clothify-v3.scm b/plug-ins/script-fu/scripts/clothify-v3.scm
new file mode 100644
index 0000000000..00207dec9b
--- /dev/null
+++ b/plug-ins/script-fu/scripts/clothify-v3.scm
@@ -0,0 +1,84 @@
+; CLOTHIFY version 1.02
+; Gives the current layer in the indicated image a cloth-like texture.
+; Process invented by Zach Beane (Xath irc gimp net)
+;
+; Tim Newsome <drz froody bloke com> 4/11/97
+
+; v3>>> Adapted to take many drawables, but only handle the first
+; v3>>> drawables is-a vector, and there is no formal arg for its length i.e.  n_drawables
+
+(define (script-fu-clothify-v3 timg drawables bx by azimuth elevation depth)
+  (let* (
+        (tdrawable (aref drawables 0))  v3>>> only the first drawable
+        (width (car (gimp-drawable-get-width tdrawable)))
+        (height (car (gimp-drawable-get-height tdrawable)))
+        (img (car (gimp-image-new width height RGB)))
+;       (layer-two (car (gimp-layer-new img width height RGB-IMAGE "Y Dots" 100 LAYER-MODE-MULTIPLY)))
+        (layer-one (car (gimp-layer-new img width height RGB-IMAGE "X Dots" 100 LAYER-MODE-NORMAL)))
+        (layer-two 0)
+        (bump-layer 0)
+        )
+
+    (gimp-context-push)
+    (gimp-context-set-defaults)
+
+    (gimp-image-undo-disable img)
+
+    (gimp-image-insert-layer img layer-one 0 0)
+
+    (gimp-context-set-background '(255 255 255))
+    (gimp-drawable-edit-fill layer-one FILL-BACKGROUND)
+
+    (plug-in-noisify RUN-NONINTERACTIVE img layer-one FALSE 0.7 0.7 0.7 0.7)
+
+    (set! layer-two (car (gimp-layer-copy layer-one 0)))
+    (gimp-layer-set-mode layer-two LAYER-MODE-MULTIPLY)
+    (gimp-image-insert-layer img layer-two 0 0)
+
+    (plug-in-gauss-rle RUN-NONINTERACTIVE img layer-one bx TRUE FALSE)
+    (plug-in-gauss-rle RUN-NONINTERACTIVE img layer-two by FALSE TRUE)
+    (gimp-image-flatten img)
+    (set! bump-layer (car (gimp-image-get-active-layer img)))
+
+    (plug-in-c-astretch RUN-NONINTERACTIVE img bump-layer)
+    (plug-in-noisify RUN-NONINTERACTIVE img bump-layer FALSE 0.2 0.2 0.2 0.2)
+
+    (plug-in-bump-map RUN-NONINTERACTIVE img tdrawable bump-layer azimuth elevation depth 0 0 0 0 FALSE 
FALSE 0)
+    (gimp-image-delete img)
+    (gimp-displays-flush)
+
+    (gimp-context-pop)
+
+    ; well-behaved requires error if more than one drawable
+    ( if (> (vector-length drawables) 1 )
+         (begin
+            ; Msg to status bar, need not be acknowledged by any user
+            (gimp-message "Received more than one drawable.")
+            ; Msg propagated in a GError to Gimp's error dialog that must be acknowledged
+            (write "Received more than one drawable.")
+            ; Indicate err to programmed callers
+            #f)
+         #t
+    )
+  )
+)
+
+; v3 >>> no image or drawable declared.
+; v3 >>> SF-ONE-DRAWABLE means contracts to process only one drawable
+(script-fu-register-filter "script-fu-clothify-v3"
+  _"_Clothify v3..."
+  _"Add a cloth-like texture to the selected region (or alpha)"
+  "Tim Newsome <drz froody bloke com>"
+  "Tim Newsome"
+  "4/11/97"
+  "RGB* GRAY*"
+  SF-ONE-DRAWABLE
+  SF-ADJUSTMENT _"Blur X"         '(9 3 100 1 10 0 1)
+  SF-ADJUSTMENT _"Blur Y"         '(9 3 100 1 10 0 1)
+  SF-ADJUSTMENT _"Azimuth"        '(135 0 360 1 10 1 0)
+  SF-ADJUSTMENT _"Elevation"      '(45 0 90 1 10 1 0)
+  SF-ADJUSTMENT _"Depth"          '(3 1 50 1 10 0 1)
+)
+
+(script-fu-menu-register "script-fu-clothify-v3"
+                         "<Image>/Filters/Artistic")
diff --git a/plug-ins/script-fu/scripts/meson.build b/plug-ins/script-fu/scripts/meson.build
index 5dc33b52b1..2feae66d3e 100644
--- a/plug-ins/script-fu/scripts/meson.build
+++ b/plug-ins/script-fu/scripts/meson.build
@@ -53,6 +53,7 @@ scripts = [
   'waves-anim.scm',
   'weave.scm',
   'xach-effect.scm',
+  'clothify-v3.scm'
 ]
 
 if not stable
@@ -75,6 +76,7 @@ install_data(
 
 scripts_independent = [
   { 'name': 'ts-helloworld' },
+  { 'name': 'test-sphere-v3' },
 ]
 
 foreach plugin : scripts_independent
diff --git a/plug-ins/script-fu/scripts/plug-in-compat.init b/plug-ins/script-fu/scripts/plug-in-compat.init
index dd68c2c24f..4eb8dd9198 100644
--- a/plug-ins/script-fu/scripts/plug-in-compat.init
+++ b/plug-ins/script-fu/scripts/plug-in-compat.init
@@ -22,3 +22,16 @@
               (- 255 (caddr dest-color-1)) (- 255 (caddr dest-color-2)))
   (gimp-levels layer HISTOGRAM-VALUE 0 255 1.0 255 0)
 )
+
+; since 3.0 a layer selection can be many,
+; so PDB methods to get selections return an int and a GObjectArray,
+; which in Scheme is a list containing a numeric and a vector.
+; and the word "active" changed to "selected".
+; Formerly, such PDB methods returned a list of one element, the ID of a layer.
+
+; A compatible replacement for gimp-image-get-active-layer.
+; This should be used only when you know the image has only one layer.
+; In other situations, you may break a contract to process all selected layers.
+(define (gimp-image-get-active-layer img)
+  (list (aref (cadr (gimp-image-get-selected-layers img)) 0))
+)
diff --git a/plug-ins/script-fu/scripts/test-sphere-v3.scm b/plug-ins/script-fu/scripts/test-sphere-v3.scm
new file mode 100644
index 0000000000..d9a696194b
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test-sphere-v3.scm
@@ -0,0 +1,174 @@
+#!/usr/bin/env gimp-script-fu-interpreter-3.0
+
+; v3 >>> Has shebang, is interpreter
+
+; This is a a test script to test Script-Fu parameter API.
+
+; For GIMP 3: uses GimpImageProcedure, GimpProcedureDialog, GimpConfig
+
+; See also test-sphere.scm, for GIMP 2, from which this is derived
+; Diffs marked with ; v3 >>>
+
+
+; v3 >>> signature of GimpImageProcedure
+; drawables is a vector
+(define (script-fu-test-sphere-v3
+                               image
+                               drawables
+                               radius
+                               light
+                               shadow
+                               bg-color
+                               sphere-color
+                               brush
+                               text
+                               multi-text
+                               pattern
+                               gradient
+                               gradient-reverse
+                               font
+                               size
+                               unused-palette
+                               unused-filename
+                               unused-orientation
+                               unused-interpolation
+                               unused-dirname
+                               unused-image
+                               unused-layer
+                               unused-channel
+                               unused-drawable)
+  (let* (
+        (width (* radius 3.75))
+        (height (* radius 2.5))
+        (img (car (gimp-image-new width height RGB)))
+        (drawable (car (gimp-layer-new img width height RGB-IMAGE
+                                       "Sphere Layer" 100 LAYER-MODE-NORMAL)))
+        (radians (/ (* light *pi*) 180))
+        (cx (/ width 2))
+        (cy (/ height 2))
+        (light-x (+ cx (* radius (* 0.6 (cos radians)))))
+        (light-y (- cy (* radius (* 0.6 (sin radians)))))
+        (light-end-x (+ cx (* radius (cos (+ *pi* radians)))))
+        (light-end-y (- cy (* radius (sin (+ *pi* radians)))))
+        (offset (* radius 0.1))
+        (text-extents (gimp-text-get-extents-fontname multi-text
+                                                      size PIXELS
+                                                      font))
+        (x-position (- cx (/ (car text-extents) 2)))
+        (y-position (- cy (/ (cadr text-extents) 2)))
+        (shadow-w 0)
+        (shadow-x 0)
+        )
+
+    (gimp-context-push)
+    (gimp-context-set-defaults)
+
+    (gimp-image-undo-disable img)
+    (gimp-image-insert-layer img drawable 0 0)
+    (gimp-context-set-foreground sphere-color)
+    (gimp-context-set-background bg-color)
+    (gimp-drawable-edit-fill drawable FILL-BACKGROUND)
+    (gimp-context-set-background '(20 20 20))
+
+    (if (and
+         (or (and (>= light 45) (<= light 75))
+             (and (<= light 135) (>= light 105)))
+         (= shadow TRUE))
+        (let ((shadow-w (* (* radius 2.5) (cos (+ *pi* radians))))
+              (shadow-h (* radius 0.5))
+              (shadow-x cx)
+              (shadow-y (+ cy (* radius 0.65))))
+          (if (< shadow-w 0)
+              (begin (set! shadow-x (+ cx shadow-w))
+                     (set! shadow-w (- shadow-w))))
+
+          (gimp-context-set-feather TRUE)
+          (gimp-context-set-feather-radius 7.5 7.5)
+          (gimp-image-select-ellipse img CHANNEL-OP-REPLACE shadow-x shadow-y shadow-w shadow-h)
+          (gimp-context-set-pattern pattern)
+          (gimp-drawable-edit-fill drawable FILL-PATTERN)))
+
+    (gimp-context-set-feather FALSE)
+    (gimp-image-select-ellipse img CHANNEL-OP-REPLACE (- cx radius) (- cy radius)
+                               (* 2 radius) (* 2 radius))
+
+    (gimp-context-set-gradient-fg-bg-rgb)
+    (gimp-drawable-edit-gradient-fill drawable
+                                     GRADIENT-RADIAL offset
+                                     FALSE 0 0
+                                     TRUE
+                                     light-x light-y
+                                     light-end-x light-end-y)
+
+    (gimp-selection-none img)
+
+    (gimp-image-select-ellipse img CHANNEL-OP-REPLACE 10 10 50 50)
+
+    (gimp-context-set-gradient gradient)
+    (gimp-context-set-gradient-reverse gradient-reverse)
+    (gimp-drawable-edit-gradient-fill drawable
+                                     GRADIENT-LINEAR offset
+                                     FALSE 0 0
+                                     TRUE
+                                     10 10
+                                     30 60)
+
+    (gimp-selection-none img)
+
+    (gimp-context-set-foreground '(0 0 0))
+    (gimp-floating-sel-anchor (car (gimp-text-fontname img drawable
+                                                       x-position y-position
+                                                       multi-text
+                                                       0 TRUE
+                                                       size PIXELS
+                                                       font)))
+
+    (gimp-image-undo-enable img)
+    (gimp-display-new img)
+
+    (gimp-context-pop)
+  )
+)
+
+; v3 >>> use script-fu-register-filter
+; v3 >>> menu item is v3, alongside old one
+; v3 >>> not yet localized
+
+(script-fu-register-filter "script-fu-test-sphere-v3"
+  "Sphere v3..."
+  "Test script-fu-register-filter and GimpProcedureDialog: needs 2 selected layers."
+  "Spencer Kimball, Sven Neumann"
+  "Spencer Kimball"
+  "1996, 1998"
+  "*"  ; image types any
+  SF-TWO-OR-MORE-DRAWABLE  ; v3 >>> additional argument
+  SF-ADJUSTMENT "Radius (in pixels)" (list 100 1 5000 1 10 0 SF-SPINNER)
+  SF-ADJUSTMENT "Lighting (degrees)" (list 45 0 360 1 10 1 SF-SLIDER)
+  SF-TOGGLE     "Shadow"             TRUE
+  SF-COLOR      "Background color"   "white"
+  SF-COLOR      "Sphere color"       "red"
+  SF-BRUSH      "Brush"              '("2. Hardness 100" 100 44 0)
+  SF-STRING     "Text"               "Tiny-Fu rocks!"
+  SF-TEXT       "Multi-line text"    "Hello,\nWorld!"
+  SF-PATTERN    "Pattern"            "Maple Leaves"
+  SF-GRADIENT   "Gradient"           "Deep Sea"
+  SF-TOGGLE     "Gradient reverse"   FALSE
+  SF-FONT       "Font"               "Agate"
+  SF-ADJUSTMENT "Font size (pixels)" '(50 1 1000 1 10 0 1)
+  SF-PALETTE    "Palette"            "Default"
+  SF-FILENAME   "Environment map"
+                (string-append gimp-data-directory
+                               "/scripts/images/beavis.jpg")
+  SF-OPTION     "Orientation"        '("Horizontal"
+                                       "Vertical")
+  SF-ENUM       "Interpolation"      '("InterpolationType" "linear")
+  SF-DIRNAME    "Output directory"   "/var/tmp/"
+  SF-IMAGE      "Image"              -1
+  SF-LAYER      "Layer"              -1
+  SF-CHANNEL    "Channel"            -1
+  SF-DRAWABLE   "Drawable"           -1
+  SF-VECTORS    "Vectors"            -1
+)
+
+(script-fu-menu-register "script-fu-test-sphere-v3"
+                         "<Image>/Filters/Development/Script-Fu/Test")
diff --git a/plug-ins/script-fu/scripts/test/always-fail/always-fail.scm 
b/plug-ins/script-fu/scripts/test/always-fail/always-fail.scm
new file mode 100644
index 0000000000..04a523d9c2
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/always-fail/always-fail.scm
@@ -0,0 +1,30 @@
+#!/usr/bin/env gimp-script-fu-interpreter-3.0
+
+; A script that always fails
+;
+; Setup: copy this file w/ executable permission, and its parent dir to /plug-ins
+; Example: to ~/.gimp-2.99/plug-ins/always-fail/always-fail.scm
+
+; Expect "Test>Always fail" in the menus
+; Expect when chosen, message on GIMP message bar "Failing"
+; Expect a dialog in GIMP app that requires an OK
+
+(define (script-fu-always-fail)
+  (begin
+    (gimp-message "Failing")
+    ; since last expression, the result, and should mean error
+    #f
+  )
+)
+
+(script-fu-register "script-fu-always-fail"
+  "Always fail"
+  _"Expect error dialog in Gimp, or PDB execution error when called by another"
+  "lkk"
+  "lkk"
+  "2022"
+  ""  ; requires no image
+  ; no arguments or dialog
+)
+
+(script-fu-menu-register "script-fu-always-fail" "<Image>/Test")
diff --git a/plug-ins/script-fu/scripts/test/call-always-fail/call-always-fail.scm 
b/plug-ins/script-fu/scripts/test/call-always-fail/call-always-fail.scm
new file mode 100644
index 0000000000..a72fec67d5
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/call-always-fail/call-always-fail.scm
@@ -0,0 +1,29 @@
+#!/usr/bin/env gimp-script-fu-interpreter-3.0
+
+; A script that calls a script that always fails
+;
+; Setup: copy this file w/ executable permission, and its parent dir to /plug-ins
+; Example: to ~/.gimp-2.99/plug-ins/always-fail/always-fail.scm
+
+; Expect "Test>Call always fail" in the menus
+; Expect when chosen, message on GIMP message bar "Failing" (from script-fu-always-fail)
+; Expect a dialog in GIMP app that requires an OK
+
+(define (script-fu-call-always-fail)
+  ; call a script that always fails
+  (script-fu-always-fail)
+  ; we have not checked the result and declaring the error on our own.
+  ; since this is the last expression, the #f from the call should propogate.
+)
+
+(script-fu-register "script-fu-call-always-fail"
+  "Call always fail"
+  _"Expect error dialog in Gimp, having concatenated error messages"
+  "lkk"
+  "lkk"
+  "2022"
+  ""  ; requires no image
+  ; no arguments or dialog
+)
+
+(script-fu-menu-register "script-fu-call-always-fail" "<Image>/Test")
diff --git a/po-script-fu/POTFILES.skip b/po-script-fu/POTFILES.skip
index 7c7f9c1ba3..b61a44621f 100644
--- a/po-script-fu/POTFILES.skip
+++ b/po-script-fu/POTFILES.skip
@@ -54,6 +54,7 @@ plug-ins/script-fu/scripts/test/test1/test3.scm
 plug-ins/script-fu/scripts/test/test4/test4.scm
 plug-ins/script-fu/scripts/test/test7/test7.scm
 plug-ins/script-fu/scripts/test/test8/test8.scm
+plug-ins/script-fu/scripts/clothify-v3.scm
 plug-ins/selection-to-path
 plug-ins/twain
 plug-ins/ui


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]