From 10245ea0560b9de198f6bdd190fd16f5515c0507 Mon Sep 17 00:00:00 2001 From: Variable <77773850+Variable-ind@users.noreply.github.com> Date: Sat, 27 May 2023 19:29:53 +0500 Subject: [PATCH] [Targeted for 0.11.0] Export options to extension (#869) * Added Export Options * some improvements * Improvements * linting * don't add format if it's already added * Fix some more bugs * fixed accidental change * add accidentally deleted signal --- src/Autoload/Export.gd | 53 +++++++++++++++++++++++++++++- src/Autoload/ExtensionsAPI.gd | 60 ++++++++++++++++++++++++++++++++++ src/UI/Dialogs/ExportDialog.gd | 10 +++--- 3 files changed, 118 insertions(+), 5 deletions(-) diff --git a/src/Autoload/Export.gd b/src/Autoload/Export.gd index 8afce1329..e965bf018 100644 --- a/src/Autoload/Export.gd +++ b/src/Autoload/Export.gd @@ -6,6 +6,12 @@ enum AnimationDirection { FORWARD = 0, BACKWARDS = 1, PING_PONG = 2 } # See file_format_string, file_format_description, and ExportDialog.gd enum FileFormat { PNG = 0, GIF = 1, APNG = 2 } +# list of animated formats +var animated_formats := [FileFormat.GIF, FileFormat.APNG] + +# A dictionary of custom exporter generators (Recieved from extensions) +var custom_exporter_generators := {} + var current_tab: int = ExportTab.IMAGE # All frames and their layers processed/blended into images var processed_images := [] # Image[] @@ -40,6 +46,19 @@ func _exit_tree() -> void: gif_export_thread.wait_to_finish() +func add_file_format(name: String) -> int: + var id = FileFormat.size() + FileFormat.merge({name: id}) + return id + + +func remove_file_format(id: int) -> void: + for key in Export.FileFormat.keys(): + if Export.FileFormat[key] == id: + Export.FileFormat.erase(key) + return + + func external_export(project := Global.current_project) -> void: process_data(project) export_processed_images(true, Global.export_dialog, project) @@ -191,6 +210,29 @@ func export_processed_images( scale_processed_images() + # override if a custom export is chosen + if project.file_format in custom_exporter_generators.keys(): + # Divert the path to the custom exporter instead + var custom_exporter: Object = custom_exporter_generators[project.file_format][0] + if custom_exporter.has_method("override_export"): + var result := true + var details := { + "processed_images": processed_images, + "durations": durations, + "export_dialog": export_dialog, + "export_paths": export_paths, + "project": project + } + if OS.get_name() != "HTML5" and is_single_file_format(project): + if gif_export_thread.is_active(): + gif_export_thread.wait_to_finish() + var error = gif_export_thread.start(custom_exporter, "override_export", details) + if error == OK: + result = gif_export_thread.wait_to_finish() + else: + result = custom_exporter.call("override_export", details) + return result + if is_single_file_format(project): var exporter: AImgIOBaseExporter if project.file_format == FileFormat.APNG: @@ -304,11 +346,16 @@ func file_format_string(format_enum: int) -> String: FileFormat.APNG: return ".apng" _: + # If a file format description is not found, try generating one + if custom_exporter_generators.has(format_enum): + return custom_exporter_generators[format_enum][1] return "" func file_format_description(format_enum: int) -> String: match format_enum: + # these are overrides + # (if they are not given, they will generate themselves based on the enum key name) FileFormat.PNG: return "PNG Image" FileFormat.GIF: @@ -316,13 +363,17 @@ func file_format_description(format_enum: int) -> String: FileFormat.APNG: return "APNG Image" _: + # If a file format description is not found, try generating one + for key in FileFormat.keys(): + if FileFormat[key] == format_enum: + return str(key.capitalize()) return "" func is_single_file_format(project := Global.current_project) -> bool: # True when exporting to .gif and .apng (and potentially video formats in the future) # False when exporting to .png, and other non-animated formats in the future - return project.file_format == FileFormat.GIF or project.file_format == FileFormat.APNG + return animated_formats.has(project.file_format) func create_export_path(multifile: bool, project: Project, frame: int = 0) -> String: diff --git a/src/Autoload/ExtensionsAPI.gd b/src/Autoload/ExtensionsAPI.gd index 14a14398e..4e23b78e1 100644 --- a/src/Autoload/ExtensionsAPI.gd +++ b/src/Autoload/ExtensionsAPI.gd @@ -8,6 +8,7 @@ var panel := PanelAPI.new() var theme := ThemeAPI.new() var tools := ToolAPI.new() var project := ProjectAPI.new() +var exports := ExportAPI.new() var signals := SignalsAPI.new() # This fail-safe below is designed to work ONLY if Pixelorama is launched in Godot Editor @@ -365,6 +366,65 @@ class ProjectAPI: print("cel at frame ", frame, ", layer ", layer, " is not a PixelCel") +class ExportAPI: + # gdlint: ignore=class-variable-name + var ExportTab := Export.ExportTab + + func add_export_option( + format_info: Dictionary, exporter_generator, tab := ExportTab.IMAGE, is_animated := true + ) -> int: + # separate enum name and file name + var extension = "" + var format_name = "" + if format_info.has("extension"): + extension = format_info["extension"] + if format_info.has("description"): + format_name = format_info["description"].to_upper().replace(" ", "_") + # change format name if another one uses the same name + for i in range(Export.FileFormat.size()): + var test_name = format_name + if i != 0: + test_name = str(test_name, "_", i) + if !Export.FileFormat.keys().has(test_name): + format_name = test_name + break + # add to FileFormat enum + var id := Export.FileFormat.size() + for i in Export.FileFormat.size(): # use an empty id if it's available + if !Export.FileFormat.values().has(i): + id = i + Export.FileFormat.merge({format_name: id}) + # add exporter generator + Export.custom_exporter_generators.merge({id: [exporter_generator, extension]}) + # add to animated (or not) + if is_animated: + Export.animated_formats.append(id) + # add to export dialog + match tab: + ExportTab.IMAGE: + Global.export_dialog.image_exports.append(id) + ExportTab.SPRITESHEET: + Global.export_dialog.spritesheet_exports.append(id) + _: # Both + Global.export_dialog.image_exports.append(id) + Global.export_dialog.spritesheet_exports.append(id) + ExtensionsApi.add_action("add_exporter") + return id + + func remove_export_option(id: int): + if Export.custom_exporter_generators.has(id): + # remove enum + Export.remove_file_format(id) + # remove exporter generator + Export.custom_exporter_generators.erase(id) + # remove from animated (or not) + Export.animated_formats.erase(id) + # add to export dialog + Global.export_dialog.image_exports.erase(id) + Global.export_dialog.spritesheet_exports.erase(id) + ExtensionsApi.remove_action("add_exporter") + + class SignalsAPI: # system to auto-adjust texture_changed to the "current cel" signal texture_changed diff --git a/src/UI/Dialogs/ExportDialog.gd b/src/UI/Dialogs/ExportDialog.gd index e21f6434a..72a722853 100644 --- a/src/UI/Dialogs/ExportDialog.gd +++ b/src/UI/Dialogs/ExportDialog.gd @@ -6,6 +6,10 @@ signal resume_export_function var preview_current_frame := 0 var preview_frames := [] +# allow custom exporters to be added +var image_exports := [Export.FileFormat.PNG, Export.FileFormat.GIF, Export.FileFormat.APNG] +var spritesheet_exports := [Export.FileFormat.PNG] + onready var tabs: Tabs = $VBoxContainer/Tabs onready var checker: ColorRect = $"%TransparentChecker" onready var previews: GridContainer = $"%Previews" @@ -164,11 +168,9 @@ func remove_previews() -> void: func set_file_format_selector() -> void: match Export.current_tab: Export.ExportTab.IMAGE: - _set_file_format_selector_suitable_file_formats( - [Export.FileFormat.PNG, Export.FileFormat.GIF, Export.FileFormat.APNG] - ) + _set_file_format_selector_suitable_file_formats(image_exports) Export.ExportTab.SPRITESHEET: - _set_file_format_selector_suitable_file_formats([Export.FileFormat.PNG]) + _set_file_format_selector_suitable_file_formats(spritesheet_exports) # Updates the suitable list of file formats. First is preferred.