Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
GIMP 3.x Python Plugin Issue: Reliable Program Exit and Image Closing
#1
Hello GIMP Community,

I am facing an issue in a GIMP 3.0 Python plugin: reliably closing the program and the last active image when the sequential workflow is finished.

My script is running in the GUI Mode, and I need to perform two actions in order:
  1. Close the last open image container.
  2. Force GIMP to quit the application entirely.
The old methods from v2 do not work anymore and I am unsure if what I am trying to achieve is actually possible.

Here is my script so far (German annotations):

Code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import sys
import gi
gi.require_version('Gimp', '3.0')
gi.require_version('GimpUi', '3.0')
from gi.repository import Gimp, GimpUi, GObject, GLib, Gio
import os
import json
import tempfile
import traceback

# Pfad für die temporäre Konfigurationsdatei
CONFIG_PATH = os.path.join(tempfile.gettempdir(), 'gimp_spore_workflow_config.json')

class ExportAndNextWorkflow(Gimp.PlugIn):

   def do_query_procedures(self):
       return ['python-fu-export-and-next']

   def do_set_i18n(self, procname):
       return False, 'gimp30-python', None

   def do_create_procedure(self, name):
       procedure = Gimp.ImageProcedure.new(
           self, name,
           Gimp.PDBProcType.PLUGIN,
           self.run, None
       )

       procedure.set_image_types("*")
       procedure.set_sensitivity_mask(Gimp.ProcedureSensitivityMask.DRAWABLE)
       procedure.set_menu_label("1. Exportieren & Nächstes Bild")
       procedure.add_menu_path('<Image>/Auswahl/')

       procedure.set_documentation(
           "Exportiert die Auswahl, speichert W/H und lädt das nächste Bild.",
           "Exportiert die aktuelle Auswahl und bereitet den nächsten Schritt vor.",
           name
       )
       procedure.set_attribution("Author", "Author", "2024")

       return procedure

   # --- HILFSFUNKTIONEN ---

   def load_config(self):
       if os.path.exists(CONFIG_PATH):
           try:
               with open(CONFIG_PATH, 'r') as f:
                   return json.load(f)
           except:
               pass
       return None

   def save_config(self, width, height, current_path=None):
       data = {'width': int(width), 'height': int(height)}
       try:
           existing_data = self.load_config()
           if existing_data:
               data.update(existing_data)

           if current_path is not None:
               data['last_path'] = current_path

           with open(CONFIG_PATH, 'w') as f:
               json.dump(data, f, indent=4)
       except Exception as e:
           pass

   def get_next_file(self, current_path):
       directory = os.path.dirname(current_path)
       basename = os.path.basename(current_path)

       extensions = ('.jpg', '.jpeg', '.png', '.tif', '.tiff', '.xcf')
       files = [f for f in os.listdir(directory) if f.lower().endswith(extensions)]
       files.sort()

       try:
           index = files.index(basename)
           if index + 1 < len(files):
               return os.path.join(directory, files[index + 1])
       except ValueError:
           pass

       return None

   # --- HAUPT-RUN-FUNKTION ---

   def run(self, procedure, run_mode, image, n_drawables, drawables, config, run_data=None):

       # 0. Initialisierung & Auswahlprüfung
       selection = image.get_selection()
       success, non_empty, x1, y1, x2, y2 = selection.bounds(image)

       if not non_empty:
           error = GLib.Error.new_literal(Gimp.PlugIn.error_quark(), "Keine Auswahl vorhanden.", 0)
           return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, error)

       target_w = x2 - x1
       target_h = y2 - y1

       # 1. Pfad ermitteln
       current_path = None
       current_file = image.get_file()

       if current_file is not None:
           current_path = current_file.get_path()
       else:
           config_data = self.load_config()
           if config_data and 'last_path' in config_data:
               current_path = config_data['last_path']

       if current_path is None:
           error = GLib.Error.new_literal(Gimp.PlugIn.error_quark(),
                                           "Bild hat keinen Dateipfad (Konfiguration leer).", 0)
           return procedure.new_return_values(Gimp.PDBStatusType.EXECUTION_ERROR, error)

       directory = os.path.dirname(current_path)
       basename = os.path.splitext(os.path.basename(current_path))[0]

       # Zielordner für Exporte definieren
       target_dir = os.path.join(directory, "Auswahlen")

       # --- TEIL A: EXPORTIEREN ---

       try:
           # 2. Zielordner erstellen (falls nicht vorhanden)
           if not os.path.exists(target_dir):
               os.makedirs(target_dir)

           # 3. Nächste freie Export-Nummer finden (suche im Zielordner)
           number = 1
           while True:
               new_filename = f"[{number}] {basename}.jpg"
               new_filepath = os.path.join(target_dir, new_filename)
               if not os.path.exists(new_filepath):
                   break
               number += 1

           # 4. Auswahl kopieren, neues Bild erstellen, speichern
           drawable = image.get_layers()[-1]
           Gimp.edit_copy([drawable])
           new_image = Gimp.edit_paste_as_new_image()

           # Speichere direkt in den Auswahlen-Ordner
           new_file = Gio.file_new_for_path(new_filepath)
           Gimp.file_save(Gimp.RunMode.NONINTERACTIVE, new_image, new_file)
           new_image.delete()

           # 5. Auswahlgröße speichern (für Script 2)
           self.save_config(target_w, target_h)

       except Exception as e:
           if 'new_image' in locals():
               try: new_image.delete()
               except: pass

           error_msg = f"Fehler beim Export: {str(e)}\n{traceback.format_exc()}"
           error = GLib.Error.new_literal(Gimp.PlugIn.error_quark(), error_msg, 0)
           return procedure.new_return_values(Gimp.PDBStatusType.EXECUTION_ERROR, error)

       # --- TEIL B: NÄCHSTES BILD LADEN (MIT ENDPRÜFUNG) ---

       # 6. Nächstes Bild ermitteln
       next_path = self.get_next_file(current_path)

       if not next_path:
           # ENDE DER SEQUENZ ERREICHT!

           # Letzten Pfad zurücksetzen.
           self.save_config(target_w, target_h, current_path=None)

           # Aktuelles Bild aus der Anzeige entfernen
           image.delete()

           # GIMP 3.0 Methode zum Aufruf der PDB-Prozedur "gimp-quit"
           try:
               # Korrektur: Verwendung von Gimp.TYPE_INT anstelle von GObject.TYPE_INT
               Gimp.plug_in_manager_run(
                   "gimp-quit",
                   Gimp.RunMode.NONINTERACTIVE,
                   GObject.Value.new(Gimp.TYPE_INT, 0)
               )
           except Exception as e:
               Gimp.message(f"WARNUNG: GIMP 3.0 Quit-Aufruf fehlgeschlagen: {str(e)}")

           # Korrigierter Rückgabestatus:
           return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, None)

       try:
           # 7. Normaler Ablauf: Bild laden und Pfad speichern
           new_gfile = Gio.file_new_for_path(next_path)
           new_image = Gimp.file_load(Gimp.RunMode.NONINTERACTIVE, new_gfile)

           self.save_config(target_w, target_h, current_path=next_path)

           # 8. Anzeige erstellen und altes Bild schließen
           Gimp.Display.new(new_image)
           image.delete()
           Gimp.displays_flush()

       except Exception as e:
           error_msg = f"Fehler beim Wechseln: {str(e)}\n{traceback.format_exc()}"
           error = GLib.Error.new_literal(Gimp.PlugIn.error_quark(), error_msg, 0)
           return procedure.new_return_values(Gimp.PDBStatusType.EXECUTION_ERROR, error)

       # Korrigierter Rückgabestatus:
       return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, None)

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


Does anyone have an idea whether what I am trying to do is possible?
Reply


Forum Jump: