initial upload

This commit is contained in:
Torsten 2025-09-26 08:16:54 +02:00
parent 1f4922ce56
commit 29a254fbbb
397 changed files with 13623 additions and 0 deletions

4
hexmapper/.editorconfig Normal file
View file

@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

64
hexmapper/Testscene.tscn Normal file
View file

@ -0,0 +1,64 @@
[gd_scene load_steps=7 format=3 uid="uid://hlrngp1n24kg"]
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_gbjqf"]
load_path = "res://.godot/imported/BoulderHills1.png-3ee92d43833962adc5c138e74c73b76b.ctex"
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_ioj5u"]
load_path = "res://.godot/imported/BoulderHills1.png-3ee92d43833962adc5c138e74c73b76b.ctex"
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_g5b6y"]
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_slrw0"]
load_path = "res://.godot/imported/Grass1.png-95db52ec56c98c7586df38eac38ccfca.ctex"
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_af0e7"]
load_path = "res://.godot/imported/Grass2.png-d618be34b2012c09fbdb1c5ef19013bf.ctex"
[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_8oe3q"]
load_path = "res://.godot/imported/Grass3.png-f00b17931a597c8e0f903ee630d4c100.ctex"
[node name="Node2D" type="Node2D"]
[node name="BoulderHills1" type="Sprite2D" parent="."]
position = Vector2(487, 413)
texture = SubResource("CompressedTexture2D_gbjqf")
[node name="BoulderHills1" type="Sprite2D" parent="BoulderHills1"]
position = Vector2(474, 412)
texture = SubResource("CompressedTexture2D_gbjqf")
[node name="BoulderHills2" type="Sprite2D" parent="."]
position = Vector2(540, 448)
texture = SubResource("CompressedTexture2D_gbjqf")
[node name="BoulderHills3" type="Sprite2D" parent="."]
position = Vector2(434, 449)
texture = SubResource("CompressedTexture2D_gbjqf")
[node name="BoulderHills4" type="Sprite2D" parent="."]
position = Vector2(487, 484)
texture = SubResource("CompressedTexture2D_gbjqf")
[node name="Brambles1" type="Sprite2D" parent="."]
position = Vector2(540, 377)
texture = SubResource("CompressedTexture2D_ioj5u")
[node name="Church5" type="Sprite2D" parent="."]
position = Vector2(432, 381)
texture = SubResource("CompressedTexture2D_g5b6y")
[node name="Grass1" type="Sprite2D" parent="."]
position = Vector2(485, 343)
texture = SubResource("CompressedTexture2D_slrw0")
[node name="Grass2" type="Sprite2D" parent="."]
position = Vector2(430, 309)
texture = SubResource("CompressedTexture2D_af0e7")
[node name="Grass3" type="Sprite2D" parent="."]
position = Vector2(377, 346)
texture = SubResource("CompressedTexture2D_8oe3q")
[node name="Grass4" type="Sprite2D" parent="."]
position = Vector2(379, 418)
texture = SubResource("CompressedTexture2D_8oe3q")

View file

@ -0,0 +1,3 @@
extends Node
@export var settings:Settings = Settings.new()

View file

@ -0,0 +1 @@
uid://dd76wxdxcm1v0

View file

@ -0,0 +1,306 @@
@tool
extends RefCounted
var _config = preload("../config/config.gd").new()
#
# Output:
# {
# "data_file": file path to the json file
# "sprite_sheet": file path to the raw image file
# }
func export_file(file_name: String, output_folder: String, options: Dictionary) -> Dictionary:
var exception_pattern = options.get('exception_pattern', "")
var only_visible_layers = options.get('only_visible_layers', false)
var output_name = file_name if options.get('output_filename') == "" else options.get('output_filename', file_name)
var first_frame_only = options.get("first_frame_only", false)
var basename = _get_file_basename(output_name)
var output_dir = ProjectSettings.globalize_path(output_folder)
var data_file = "%s/%s.json" % [output_dir, basename]
var sprite_sheet = "%s/%s.png" % [output_dir, basename]
var output = []
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
if not only_visible_layers:
arguments.push_front("--all-layers")
if first_frame_only:
arguments.push_front("'[0, 0]'")
arguments.push_front("--frame-range")
_add_sheet_type_arguments(arguments, options)
_add_ignore_layer_arguments(file_name, arguments, exception_pattern)
var local_sprite_sheet_path = ProjectSettings.localize_path(sprite_sheet)
var is_new = not ResourceLoader.exists(local_sprite_sheet_path)
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('aseprite: failed to export spritesheet')
printerr(output)
return {}
return {
"data_file": ProjectSettings.localize_path(data_file),
"sprite_sheet": local_sprite_sheet_path,
"is_first_import": is_new,
}
func export_layers(file_name: String, output_folder: String, options: Dictionary) -> Array:
var exception_pattern = options.get('exception_pattern', "")
var only_visible_layers = options.get('only_visible_layers', false)
var basename = _get_file_basename(file_name)
var layers = list_layers(file_name, only_visible_layers)
var exception_regex = _compile_regex(exception_pattern)
var output = []
for layer in layers:
if layer != "" and (not exception_regex or exception_regex.search(layer) == null):
output.push_back(export_file_with_layers(file_name, [layer], output_folder, options))
return output
func export_file_with_layers(file_name: String, layer_names: Array, output_folder: String, options: Dictionary) -> Dictionary:
var output_prefix = options.get('output_filename', "").strip_edges()
var output_dir = output_folder.replace("res://", "./").strip_edges()
var base_output_path = "%s/%s%s" % [output_dir, output_prefix, layer_names[0] if layer_names.size() == 1 else ""]
var data_file = "%s.json" % base_output_path
var sprite_sheet = "%s.png" % base_output_path
var first_frame_only = options.get("first_frame_only", false)
var output = []
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
for layer_name in layer_names:
arguments.push_front(layer_name)
arguments.push_front("--layer")
if first_frame_only:
arguments.push_front("'[0, 0]'")
arguments.push_front("--frame-range")
_add_sheet_type_arguments(arguments, options)
var local_sprite_sheet_path = ProjectSettings.localize_path(sprite_sheet)
var is_new = not ResourceLoader.exists(local_sprite_sheet_path)
var exit_code = _execute(arguments, output)
if exit_code != 0:
print('aseprite: failed to export layer spritesheet')
print(output)
return {}
return {
"data_file": ProjectSettings.localize_path(data_file),
"sprite_sheet": local_sprite_sheet_path,
"is_first_import": is_new,
}
func _add_ignore_layer_arguments(file_name: String, arguments: Array, exception_pattern: String):
var layers = _get_exception_layers(file_name, exception_pattern)
if not layers.is_empty():
for l in layers:
arguments.push_front(l)
arguments.push_front('--ignore-layer')
func _add_sheet_type_arguments(arguments: Array, options : Dictionary):
var sheet_type = options.get("sheet_type", "packed")
var item_count = options.get("sheet_columns", 0)
if (sheet_type == "columns" or sheet_type == "rows") and item_count == 0:
sheet_type = "packed"
elif options.get("sheet_merge_duplicates", true):
arguments.push_back("--merge-duplicates")
if sheet_type == "columns":
arguments.push_back("--sheet-columns")
arguments.push_back(item_count)
else:
arguments.push_back("--sheet-type")
arguments.push_back(sheet_type)
func _get_exception_layers(file_name: String, exception_pattern: String) -> Array:
var layers = list_layers(file_name)
var regex = _compile_regex(exception_pattern)
if regex == null:
return []
var exception_layers = []
for layer in layers:
if regex.search(layer) != null:
exception_layers.push_back(layer)
return exception_layers
func list_valid_layers(file_name: String, exception_pattern: String = "", show_only_visible: bool = false) -> Array:
var layers = list_layers(file_name, show_only_visible)
var exception_regex = _compile_regex(exception_pattern)
var output = []
for layer in layers:
if layer != "" and (not exception_regex or exception_regex.search(layer) == null):
output.push_back(layer)
return output
func list_layers(file_name: String, only_visible = false) -> Array:
var output = []
var arguments = ["-b", "--list-layers", file_name]
if not only_visible:
arguments.push_front("--all-layers")
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('aseprite: failed listing layers')
printerr(output)
return []
if output.is_empty():
return output
var raw = output[0].split('\n')
var sanitized = []
for s in raw:
sanitized.append(s.strip_edges())
return sanitized
func list_slices(file_name: String) -> Array:
var output = []
var arguments = ["-b", "--list-slices", file_name]
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('aseprite: failed listing slices')
printerr(output)
return []
if output.is_empty():
return output
var raw = output[0].split('\n')
var sanitized = []
for s in raw:
sanitized.append(s.strip_edges())
return sanitized
func _export_command_common_arguments(source_name: String, data_path: String, spritesheet_path: String) -> Array:
return [
"-b",
"--list-tags",
"--list-slices",
"--data",
data_path,
"--format",
"json-array",
"--sheet",
spritesheet_path,
source_name
]
func _execute(arguments, output):
return OS.execute(_aseprite_command(), arguments, output, true, true)
func _aseprite_command() -> String:
return _config.is_command_or_control_pressed()
func _get_file_basename(file_path: String) -> String:
return file_path.get_file().trim_suffix('.%s' % file_path.get_extension())
func _compile_regex(pattern):
if pattern == "":
return
var rgx = RegEx.new()
if rgx.compile(pattern) == OK:
return rgx
printerr('exception regex error')
func test_command():
var exit_code = OS.execute(_aseprite_command(), ['--version'], [], true)
return exit_code == 0
func is_valid_spritesheet(content):
return content.has("frames") and content.has("meta") and content.meta.has('image')
func get_content_frames(content):
return content.frames if typeof(content.frames) == TYPE_ARRAY else content.frames.values()
func get_slice_rect(content: Dictionary, slice_name: String) -> Variant:
if not content.has("meta") or not content.meta.has("slices"):
return null
for slice in content.meta.slices:
if slice.name == slice_name:
if slice.keys.size() > 0:
var p = slice.keys[0].bounds
return Rect2(p.x, p.y, p.w, p.h)
return null
##
## Exports tileset layers
##
## Return (dictionary):
## data_file: path to aseprite generated JSON file
## sprite_sheet: localized path to spritesheet file
func export_tileset_texture(file_name: String, output_folder: String, options: Dictionary) -> Dictionary:
var exception_pattern = options.get('exception_pattern', "")
var only_visible_layers = options.get('only_visible_layers', false)
var output_name = file_name if options.get('output_filename') == "" else options.get('output_filename', file_name)
var basename = _get_file_basename(output_name)
var output_dir = ProjectSettings.globalize_path(output_folder)
var data_path = "%s/%s.json" % [output_dir, basename]
var sprite_sheet = "%s/%s.png" % [output_dir, basename]
var output = []
var arguments = [
"-b",
"--export-tileset",
"--data",
data_path,
"--format",
"json-array",
"--sheet",
sprite_sheet,
file_name
]
if not only_visible_layers:
arguments.push_front("--all-layers")
_add_sheet_type_arguments(arguments, options)
_add_ignore_layer_arguments(file_name, arguments, exception_pattern)
var exit_code = _execute(arguments, output)
if exit_code != 0:
printerr('aseprite: failed to export spritesheet')
printerr(output)
return {}
return {
"data_file": ProjectSettings.localize_path(data_path),
"sprite_sheet": ProjectSettings.localize_path(sprite_sheet)
}

View file

@ -0,0 +1 @@
uid://dlagp0c0t7o6x

View file

@ -0,0 +1,146 @@
@tool
extends RefCounted
var result_code = preload("../config/result_codes.gd")
var _aseprite = preload("aseprite.gd").new()
enum {
FILE_EXPORT_MODE,
LAYERS_EXPORT_MODE
}
##
## Generate Aseprite spritesheet and data files for source.
##
## Options:
## output_folder (string)
## output_filename (string, optional)
## export_mode (FILE_EXPORT_MODE, LAYERS_EXPORT_MODE) default: FILE_EXPORT_MODE
## exception_pattern (string, optional)
## only_visible_layers (boolean, optional)
##
## Return:
## Array
## Dictionary
## sprite_sheet: sprite sheet path
## data_file: json file path
##
func generate_aseprite_files(source_file: String, options: Dictionary):
var check = _initial_checks(source_file, options)
if check != result_code.SUCCESS:
return result_code.error(check)
match options.get('export_mode', FILE_EXPORT_MODE):
FILE_EXPORT_MODE:
var output = _aseprite.export_file(source_file, options.output_folder, options)
if output.is_empty():
return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED)
return result_code.result([output])
LAYERS_EXPORT_MODE:
var output = _aseprite.export_layers(source_file, options.output_folder, options)
if output.is_empty():
return result_code.error(result_code.ERR_NO_VALID_LAYERS_FOUND)
return result_code.result(output)
_:
return result_code.error(result_code.ERR_UNKNOWN_EXPORT_MODE)
##
## Generate Aseprite spritesheet and data file for source.
##
## Options:
## output_folder (string)
## output_filename (string, optional)
## layer (string, optional)
## exception_pattern (string, optional)
## only_visible_layers (boolean, optional)
##
## Return:
## Dictionary
## sprite_sheet: sprite sheet path
## data_file: json file path
##
func generate_aseprite_file(source_file: String, options: Dictionary) -> Dictionary:
var check = _initial_checks(source_file, options)
if check != result_code.SUCCESS:
return result_code.error(check)
var output
if options.get("layer") != null and options.get("layer") != "":
output = _aseprite.export_file_with_layers(source_file, [options.layer], options.output_folder, options)
elif options.get("layers", []).size() > 0:
output = _aseprite.export_file_with_layers(source_file, options.layers, options.output_folder, options)
else:
output = _aseprite.export_file(source_file, options.output_folder, options)
if output.is_empty():
return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED)
return result_code.result(output)
##
## Generate a spritesheet with all tilesets in the file
##
## Options:
## exception_pattern (string)
## only_visible_layers (boolean)
## output_filename (string)
## output_folder (string)
##
## Return:
## Dictionary
## sprite_sheet: sprite sheet path
## data_file: json file path
##
func generate_tileset_files(source_file: String, options = {}) -> Dictionary:
var check = _initial_checks(source_file, options)
if check != result_code.SUCCESS:
return result_code.error(check)
var output = _aseprite.export_tileset_texture(source_file, options.output_folder, options)
if output.is_empty():
return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED)
return result_code.result(output)
##
## Perform initial source file and output folder checks
##
func _initial_checks(source: String, options: Dictionary) -> int:
if not _aseprite.test_command():
return result_code.ERR_ASEPRITE_CMD_NOT_FOUND
if not FileAccess.file_exists(source):
return result_code.ERR_SOURCE_FILE_NOT_FOUND
if not DirAccess.dir_exists_absolute(options.output_folder):
return result_code.ERR_OUTPUT_FOLDER_NOT_FOUND
return result_code.SUCCESS
##
## Load Aseprite source data file and fails if the
## content is not valid
##
func load_json_content(source_file: String) -> Dictionary:
var file = FileAccess.open(source_file, FileAccess.READ)
if file == null:
return result_code.error(FileAccess.get_open_error())
var test_json_conv = JSON.new()
test_json_conv.parse(file.get_as_text())
var content = test_json_conv.get_data()
if not _aseprite.is_valid_spritesheet(content):
return result_code.error(result_code.ERR_INVALID_ASEPRITE_SPRITESHEET)
return result_code.result(content)

View file

@ -0,0 +1 @@
uid://cqqxrajqhsbix

View file

@ -0,0 +1,212 @@
@tool
extends RefCounted
# GLOBAL SETTINGS
const _CONFIG_SECTION_KEY = 'aseprite'
const _COMMAND_KEY = 'aseprite/general/command_path'
# PROJECT SETTINGS
# animation import defaults
const _DEFAULT_EXCLUSION_PATTERN_KEY = 'aseprite/animation/layers/exclusion_pattern'
const _DEFAULT_ONLY_VISIBLE_LAYERS = 'aseprite/animation/layers/only_include_visible_layers_by_default'
const _DEFAULT_LOOP_EX_PREFIX = '_'
const _LOOP_ENABLED = 'aseprite/animation/loop/enabled'
const _LOOP_EXCEPTION_PREFIX = 'aseprite/animation/loop/exception_prefix'
# cleanup
const _REMOVE_SOURCE_FILES_KEY = 'aseprite/import/cleanup/remove_json_file'
const _SET_VISIBLE_TRACK_AUTOMATICALLY = 'aseprite/import/cleanup/automatically_hide_sprites_not_in_animation'
# automatic importer
const _IMPORTER_ENABLE_KEY = 'aseprite/import/import_plugin/enable_automatic_importer'
const _DEFAULT_IMPORTER_KEY = 'aseprite/import/import_plugin/default_automatic_importer'
const IMPORTER_SPRITEFRAMES_NAME = "SpriteFrames"
const IMPORTER_SPRITEFRAMES_SPLIT_NAME = "SpriteFrames (Split By Layer)"
const IMPORTER_NOOP_NAME = "No Import"
const IMPORTER_TILESET_TEXTURE_NAME = "Tileset Texture"
const IMPORTER_STATIC_TEXTURE_NAME = "Static Texture"
const IMPORTER_STATIC_TEXTURE_SPLIT_NAME = "Static Texture (Split By Layer)"
# wizard history
const _WIZARD_HISTORY = "wizard_history"
const _HISTORY_MAX_ENTRIES = 'aseprite/wizard/history/max_history_entries'
const _HISTORY_DEFAULT_MAX_ENTRIES = 100
# SpriteFrames import last config
const _STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG = "standalone_sf_last_import_cfg"
var _editor_settings: EditorSettings = EditorInterface.get_editor_settings()
#######################################################
# GLOBAL CONFIGS
######################################################
func default_command() -> String:
match OS.get_name():
"Windows":
return "C:\\\\Steam\\steamapps\\common\\Aseprite\\aseprite.exe"
"macOS":
return "/Applications/Aseprite.app/Contents/MacOS/aseprite"
_:
return 'aseprite'
func is_command_or_control_pressed() -> String:
var command = _editor_settings.get(_COMMAND_KEY) if _editor_settings.has_setting(_COMMAND_KEY) else ""
return command if command != "" else default_command()
#######################################################
# PROJECT SETTINGS
######################################################
# remove this config in the next major version
func is_importer_enabled() -> bool:
return _get_project_setting(_IMPORTER_ENABLE_KEY, false)
func get_default_importer() -> String:
return _get_project_setting(_DEFAULT_IMPORTER_KEY, IMPORTER_SPRITEFRAMES_NAME if is_importer_enabled() else IMPORTER_NOOP_NAME)
func should_remove_source_files() -> bool:
return _get_project_setting(_REMOVE_SOURCE_FILES_KEY, true)
func is_default_animation_loop_enabled() -> bool:
return _get_project_setting(_LOOP_ENABLED, true)
func get_animation_loop_exception_prefix() -> String:
return _get_project_setting(_LOOP_EXCEPTION_PREFIX, _DEFAULT_LOOP_EX_PREFIX)
func get_default_exclusion_pattern() -> String:
return _get_project_setting(_DEFAULT_EXCLUSION_PATTERN_KEY, "")
func should_include_only_visible_layers_by_default() -> bool:
return _get_project_setting(_DEFAULT_ONLY_VISIBLE_LAYERS, false)
func get_history_max_entries() -> int:
return _get_project_setting(_HISTORY_MAX_ENTRIES, _HISTORY_DEFAULT_MAX_ENTRIES)
func get_import_history() -> Array:
return get_plugin_metadata(_WIZARD_HISTORY, [])
func is_set_visible_track_automatically_enabled() -> bool:
return _get_project_setting(_SET_VISIBLE_TRACK_AUTOMATICALLY, false)
func save_import_history(history: Array):
set_plugin_metadata(_WIZARD_HISTORY, history)
#=========================================================
# IMPORT CONFIGS
#=========================================================
## Return config for last import done via standalone SpriteFrames import dock
func get_standalone_spriteframes_last_import_config() -> Dictionary:
return get_plugin_metadata(_STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG, {})
## Set config for last import done via standalone SpriteFrames import dock
func set_standalone_spriteframes_last_import_config(data: Dictionary) -> void:
set_plugin_metadata(_STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG, data)
func clear_standalone_spriteframes_last_import_config() -> void:
set_plugin_metadata(_STANDALONE_SPRITEFRAMES_LAST_IMPORT_CFG, {})
func get_plugin_metadata(key: String, default: Variant = null) -> Variant:
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, key, default)
func set_plugin_metadata(key: String, data: Variant):
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, key, data)
#######################################################
# INITIALIZATION
######################################################
func initialize_project_settings():
_initialize_project_cfg(_DEFAULT_EXCLUSION_PATTERN_KEY, "", TYPE_STRING)
_initialize_project_cfg(_DEFAULT_ONLY_VISIBLE_LAYERS, false, TYPE_BOOL)
_initialize_project_cfg(_LOOP_ENABLED, true, TYPE_BOOL)
_initialize_project_cfg(_LOOP_EXCEPTION_PREFIX, _DEFAULT_LOOP_EX_PREFIX, TYPE_STRING)
_initialize_project_cfg(_REMOVE_SOURCE_FILES_KEY, true, TYPE_BOOL)
_initialize_project_cfg(
_DEFAULT_IMPORTER_KEY,
IMPORTER_SPRITEFRAMES_NAME if is_importer_enabled() else IMPORTER_NOOP_NAME,
TYPE_STRING,
PROPERTY_HINT_ENUM,
",".join([
IMPORTER_NOOP_NAME,
IMPORTER_SPRITEFRAMES_NAME,
IMPORTER_SPRITEFRAMES_SPLIT_NAME,
IMPORTER_TILESET_TEXTURE_NAME,
IMPORTER_STATIC_TEXTURE_NAME,
IMPORTER_STATIC_TEXTURE_SPLIT_NAME
])
)
_initialize_project_cfg(_HISTORY_MAX_ENTRIES, _HISTORY_DEFAULT_MAX_ENTRIES, TYPE_INT)
_initialize_project_cfg(_SET_VISIBLE_TRACK_AUTOMATICALLY, false, TYPE_BOOL)
ProjectSettings.save()
_initialize_editor_cfg(_COMMAND_KEY, default_command(), TYPE_STRING)
func clear_project_settings():
var _all_settings = [
_DEFAULT_EXCLUSION_PATTERN_KEY,
_LOOP_ENABLED,
_LOOP_EXCEPTION_PREFIX,
_REMOVE_SOURCE_FILES_KEY,
_DEFAULT_IMPORTER_KEY,
_HISTORY_MAX_ENTRIES,
_SET_VISIBLE_TRACK_AUTOMATICALLY,
_DEFAULT_ONLY_VISIBLE_LAYERS,
]
for key in _all_settings:
ProjectSettings.clear(key)
ProjectSettings.save()
func _initialize_project_cfg(key: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE, hint_string = null):
if not ProjectSettings.has_setting(key):
ProjectSettings.set(key, default_value)
ProjectSettings.set_initial_value(key, default_value)
ProjectSettings.add_property_info({
"name": key,
"type": type,
"hint": hint,
"hint_string": hint_string,
})
func _get_project_setting(key: String, default_value):
if not ProjectSettings.has_setting(key):
return default_value
var p = ProjectSettings.get(key)
return p if p != null else default_value
func _initialize_editor_cfg(key: String, default_value, type: int, hint: int = PROPERTY_HINT_NONE):
if not _editor_settings.has_setting(key):
_editor_settings.set(key, default_value)
_editor_settings.set_initial_value(key, default_value, false)
_editor_settings.add_property_info({
"name": key,
"type": type,
"hint": hint,
})

View file

@ -0,0 +1 @@
uid://brhdsbpp7c56f

View file

@ -0,0 +1,29 @@
@tool
extends PopupPanel
var _config = preload("./config.gd").new()
@onready var _aseprite_command_field = $MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer/aseprite_command
@onready var _version_label = $MarginContainer/VBoxContainer/VBoxContainer/version_found
func _ready():
_aseprite_command_field.text = _config.is_command_or_control_pressed()
_version_label.modulate.a = 0
func _on_close_button_up():
self.hide()
func _on_test_pressed():
var output = []
if _test_command(output):
_version_label.text = "%s found." % "\n".join(PackedStringArray(output)).strip_edges()
else:
_version_label.text = "Command not found."
_version_label.modulate.a = 1
func _test_command(output):
var exit_code = OS.execute(_aseprite_command_field.text, ['--version'], output, true, true)
return exit_code == 0

View file

@ -0,0 +1 @@
uid://d1tuygo3j4fru

View file

@ -0,0 +1,80 @@
[gd_scene load_steps=2 format=3 uid="uid://d0whlywijwa6s"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/config/config_dialog.gd" id="1"]
[node name="config_dialog" type="PopupPanel"]
title = "Aseprite Wizard Config"
size = Vector2i(624, 236)
visible = true
unresizable = false
borderless = false
min_size = Vector2i(624, 236)
content_scale_mode = 1
script = ExtResource("1")
[node name="MarginContainer" type="MarginContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = 4.0
offset_top = 4.0
offset_right = -532.0
offset_bottom = -416.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
theme_override_constants/margin_left = 10
theme_override_constants/margin_top = 10
theme_override_constants/margin_right = 10
theme_override_constants/margin_bottom = 10
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
text = "This configuration moved.
- To edit the aseprite command path, go to Editor > Editor Settings > Aseprite.
- To edit project specific settings, go to Project > Project Settings > Aseprite."
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Aseprite Command" type="Label" parent="MarginContainer/VBoxContainer/VBoxContainer"]
layout_mode = 2
tooltip_text = "Define the path for Aseprite command"
mouse_filter = 1
text = "Aseprite Command Path"
[node name="HBoxContainer" type="HBoxContainer" parent="MarginContainer/VBoxContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="aseprite_command" type="LineEdit" parent="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
editable = false
caret_blink = true
[node name="test" type="Button" parent="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "Test"
[node name="version_found" type="Label" parent="MarginContainer/VBoxContainer/VBoxContainer"]
modulate = Color(1, 1, 1, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "Aseprite version found"
[node name="VBoxContainer2" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
alignment = 2
[node name="close" type="Button" parent="MarginContainer/VBoxContainer/VBoxContainer2"]
layout_mode = 2
text = "Close"
[connection signal="pressed" from="MarginContainer/VBoxContainer/VBoxContainer/HBoxContainer/test" to="." method="_on_test_pressed"]
[connection signal="button_up" from="MarginContainer/VBoxContainer/VBoxContainer2/close" to="." method="_on_close_button_up"]

View file

@ -0,0 +1,37 @@
@tool
extends RefCounted
const SUCCESS = 0
const ERR_ASEPRITE_CMD_NOT_FOUND = 1
const ERR_SOURCE_FILE_NOT_FOUND = 2
const ERR_OUTPUT_FOLDER_NOT_FOUND = 3
const ERR_ASEPRITE_EXPORT_FAILED = 4
const ERR_UNKNOWN_EXPORT_MODE = 5
const ERR_NO_VALID_LAYERS_FOUND = 6
const ERR_INVALID_ASEPRITE_SPRITESHEET = 7
static func get_error_message(code: int):
match code:
ERR_ASEPRITE_CMD_NOT_FOUND:
return "Aseprite command failed. Please, check if the right command is in your PATH or configured through \"Editor > Editor Settings > Aseprite > General > Command Path\"."
ERR_SOURCE_FILE_NOT_FOUND:
return "source file does not exist"
ERR_OUTPUT_FOLDER_NOT_FOUND:
return "output location does not exist"
ERR_ASEPRITE_EXPORT_FAILED:
return "unable to import file"
ERR_INVALID_ASEPRITE_SPRITESHEET:
return "aseprite generated bad data file"
ERR_NO_VALID_LAYERS_FOUND:
return "no valid layers found"
_:
return "import failed with code %d" % code
static func error(error_code: int):
return { "code": error_code, "content": null, "is_ok": false }
static func result(result):
return { "code": SUCCESS, "content": result, "is_ok": true }

View file

@ -0,0 +1 @@
uid://blrm6kct24uus

View file

@ -0,0 +1,41 @@
@tool
extends RefCounted
const WIZARD_CONFIG_META_NAME = "_aseprite_wizard_config_"
const WIZARD_INTERFACE_CONFIG_META_NAME = "_aseprite_wizard_interface_config_"
const SOURCE_FILE_HASH_META_NAME = "_aseprite_wizard_source_file_hash_"
static func load_config(node: Object):
if node.has_meta(WIZARD_CONFIG_META_NAME):
return node.get_meta(WIZARD_CONFIG_META_NAME)
return {}
static func save_config(node: Object, cfg: Dictionary):
node.set_meta(WIZARD_CONFIG_META_NAME, cfg)
static func has_config(node: Object) -> bool:
return node.has_meta(WIZARD_CONFIG_META_NAME)
static func load_interface_config(node: Node, default: Dictionary = {}) -> Dictionary:
if node.has_meta(WIZARD_INTERFACE_CONFIG_META_NAME):
return node.get_meta(WIZARD_INTERFACE_CONFIG_META_NAME)
return default
static func save_interface_config(node: Node, cfg:Dictionary) -> void:
node.set_meta(WIZARD_INTERFACE_CONFIG_META_NAME, cfg)
static func set_source_hash(node: Object, hash: String) -> void:
node.set_meta(SOURCE_FILE_HASH_META_NAME, hash)
static func get_source_hash(node: Object) -> String:
if node.has_meta(SOURCE_FILE_HASH_META_NAME):
return node.get_meta(SOURCE_FILE_HASH_META_NAME)
return ""

View file

@ -0,0 +1 @@
uid://b5c7fprurl46l

View file

@ -0,0 +1,338 @@
@tool
extends "../base_sprite_resource_creator.gd"
const wizard_config = preload("../../config/wizard_config.gd")
var _DEFAULT_ANIMATION_LIBRARY = "" # GLOBAL
func create_animations(target_node: Node, player: AnimationPlayer, aseprite_files: Dictionary, options: Dictionary):
var result = _import(target_node, player, aseprite_files, options)
if result != result_code.SUCCESS:
printerr(result_code.get_error_message(result))
func _import(target_node: Node, player: AnimationPlayer, aseprite_files: Dictionary, options: Dictionary):
var source_file = aseprite_files.data_file
var sprite_sheet = aseprite_files.sprite_sheet
var data = _aseprite_file_exporter.load_json_content(source_file)
if not data.is_ok:
return data.code
var content = data.content
var context = {}
if target_node is CanvasItem:
target_node.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
else:
target_node.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
var texture := _load_texture(sprite_sheet, options.get("should_create_portable_texture", false))
_setup_texture(target_node, texture, content, context, options.slice != "")
var result = _configure_animations(target_node, player, content, context, options)
if result != result_code.SUCCESS:
return result
return _cleanup_animations(target_node, player, content, options)
func _load_texture(sprite_sheet: String, use_portable_tex: bool) -> Texture2D:
if use_portable_tex:
return _load_compressed_texture(sprite_sheet)
var texture = ResourceLoader.load(sprite_sheet, 'Image', ResourceLoader.CACHE_MODE_IGNORE)
texture.take_over_path(sprite_sheet)
return texture
func _configure_animations(target_node: Node, player: AnimationPlayer, content: Dictionary, context: Dictionary, options: Dictionary):
var frames = _aseprite.get_content_frames(content)
var slice_rect = null
if options.slice != "":
options["slice_rect"] = _aseprite.get_slice_rect(content, options.slice)
if not player.has_animation_library(_DEFAULT_ANIMATION_LIBRARY):
player.add_animation_library(_DEFAULT_ANIMATION_LIBRARY, AnimationLibrary.new())
if content.meta.has("frameTags") and content.meta.frameTags.size() > 0:
var result = result_code.SUCCESS
for tag in content.meta.frameTags:
var selected_frames = frames.slice(tag.from, tag.to + 1)
result = _add_animation_frames(target_node, player, tag.name, selected_frames, context, options, tag.direction, int(tag.get("repeat", -1)))
if result != result_code.SUCCESS:
break
return result
else:
return _add_animation_frames(target_node, player, "default", frames, context, options)
func _add_animation_frames(target_node: Node, player: AnimationPlayer, anim_name: String, frames: Array, context: Dictionary, options: Dictionary, direction = 'forward', repeat = -1):
var animation_name = anim_name
var library_name = _DEFAULT_ANIMATION_LIBRARY
var is_loopable = _config.is_default_animation_loop_enabled()
var slice_rect = options.get("slice_rect")
var is_importing_slice: bool = slice_rect != null
var anim_tokens := anim_name.split("/")
if anim_tokens.size() > 2:
push_error("Invalid animation name: %s" % animation_name)
return
elif anim_tokens.size() == 2:
library_name = anim_tokens[0]
animation_name = anim_tokens[1]
if not _validate_animation_name(animation_name):
push_error("Invalid animation name: %s" % animation_name)
return
# Create library if doesn't exist
if library_name != _DEFAULT_ANIMATION_LIBRARY and not player.has_animation_library(library_name):
player.add_animation_library(library_name, AnimationLibrary.new())
# Check loop
if animation_name.begins_with(_config.get_animation_loop_exception_prefix()):
animation_name = animation_name.substr(_config.get_animation_loop_exception_prefix().length())
is_loopable = not is_loopable
# Add library
if not player.get_animation_library(library_name).has_animation(animation_name):
player.get_animation_library(library_name).add_animation(animation_name, Animation.new())
var full_name = (
animation_name if library_name == "" else "%s/%s" % [library_name, animation_name]
)
var animation = player.get_animation(full_name)
_cleanup_tracks(target_node, player, animation)
var frame_track = _get_property_track_path(player, target_node, _get_frame_property(is_importing_slice))
var frame_track_index = _create_track(target_node, animation, frame_track)
if direction == "reverse" or direction == "pingpong_reverse":
frames.reverse()
var animation_length = 0
var repetition = 1
if repeat != -1:
is_loopable = false
repetition = repeat
for i in range(repetition):
for frame in frames:
var frame_key = _get_frame_key(target_node, frame, context, slice_rect)
animation.track_insert_key(frame_track_index, animation_length, frame_key)
animation_length += frame.duration / 1000
# Godot 4 has an Animation.LOOP_PINGPONG mode, however it does not
# behave like in Aseprite, so I'm keeping the custom implementation
if direction.begins_with("pingpong"):
var working_frames = frames.duplicate()
working_frames.remove_at(working_frames.size() - 1)
if is_loopable or (repetition > 1 and i < repetition - 1):
working_frames.remove_at(0)
working_frames.reverse()
for frame in working_frames:
var frame_key = _get_frame_key(target_node, frame, context, slice_rect)
animation.track_insert_key(frame_track_index, animation_length, frame_key)
animation_length += frame.duration / 1000
# if keep_anim_length is enabled only adjust length if
# - there aren't other tracks besides metas and frame
# - the current animation is shorter than new one
if not options.keep_anim_length or (animation.get_track_count() == 1 or animation.length < animation_length):
animation.length = animation_length
animation.loop_mode = Animation.LOOP_LINEAR if is_loopable else Animation.LOOP_NONE
return result_code.SUCCESS
const _INVALID_TOKENS := ["/", ":", ",", "["]
func _validate_animation_name(name: String) -> bool:
return not _INVALID_TOKENS.any(func(token: String): return token in name)
func _create_track(target_node: Node, animation: Animation, track: String) -> int:
var track_index = animation.find_track(track, Animation.TYPE_VALUE)
if track_index != -1:
animation.remove_track(track_index)
track_index = animation.add_track(Animation.TYPE_VALUE)
animation.track_set_path(track_index, track)
animation.track_set_interpolation_loop_wrap(track_index, false)
animation.value_track_set_update_mode(track_index, Animation.UPDATE_DISCRETE)
return track_index
func _get_property_track_path(player: AnimationPlayer, target_node: Node, prop: String) -> String:
var node_path = player.get_node(player.root_node).get_path_to(target_node)
return "%s:%s" % [node_path, prop]
func _cleanup_animations(target_node: Node, player: AnimationPlayer, content: Dictionary, options: Dictionary):
if not (content.meta.has("frameTags") and content.meta.frameTags.size() > 0):
return result_code.SUCCESS
_remove_unused_animations(target_node, player, content)
_hide_unused_nodes(player, content)
return result_code.SUCCESS
## remove tracks and animations for nodes without animation tag
func _remove_unused_animations(target_node: Node, player: AnimationPlayer, content: Dictionary):
var tags: Array[String] = []
for t in content.meta.frameTags:
tags.push_back(_animation_name_without_loop_prefix(t.name))
for a in player.get_animation_list():
if tags.has(a):
continue
var animation := player.get_animation(a)
for p in _get_props_to_cleanup():
var track = _get_property_track_path(player, target_node, p)
var track_index = animation.find_track(track, Animation.TYPE_VALUE)
if track_index != -1:
animation.remove_track(track_index)
if animation.get_track_count() == 0:
var p = _get_animation_data(a)
player.get_animation_library(p.library).remove_animation(p.animation)
## control visibility track when node has "hide unused" option available
func _hide_unused_nodes(player: AnimationPlayer, content: Dictionary):
var root_node := player.get_node(player.root_node)
var all_animations := player.get_animation_list()
var all_sprite_nodes: Array[Node] = []
var animation_sprites := {}
for a in all_animations:
var animation := player.get_animation(a)
var sprite_nodes: Array[Node] = []
# get all supported nodes in animation
for track_idx in animation.get_track_count():
var raw_path := animation.track_get_path(track_idx)
var path := _remove_properties_from_path(raw_path)
var sprite_node := root_node.get_node(path)
if not _is_supported_node(sprite_node):
continue
# ignore nodes with no wizard config or not supposed to be hidden
if not wizard_config.has_config(sprite_node) or not wizard_config.load_config(sprite_node).get("set_vis_track", false):
continue
if not sprite_nodes.has(sprite_node):
sprite_nodes.append(sprite_node)
if not all_sprite_nodes.has(sprite_node):
all_sprite_nodes.append(sprite_node)
animation_sprites[animation] = sprite_nodes
for animation: Animation in animation_sprites:
var sprite_nodes : Array[Node] = animation_sprites[animation]
for node in all_sprite_nodes:
# node should be visible if they are in the list and have tracks available
var node_visibility: bool = sprite_nodes.has(node) and _relevant_track_count(node, player, animation) > 0
var visible_track := _get_property_track_path(player, node, "visible")
var visible_track_index := _create_track(node, animation, visible_track)
animation.track_insert_key(visible_track_index, 0, node_visibility)
func list_layers(file: String, only_visibles = false) -> Array:
return _aseprite.list_layers(file, only_visibles)
func list_slices(file: String) -> Array:
return _aseprite.list_slices(file)
func _remove_properties_from_path(path: NodePath) -> NodePath:
var string_path := path as String
if !(":" in string_path):
return string_path as NodePath
var property_path := path.get_concatenated_subnames() as String
string_path = string_path.substr(0, string_path.length() - property_path.length() - 1)
return string_path as NodePath
func _cleanup_tracks(target_node: Node, player: AnimationPlayer, animation: Animation):
for track_key in ["texture", "hframes", "vframes", "region_rect", "frame"]:
var track = _get_property_track_path(player, target_node, track_key)
var track_index = animation.find_track(track, Animation.TYPE_VALUE)
if track_index != -1:
animation.remove_track(track_index)
func _setup_texture(target_node: Node, texture: Texture2D, content: Dictionary, context: Dictionary, is_importing_slice: bool):
push_error("_setup_texture not implemented!")
func _get_frame_property(is_importing_slice: bool) -> String:
push_error("_get_frame_property not implemented!")
return ""
func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary, slice_info: Variant):
push_error("_get_frame_key not implemented!")
func _get_props_to_cleanup() -> Array[String]:
push_error("_props_to_cleanup not implemented!")
return []
func _is_supported_node(target_node: Node):
return target_node is Sprite2D or target_node is Sprite3D or target_node is TextureRect
func _get_animation_data(animaton_name: String) -> Dictionary:
var parts = animaton_name.split("/")
if parts.size() == 2:
return {
"library": parts[0],
"animation": parts[1],
}
return {
"library": _DEFAULT_ANIMATION_LIBRARY,
"animation": parts[0],
}
func _relevant_track_count(target_node: Node, player: AnimationPlayer, animation: Animation) -> int:
var track_count := 0
for p in _get_props_to_cleanup():
if p == "visible":
continue
var track = _get_property_track_path(player, target_node, p)
var track_index = animation.find_track(track, Animation.TYPE_VALUE)
if track_index != -1:
track_count += 1
return track_count
func _animation_name_without_loop_prefix(animation_name: String) -> String:
if animation_name.begins_with(_config.get_animation_loop_exception_prefix()):
return animation_name.substr(_config.get_animation_loop_exception_prefix().length())
return animation_name

View file

@ -0,0 +1 @@
uid://c5gvrr4hajril

View file

@ -0,0 +1,49 @@
extends "animation_creator.gd"
func _setup_texture(sprite: Node, texture: Texture2D, content: Dictionary, context: Dictionary, is_importing_slice: bool):
sprite.texture = texture
if content.frames.is_empty():
return
if is_importing_slice:
sprite.region_enabled = true
sprite.hframes = 1
sprite.vframes = 1
sprite.frame = 0
else:
sprite.region_enabled = false
sprite.hframes = content.meta.size.w / content.frames[0].sourceSize.w
sprite.vframes = content.meta.size.h / content.frames[0].sourceSize.h
func _get_frame_property(is_importing_slice: bool) -> String:
return "frame" if not is_importing_slice else "region_rect"
func _get_props_to_cleanup() -> Array[String]:
return ["frame", "region_rect", "visible"]
func _get_frame_key(sprite: Node, frame: Dictionary, context: Dictionary, slice_info: Variant):
if slice_info != null:
return _create_slice_rect(frame, slice_info)
return _calculate_frame_index(sprite,frame)
func _calculate_frame_index(sprite: Node, frame: Dictionary) -> int:
var column = floor(frame.frame.x * sprite.hframes / sprite.texture.get_width())
var row = floor(frame.frame.y * sprite.vframes / sprite.texture.get_height())
return (row * sprite.hframes) + column
func _create_slice_rect(frame_data: Dictionary, slice_rect: Rect2) -> Rect2:
var frame = frame_data.frame
return Rect2(
frame.x + slice_rect.position.x,
frame.y + slice_rect.position.y,
slice_rect.size.x,
slice_rect.size.y
)

View file

@ -0,0 +1 @@
uid://dsl612cp252oh

View file

@ -0,0 +1,32 @@
extends "animation_creator.gd"
func _setup_texture(target_node: Node, texture: Texture2D, content: Dictionary, context: Dictionary, _is_importing_slice: bool):
context["base_texture"] = texture
func _get_frame_property(_is_importing_slice: bool) -> String:
return "texture"
func _get_props_to_cleanup() -> Array[String]:
return ["texture", "visible"]
func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary, slice_info: Variant):
return _get_atlas_texture(context["base_texture"], frame, slice_info)
func _get_atlas_texture(base_texture: Texture2D, frame_data: Dictionary, slice_info: Variant) -> AtlasTexture:
var tex = AtlasTexture.new()
tex.atlas = base_texture
tex.region = Rect2(Vector2(frame_data.frame.x, frame_data.frame.y), Vector2(frame_data.frame.w, frame_data.frame.h))
tex.filter_clip = true
if slice_info != null:
tex.region.position.x += slice_info.position.x
tex.region.position.y += slice_info.position.y
tex.region.size.x = slice_info.size.x
tex.region.size.y = slice_info.size.y
return tex

View file

@ -0,0 +1,15 @@
@tool
extends RefCounted
var result_code = preload("../config/result_codes.gd")
var _aseprite = preload("../aseprite/aseprite.gd").new()
var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new()
var _config = preload("../config/config.gd").new()
func _load_compressed_texture(sprite_sheet: String) -> PortableCompressedTexture2D:
var image = Image.load_from_file(sprite_sheet)
var tex := PortableCompressedTexture2D.new()
tex.create_from_image(image, PortableCompressedTexture2D.COMPRESSION_MODE_LOSSLESS)
return tex

View file

@ -0,0 +1 @@
uid://cahr0h02r8nt2

View file

@ -0,0 +1,284 @@
@tool
extends "../base_sprite_resource_creator.gd"
enum {
FILE_EXPORT_MODE,
LAYERS_EXPORT_MODE
}
###
### Create SpriteFrames from aseprite files and insert
### them to the animated_sprite node
###
func create_animations(animated_sprite: Node, aseprite_files: Dictionary, options: Dictionary) -> void:
var sprite_frames_result = _create_sprite_frames(aseprite_files, options)
if not sprite_frames_result.is_ok:
printerr(result_code.get_error_message(sprite_frames_result.code))
return
animated_sprite.frames = sprite_frames_result.content.resource
if animated_sprite is CanvasItem:
animated_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
else:
animated_sprite.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
func create_resources(source_files: Array, options: Dictionary = {}) -> Dictionary:
var resources = []
for o in source_files:
if o.is_empty():
return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED)
var result = _create_sprite_frames(o, options)
if not result.is_ok:
return result
resources.push_back({
"data_file": o.data_file,
"resource": result.content.resource,
"extra_gen_files": result.content.extra_gen_files
})
return result_code.result(resources)
func _create_sprite_frames(data: Dictionary, options: Dictionary) -> Dictionary:
var aseprite_resources = _load_aseprite_resources(data, options)
if not aseprite_resources.is_ok:
return aseprite_resources
var resource = _create_sprite_frames_with_animations(
aseprite_resources.content.metadata,
aseprite_resources.content.texture,
options,
)
return result_code.result({
"resource": resource,
"extra_gen_files": aseprite_resources.content.extra_gen_files
})
func _load_aseprite_resources(aseprite_data: Dictionary, options: Dictionary) -> Dictionary:
var content_result = _aseprite_file_exporter.load_json_content(aseprite_data.data_file)
if not content_result.is_ok:
return content_result
var result = _load_or_create_texture_resource(aseprite_data.sprite_sheet, options)
return result_code.result({
"metadata": content_result.content,
"texture": result.texture,
"extra_gen_files": result.extra_gen_files
})
func _load_or_create_texture_resource(sprite_sheet: String, options: Dictionary) -> Dictionary:
if not options.get("should_create_portable_texture", false):
return { "texture": _load_texture(sprite_sheet), "extra_gen_files": [] }
if options.get("sheet_base_path", "") == "":
return {
"texture": create_packed_texture(sprite_sheet),
"extra_gen_files": []
}
var texture_path = "%s.%s.texture.res" % [options.sheet_base_path, sprite_sheet.get_file().get_basename() ]
return {
"texture": create_packed_texture(sprite_sheet, texture_path),
"extra_gen_files": [texture_path]
}
func save_resources(resources: Array) -> int:
for resource in resources:
var code = _save_resource(resource.resource, resource.data_file)
if code != OK:
return code
return OK
func _save_resource(resource, source_path: String) -> int:
var save_path = "%s.%s" % [source_path.get_basename(), "res"]
var code = ResourceSaver.save(resource, save_path, ResourceSaver.FLAG_REPLACE_SUBRESOURCE_PATHS)
resource.take_over_path(save_path)
return code
func _create_sprite_frames_with_animations(content: Dictionary, texture, options: Dictionary) -> SpriteFrames:
var frame_cache = {}
var frames = _aseprite.get_content_frames(content)
var sprite_frames := SpriteFrames.new()
sprite_frames.remove_animation("default")
var frame_rect: Variant = null
# currently, aseprite does not work with the --slice option, so we need to manually
# do it. https://github.com/aseprite/aseprite/issues/2469
if options.get("slice", "") != "":
frame_rect = _aseprite.get_slice_rect(content, options.slice)
if content.meta.has("frameTags") and content.meta.frameTags.size() > 0:
for tag in content.meta.frameTags:
var selected_frames = frames.slice(tag.from, tag.to + 1)
_add_animation_frames(
sprite_frames,
tag.name,
selected_frames,
texture,
frame_rect,
{ "should_round_fps": options.get("should_round_fps", true) },
tag.direction,
int(tag.get("repeat", -1)),
frame_cache
)
else:
_add_animation_frames(sprite_frames, "default", frames, texture, frame_rect, { "should_round_fps": options.get("should_round_fps", true) })
return sprite_frames
func _add_animation_frames(
sprite_frames: SpriteFrames,
anim_name: String,
frames: Array,
texture,
frame_rect: Variant,
options: Dictionary,
direction = 'forward',
repeat = -1,
frame_cache = {}
):
var animation_name := anim_name
var is_loopable = _config.is_default_animation_loop_enabled()
var loop_prefix = _config.get_animation_loop_exception_prefix()
if animation_name.begins_with(loop_prefix):
animation_name = anim_name.trim_prefix(loop_prefix)
is_loopable = not is_loopable
sprite_frames.add_animation(animation_name)
var min_duration = _get_min_duration(frames)
var fps = _calculate_fps(min_duration, options.should_round_fps)
if direction == "reverse" or direction == "pingpong_reverse":
frames.reverse()
var repetition = 1
if repeat != -1:
is_loopable = false
repetition = repeat
for i in range(repetition):
for frame in frames:
_add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache, frame_rect)
if direction.begins_with("pingpong"):
var working_frames = frames.duplicate()
working_frames.remove_at(working_frames.size() - 1)
if is_loopable or (repetition > 1 and i < repetition - 1):
working_frames.remove_at(0)
working_frames.reverse()
for frame in working_frames:
_add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache, frame_rect)
sprite_frames.set_animation_loop(animation_name, is_loopable)
sprite_frames.set_animation_speed(animation_name, fps)
func _calculate_fps(min_duration: int, should_round: bool) -> float:
if should_round:
return ceil(1000.0 / min_duration)
return 1000.0 / min_duration
func _get_min_duration(frames) -> int:
var min_duration = 100000
for frame in frames:
if frame.duration < min_duration:
min_duration = frame.duration
return min_duration
func _load_texture(path) -> CompressedTexture2D:
ResourceLoader.load_threaded_request(path, "CompressedTexture2D", false, ResourceLoader.CACHE_MODE_REPLACE)
return ResourceLoader.load_threaded_get(path)
func create_packed_texture(sprite_sheet: String, save_path: String = "") -> PortableCompressedTexture2D:
var tex := _load_compressed_texture(sprite_sheet)
# if no path was provided, we just want the texture in memory
if save_path == "":
return tex
var exit_code = ResourceSaver.save(tex, save_path)
if exit_code != OK:
printerr(result_code.get_error_message(result_code.ERR_ASEPRITE_EXPORT_FAILED))
return null
return ResourceLoader.load(save_path)
func _add_to_sprite_frames(
sprite_frames,
animation_name: String,
texture,
frame: Dictionary,
min_duration: int,
frame_cache: Dictionary,
frame_rect: Variant,
):
var atlas : AtlasTexture = _create_atlastexture_from_frame(texture, frame, sprite_frames, frame_cache, frame_rect)
var duration = frame.duration / min_duration
sprite_frames.add_frame(animation_name, atlas, duration)
func _create_atlastexture_from_frame(
image,
frame_data,
sprite_frames: SpriteFrames,
frame_cache: Dictionary,
frame_rect: Variant,
) -> AtlasTexture:
var frame = frame_data.frame
var region := Rect2(frame.x, frame.y, frame.w, frame.h)
# this is to manually set the slice
if frame_rect != null:
region.position.x += frame_rect.position.x
region.position.y += frame_rect.position.y
region.size.x = frame_rect.size.x
region.size.y = frame_rect.size.y
var key := "%s_%s_%s_%s" % [frame.x, frame.y, frame.w, frame.h]
var texture = frame_cache.get(key)
if texture != null and texture.atlas == image:
return texture
var atlas_texture := AtlasTexture.new()
atlas_texture.atlas = image
atlas_texture.region = region
frame_cache[key] = atlas_texture
return atlas_texture
func list_layers(file: String, only_visibles = false) -> Array:
return _aseprite.list_layers(file, only_visibles)
func list_slices(file: String) -> Array:
return _aseprite.list_slices(file)
func _get_file_basename(file_path: String) -> String:
return file_path.get_file().trim_suffix('.%s' % file_path.get_extension())

View file

@ -0,0 +1 @@
uid://dl8ny6cpqq0sj

View file

@ -0,0 +1,31 @@
@tool
extends "../base_sprite_resource_creator.gd"
func load_texture(target_node: Node, aseprite_files: Dictionary, options: Dictionary) -> void:
var source_file = aseprite_files.data_file
var sprite_sheet = aseprite_files.sprite_sheet
var data = _aseprite_file_exporter.load_json_content(source_file)
var texture
if options.get("should_create_portable_texture", false):
texture = _load_compressed_texture(sprite_sheet)
else:
texture = ResourceLoader.load(sprite_sheet)
if not data.is_ok:
printerr("Failed to load aseprite source %s" % source_file)
return
if options.slice == "":
target_node.texture = texture
else:
var region = _aseprite.get_slice_rect(data.content, options.slice)
var atlas_texture := AtlasTexture.new()
atlas_texture.atlas = texture
atlas_texture.region = region
target_node.texture = atlas_texture
if target_node is CanvasItem:
target_node.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
else:
target_node.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST

View file

@ -0,0 +1 @@
uid://ywvg22ffi7cx

View file

@ -0,0 +1,119 @@
## This export plugin makes sure all textures are included in
## the exported game and also remove any metadata that could
## potentially leak information about paths in the host system.
extends EditorExportPlugin
const wizard_config = preload("../config/wizard_config.gd")
func _get_name():
return "aseprite_wizard_metadata_export_plugin"
func _export_file(path: String, type: String, features: PackedStringArray) -> void:
match type:
"PackedScene":
_cleanup_scene(path, type)
"SpriteFrames":
_handle_spriteframes(path, type)
func _cleanup_scene(path: String, type: String):
var scene : PackedScene = ResourceLoader.load(path, type, ResourceLoader.CACHE_MODE_IGNORE)
var scene_changed := false
var root_node := scene.instantiate(PackedScene.GEN_EDIT_STATE_INSTANCE)
var nodes := [root_node]
#remove_at metadata from scene
while not nodes.is_empty():
var node : Node = nodes.pop_front()
for child in node.get_children():
nodes.push_back(child)
if _remove_meta(node):
scene_changed = true
#save scene if changed
if scene_changed:
var filtered_scene := PackedScene.new()
if filtered_scene.pack(root_node) != OK:
print("Error updating scene")
return
var content := _get_scene_content(path, filtered_scene)
add_file(path, content, true)
root_node.free()
func _remove_meta(node: Object) -> bool:
if node.has_meta(wizard_config.WIZARD_CONFIG_META_NAME):
node.remove_meta(wizard_config.WIZARD_CONFIG_META_NAME)
return true
return false
func _get_scene_content(path:String, scene:PackedScene) -> PackedByteArray:
var tmp_path = OS.get_cache_dir() + "tmp_scene." + path.get_extension()
ResourceSaver.save(scene, tmp_path)
var tmp_file = FileAccess.open(tmp_path, FileAccess.READ)
var content : PackedByteArray = tmp_file.get_buffer(tmp_file.get_length())
tmp_file.close()
if FileAccess.file_exists(tmp_path):
DirAccess.remove_absolute(tmp_path)
return content
## As of today, Godot only includes in the export the resources
## directly referenced by the import plugin.
## To make the generated textures available, I need to explicitly
## add them to the exported package.
func _import_extra_textures(node: SpriteFrames):
# check if this is a wizard import
if not node.has_meta("imported_via_aw"):
return
var animations = node.get_animation_names()
var textures = []
for animation in animations:
var frame_count = node.get_frame_count(animation)
for idx in range(frame_count):
var atlas = node.get_frame_texture(animation, idx)
if atlas == null:
continue
var tex = atlas.atlas
if tex == null:
continue
if not textures.has(tex.resource_path):
textures.push_back(tex.resource_path)
for texture_path in textures:
var file = FileAccess.open(texture_path, FileAccess.READ)
var content : PackedByteArray = file.get_buffer(file.get_length())
add_file(texture_path, content, false)
func _handle_spriteframes(path: String, type: String):
var resource : SpriteFrames = ResourceLoader.load(path, type, ResourceLoader.CACHE_MODE_IGNORE)
_import_extra_textures(resource)
if _remove_meta(resource):
var content := _create_temp_resource(path, resource)
add_file(path, content, true)
func _create_temp_resource(path: String, resource: SpriteFrames) -> PackedByteArray:
var tmp_path = OS.get_cache_dir() + "tmp_spriteframes_resource." + path.get_extension()
ResourceSaver.save(resource, tmp_path)
var tmp_file = FileAccess.open(tmp_path, FileAccess.READ)
var content : PackedByteArray = tmp_file.get_buffer(tmp_file.get_length())
tmp_file.close()
if FileAccess.file_exists(tmp_path):
DirAccess.remove_absolute(tmp_path)
return content

View file

@ -0,0 +1 @@
uid://b6ag7a1rlkjip

View file

@ -0,0 +1,22 @@
@tool
extends Node
const WAIT_TIME_IN_SECONDS := 0.8
var _time_left := 0.0
var _scheduled := false
## Schedule a file system scan. This method works like a debouncer
## postponing the scan until no more calls are being made
func schedule_file_system_scan() -> void:
_time_left = WAIT_TIME_IN_SECONDS
_scheduled = true
func _process(delta: float) -> void:
if _scheduled:
if _time_left > 0.0:
_time_left -= delta
else:
_scheduled = false
if not EditorInterface.get_resource_filesystem().is_scanning():
EditorInterface.get_resource_filesystem().scan.call_deferred()

View file

@ -0,0 +1 @@
uid://bivugcpjlxeiv

View file

@ -0,0 +1,117 @@
@tool
extends EditorImportPlugin
const result_codes = preload("../config/result_codes.gd")
var config = preload("../config/config.gd").new()
var _aseprite = preload("../aseprite/aseprite.gd").new()
var file_system_helper
func _init(fs_helper) -> void:
file_system_helper = fs_helper
func _get_recognized_extensions():
return ["aseprite", "ase"]
func _get_save_extension():
return "json"
func _get_resource_type():
return "JSON"
func _get_preset_count():
return 1
func _get_preset_name(i):
return "Default"
func _get_import_order():
return 1
func _get_option_visibility(path, option, options):
return true
func _import(source_file, save_path, options, platform_variants, gen_files):
var old_data = _load_old_data(source_file)
var exception_pattern = options.get('layer/exclude_layers_pattern', "")
var should_include_only_visibles = options.get('layer/only_visible_layers', false)
var absolute_source_file = ProjectSettings.globalize_path(source_file)
var layers = _aseprite.list_valid_layers(
absolute_source_file,
exception_pattern,
should_include_only_visibles
)
var layers_resources_folder = options["output/layers_resources_folder"]
var import_options = _get_base_import_options(options)
import_options["source"] = source_file
var base_name = source_file.get_basename()
if layers_resources_folder != "":
if not DirAccess.dir_exists_absolute(layers_resources_folder):
DirAccess.make_dir_recursive_absolute(layers_resources_folder)
base_name = "%s/%s" % [layers_resources_folder, base_name.get_file()]
var data_to_save = {
"layers": {}
}
for layer in layers:
var layer_save_path = "%s_l_%s.%s" % [base_name, layer, _layer_extension()]
var file = FileAccess.open(layer_save_path, FileAccess.WRITE)
file.store_string(JSON.stringify({
"layer": layer,
"import_options": import_options,
}))
data_to_save.layers[layer] = layer_save_path
var json = JSON.new()
json.data = data_to_save
var exit_code = ResourceSaver.save(json, "%s.%s" % [save_path, _get_save_extension()])
_cleanup_old_layers(old_data, layers)
file_system_helper.schedule_file_system_scan()
return exit_code
func _layer_extension() -> String:
return ""
func _get_base_import_options(options: Dictionary):
return {}
func _load_old_data(source_file: String):
var old_data = {}
if ResourceLoader.exists(source_file):
var d = ResourceLoader.load(source_file)
if d is JSON:
old_data = d.data.layers
return old_data
func _cleanup_old_layers(old_data: Dictionary, layers: Array):
var old_layers = old_data.keys()
for old_layer in old_layers:
if not layers.has(old_layer):
var file = old_data[old_layer]
if FileAccess.file_exists(file):
DirAccess.remove_absolute(file)

View file

@ -0,0 +1 @@
uid://berv346k540w7

View file

@ -0,0 +1,57 @@
@tool
extends EditorImportPlugin
##
## No-op importer to allow files to be seen and
## managed, but without triggering a real import
##
var config = preload("../config/config.gd").new()
func _get_importer_name():
return "aseprite_wizard.plugin.noop"
func _get_visible_name():
return "Aseprite (No Import)"
func _get_recognized_extensions():
return ["aseprite", "ase"]
func _get_save_extension():
return "res"
func _get_resource_type():
return "PackedDataContainer"
func _get_preset_count():
return 1
func _get_preset_name(i):
return "Default"
func _get_priority():
return 2.0 if config.get_default_importer() == config.IMPORTER_NOOP_NAME else 1.0
func _get_import_order():
return 1
func _get_import_options(_path, _i):
return []
func _get_option_visibility(path, option, options):
return true
func _import(source_file, save_path, options, platform_variants, gen_files):
var container = PackedDataContainer.new()
return ResourceSaver.save(container, "%s.%s" % [save_path, _get_save_extension()])

View file

@ -0,0 +1 @@
uid://cf00cl8s3dvod

View file

@ -0,0 +1,112 @@
@tool
extends EditorImportPlugin
const result_codes = preload("../../config/result_codes.gd")
var config = preload("../../config/config.gd").new()
var _aseprite = preload("../../aseprite/aseprite.gd").new()
var _sf_creator = preload("../../creators/sprite_frames/sprite_frames_creator.gd").new()
func _get_importer_name():
return "aseprite_wizard.plugin.spriteframes-split-layer"
func _get_visible_name():
return "Aseprite Layer SpriteFrames"
func _get_recognized_extensions():
return ["ase_layer"]
func _get_save_extension():
return "res"
func _get_resource_type():
return "SpriteFrames"
func _get_preset_count():
return 1
func _get_preset_name(i):
return "Default"
func _get_priority():
return 1.0
func _get_import_order():
return 1
func _get_import_options(_path, _i):
return []
func _get_option_visibility(path, option, options):
return true
func _import(source_file, save_path, options, platform_variants, gen_files):
var file = FileAccess.open(source_file, FileAccess.READ)
var data = JSON.parse_string(file.get_as_text())
var source_path = source_file.get_base_dir()
var absolute_source_file = ProjectSettings.globalize_path(data.import_options.source)
var export_options = {
"sheet_type": data.import_options.sheet_type,
"sheet_columns": data.import_options.sheet_columns,
"should_round_fps": data.import_options.should_round_fps,
}
var source_files = _aseprite.export_file_with_layers(
absolute_source_file,
[data.layer],
source_path,
export_options,
)
if source_files.is_empty():
printerr("ERROR - Could not import aseprite file. Layer not found.")
return FAILED
#
var resources = _sf_creator.create_resources([source_files], {
"should_round_fps": data.import_options.should_round_fps,
"should_create_portable_texture": true,
"sheet_base_path": save_path,
})
if not resources.is_ok:
printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(resources.code))
return FAILED
var resource = resources.content[0]
resource.resource.set_meta("imported_via_aw", true)
var resource_path = "%s.res" % save_path
var exit_code = ResourceSaver.save(resource.resource, resource_path)
resource.resource.take_over_path(resource_path)
for extra_file in resource.extra_gen_files:
gen_files.push_back(extra_file)
#
if config.should_remove_source_files():
_remove_source_files(source_files)
if exit_code != OK:
printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code))
return FAILED
return OK
func _remove_source_files(source_files: Dictionary):
DirAccess.remove_absolute(source_files.data_file)
DirAccess.remove_absolute(source_files.sprite_sheet)

View file

@ -0,0 +1 @@
uid://ccsolt4jb8f67

View file

@ -0,0 +1,52 @@
@tool
extends "../static_texture_import_plugin_base.gd"
##
## Static texture importer (Split).
## Imports first frame from Aseprite file as texture in multiple resources
##
func _get_importer_name():
return "aseprite_wizard.plugin.static-texture-split-layer"
func _get_visible_name():
return "Aseprite Layer Texture"
func _get_recognized_extensions():
return ["ase_layer_tex"]
func _get_priority():
return 1.0
func _get_import_options(_path, _i):
return []
func _import(source_file, save_path, options, platform_variants, gen_files):
var file = FileAccess.open(source_file, FileAccess.READ)
var i_data = JSON.parse_string(file.get_as_text())
var source_path = source_file.get_base_dir()
var absolute_source_file = ProjectSettings.globalize_path(i_data.import_options.source)
var aseprite_opts = {
"layer": i_data.layer,
"output_filename": '',
"output_folder": source_path,
"first_frame_only": true,
}
var result = _generate_texture(absolute_source_file, aseprite_opts)
if not result.is_ok:
printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(result.code))
return FAILED
var sprite_sheet = result.content.sprite_sheet
var data = result.content.data
return _save_resource(sprite_sheet, save_path, result.content.data_file, data.meta.size)

View file

@ -0,0 +1,125 @@
@tool
extends EditorImportPlugin
const result_codes = preload("../config/result_codes.gd")
var config = preload("../config/config.gd").new()
var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new()
var _sf_creator = preload("../creators/sprite_frames/sprite_frames_creator.gd").new()
var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
func _get_importer_name():
return "aseprite_wizard.plugin.spriteframes"
func _get_visible_name():
return "Aseprite SpriteFrames"
func _get_recognized_extensions():
return ["aseprite", "ase"]
func _get_save_extension():
return "res"
func _get_resource_type():
return "SpriteFrames"
func _get_preset_count():
return 1
func _get_preset_name(i):
return "Default"
func _get_priority():
return 2.0 if config.get_default_importer() == config.IMPORTER_SPRITEFRAMES_NAME else 1.0
func _get_import_order():
return 1
func _get_import_options(_path, _i):
return [
{"name": "layer/exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()},
{"name": "layer/only_visible_layers", "default_value": false},
{
"name": "sheet/sheet_type",
"default_value": "packed",
"property_hint": PROPERTY_HINT_ENUM,
"hint_string": "columns,horizontal,vertical,packed",
},
{
"name": "sheet/sheet_columns",
"default_value": 12,
},
{"name": "animation/round_fps", "default_value": true},
]
func _get_option_visibility(path, option, options):
return true
func _import(source_file, save_path, options, platform_variants, gen_files):
var absolute_source_file = ProjectSettings.globalize_path(source_file)
var absolute_save_path = ProjectSettings.globalize_path(save_path)
var source_path = source_file.get_base_dir()
var source_basename = source_file.substr(source_path.length()+1, -1)
source_basename = source_basename.substr(0, source_basename.rfind('.'))
var aseprite_opts = {
"export_mode": _sf_creator.FILE_EXPORT_MODE,
"exception_pattern": options['layer/exclude_layers_pattern'],
"only_visible_layers": options['layer/only_visible_layers'],
"output_filename": '',
"output_folder": source_path,
"sheet_type": options["sheet/sheet_type"],
"sheet_columns": options["sheet/sheet_columns"],
}
var source_files = _aseprite_file_exporter.generate_aseprite_files(absolute_source_file, aseprite_opts)
if not source_files.is_ok:
printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(source_files.code))
return FAILED
var resources = _sf_creator.create_resources(source_files.content, {
"should_round_fps": options["animation/round_fps"],
"should_create_portable_texture": true,
"sheet_base_path": save_path,
})
if not resources.is_ok:
printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(resources.code))
return FAILED
var resource: Dictionary = resources.content[0]
resource.resource.set_meta("imported_via_aw", true)
var resource_path = "%s.res" % save_path
var exit_code = ResourceSaver.save(resource.resource, resource_path)
resource.resource.take_over_path(resource_path)
#for extra_file in resource.extra_gen_files:
#gen_files.push_back(extra_file)
if config.should_remove_source_files():
_remove_source_files(source_files.content)
if exit_code != OK:
printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code))
return FAILED
return OK
func _remove_source_files(source_files: Array):
for s in source_files:
DirAccess.remove_absolute(s.data_file)
DirAccess.remove_absolute(s.sprite_sheet)

View file

@ -0,0 +1 @@
uid://bffpek43i21it

View file

@ -0,0 +1,49 @@
@tool
extends "./multiple_import_plugin_base.gd"
func _get_importer_name():
return "aseprite_wizard.plugin.spriteframes-split"
func _get_visible_name():
return "Aseprite SpriteFrames (Split By Layer)"
func _get_priority():
return 2.0 if config.get_default_importer() == config.IMPORTER_SPRITEFRAMES_SPLIT_NAME else 1.0
func _get_import_options(_path, _i):
return [
{"name": "layer/exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()},
{"name": "layer/only_visible_layers", "default_value": false},
{
"name": "sheet/sheet_type",
"default_value": "packed",
"property_hint": PROPERTY_HINT_ENUM,
"hint_string": "columns,horizontal,vertical,packed",
},
{
"name": "sheet/sheet_columns",
"default_value": 12,
},
{"name": "animation/round_fps", "default_value": true},
{
"name": "output/layers_resources_folder",
"default_value": "",
"property_hint": PROPERTY_HINT_DIR,
}
]
func _layer_extension() -> String:
return "ase_layer"
func _get_base_import_options(options: Dictionary):
return {
"sheet_type": options["sheet/sheet_type"],
"sheet_columns": options["sheet/sheet_columns"],
"should_round_fps": options["animation/round_fps"],
}

View file

@ -0,0 +1 @@
uid://cet3t6bfaryhx

View file

@ -0,0 +1,54 @@
@tool
extends "./static_texture_import_plugin_base.gd"
##
## Static texture importer.
## Imports first frame from Aseprite file as texture
##
func _get_importer_name():
return "aseprite_wizard.plugin.static-texture"
func _get_visible_name():
return "Aseprite Texture"
func _get_recognized_extensions():
return ["aseprite", "ase"]
func _get_priority():
return 2.0 if config.get_default_importer() == config.IMPORTER_STATIC_TEXTURE_NAME else 0.8
func _get_import_options(_path, _i):
return [
{"name": "layer/exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()},
{"name": "layer/only_visible_layers", "default_value": false},
]
func _import(source_file, save_path, options, platform_variants, gen_files):
var absolute_source_file = ProjectSettings.globalize_path(source_file)
var source_path = source_file.get_base_dir()
var aseprite_opts = {
"exception_pattern": options['layer/exclude_layers_pattern'],
"only_visible_layers": options['layer/only_visible_layers'],
"output_filename": '',
"output_folder": source_path,
"first_frame_only": true,
}
var result = _generate_texture(absolute_source_file, aseprite_opts)
if not result.is_ok:
printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(result.code))
return FAILED
var sprite_sheet = result.content.sprite_sheet
var data = result.content.data
return _save_resource(sprite_sheet, save_path, result.content.data_file, data.meta.size)

View file

@ -0,0 +1 @@
uid://cgissgnbau7d5

View file

@ -0,0 +1,71 @@
@tool
extends EditorImportPlugin
const result_codes = preload("../config/result_codes.gd")
var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new()
var config = preload("../config/config.gd").new()
func _get_save_extension():
return "res"
func _get_resource_type():
return "PortableCompressedTexture2D"
func _get_preset_count():
return 1
func _get_preset_name(i):
return "Default"
func _get_import_order():
return 1
func _get_option_visibility(path, option, options):
return true
func _generate_texture(absolute_source_file: String, options: Dictionary) -> Dictionary:
var result = _aseprite_file_exporter.generate_aseprite_file(absolute_source_file, options)
if not result.is_ok:
return result
var sprite_sheet = result.content.sprite_sheet
var data_result = _aseprite_file_exporter.load_json_content(result.content.data_file)
if not data_result.is_ok:
return data_result
var data = data_result.content
return result_codes.result({
"data_file": result.content.data_file,
"sprite_sheet": sprite_sheet,
"data": data
})
func _save_resource(sprite_sheet: String, save_path: String, data_file_path: String, size: Dictionary) -> int:
var image = Image.load_from_file(sprite_sheet)
var tex := PortableCompressedTexture2D.new()
tex.create_from_image(image, PortableCompressedTexture2D.COMPRESSION_MODE_LOSSLESS)
#
var exit_code = ResourceSaver.save(tex, "%s.%s" % [save_path, _get_save_extension()])
if config.should_remove_source_files():
DirAccess.remove_absolute(data_file_path)
DirAccess.remove_absolute(sprite_sheet)
if exit_code != OK:
printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code))
return FAILED
return OK

View file

@ -0,0 +1 @@
uid://b1q5n3o01i5tv

View file

@ -0,0 +1,34 @@
@tool
extends "./multiple_import_plugin_base.gd"
func _get_importer_name():
return "aseprite_wizard.plugin.static-texture-split"
func _get_visible_name():
return "Aseprite Texture (Split By Layer)"
func _get_priority():
return 2.0 if config.get_default_importer() == config.IMPORTER_STATIC_TEXTURE_SPLIT_NAME else 1.0
func _get_import_options(_path, _i):
return [
{"name": "layer/exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()},
{"name": "layer/only_visible_layers", "default_value": false},
{
"name": "output/layers_resources_folder",
"default_value": "",
"property_hint": PROPERTY_HINT_DIR,
}
]
func _layer_extension() -> String:
return "ase_layer_tex"
func _get_base_import_options(options: Dictionary):
return {}

View file

@ -0,0 +1 @@
uid://slny0vb6r7j2

View file

@ -0,0 +1,138 @@
@tool
extends EditorImportPlugin
##
## Tileset texture importer.
## Imports Aseprite tileset layers as an AtlasTexture
##
const result_codes = preload("../config/result_codes.gd")
var _aseprite_file_exporter = preload("../aseprite/file_exporter.gd").new()
var config = preload("../config/config.gd").new()
var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
func _get_importer_name():
return "aseprite_wizard.plugin.tileset-texture"
func _get_visible_name():
return "Aseprite Tileset Texture"
func _get_recognized_extensions():
return ["aseprite", "ase"]
func _get_save_extension():
return "res"
func _get_resource_type():
return "PortableCompressedTexture2D"
func _get_preset_count():
return 1
func _get_preset_name(i):
return "Default"
func _get_priority():
return 2.0 if config.get_default_importer() == config.IMPORTER_TILESET_TEXTURE_NAME else 0.9
func _get_import_order():
return 1
func _get_import_options(_path, _i):
return [
{"name": "layer/exclude_layers_pattern", "default_value": config.get_default_exclusion_pattern()},
{"name": "layer/only_visible_layers", "default_value": false},
{
"name": "sheet/sheet_type",
"default_value": "columns",
"property_hint": PROPERTY_HINT_ENUM,
"hint_string": "columns,horizontal,vertical,packed",
},
{
"name": "sheet/sheet_columns",
"default_value": 12,
},
]
func _get_option_visibility(path, option, options):
return true
func _import(source_file, save_path, options, platform_variants, gen_files):
var absolute_source_file = ProjectSettings.globalize_path(source_file)
var absolute_save_path = ProjectSettings.globalize_path(save_path)
var source_path = source_file.get_base_dir()
var source_basename = source_file.substr(source_path.length()+1, -1)
source_basename = source_basename.substr(0, source_basename.rfind('.'))
var aseprite_opts = {
"exception_pattern": options['layer/exclude_layers_pattern'],
"only_visible_layers": options['layer/only_visible_layers'],
"output_filename": '',
"output_folder": source_path,
"sheet_type": options["sheet/sheet_type"],
"sheet_columns": options["sheet/sheet_columns"],
}
var result = _generate_texture(absolute_source_file, aseprite_opts)
if not result.is_ok:
printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(result.code))
return FAILED
var sprite_sheet = result.content.sprite_sheet
var data = result.content.data
return _save_resource(sprite_sheet, save_path, result.content.data_file, data.meta.size)
func _generate_texture(absolute_source_file: String, options: Dictionary) -> Dictionary:
var result = _aseprite_file_exporter.generate_tileset_files(absolute_source_file, options)
if not result.is_ok:
return result
var sprite_sheet = result.content.sprite_sheet
var data_result = _aseprite_file_exporter.load_json_content(result.content.data_file)
if not data_result.is_ok:
return data_result
var data = data_result.content
return result_codes.result({
"data_file": result.content.data_file,
"sprite_sheet": sprite_sheet,
"data": data
})
func _save_resource(sprite_sheet: String, save_path: String, data_file_path: String, size: Dictionary) -> int:
var image = Image.load_from_file(sprite_sheet)
var tex := PortableCompressedTexture2D.new()
tex.create_from_image(image, PortableCompressedTexture2D.COMPRESSION_MODE_LOSSLESS)
var exit_code = ResourceSaver.save(tex, "%s.%s" % [save_path, _get_save_extension()])
if config.should_remove_source_files():
DirAccess.remove_absolute(data_file_path)
DirAccess.remove_absolute(sprite_sheet)
if exit_code != OK:
printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code))
return FAILED
return OK

View file

@ -0,0 +1 @@
uid://dao40mnmgx2u4

View file

@ -0,0 +1,74 @@
@tool
extends "../base_inspector_dock.gd"
var sprite_frames_creator = preload("../../../creators/sprite_frames/sprite_frames_creator.gd").new()
@onready var _animation_section := $dock_fields/VBoxContainer/extra/sections/animation_sf as VBoxContainer
@onready var _animation_section_header := $dock_fields/VBoxContainer/extra/sections/animation_sf/section_header as Button
@onready var _animation_section_container := $dock_fields/VBoxContainer/extra/sections/animation_sf/section_content as MarginContainer
@onready var _round_fps := $dock_fields/VBoxContainer/extra/sections/animation_sf/section_content/content/round_fps/CheckBox as CheckBox
const INTERFACE_SECTION_KEY_ANIMATION = "animation_section"
func _pre_setup():
_expandable_sections[INTERFACE_SECTION_KEY_ANIMATION] = { "header": _animation_section_header, "content": _animation_section_container}
func _setup():
_animation_section_header.button_down.connect(_on_animation_header_button_down)
func _load_config(cfg):
_round_fps.button_pressed = cfg.get("should_round_fps", true)
func _get_available_layers(global_source_path: String) -> Array:
return sprite_frames_creator.list_layers(global_source_path)
func _get_available_slices(global_source_path: String) -> Array:
return sprite_frames_creator.list_slices(global_source_path)
func _do_import():
var root = get_tree().get_edited_scene_root()
var source_path = ProjectSettings.globalize_path(_source)
var options = _get_import_options(root.scene_file_path.get_base_dir())
_save_config()
var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options)
if not aseprite_output.is_ok:
var error = result_code.get_error_message(aseprite_output.code)
printerr(error)
_show_message(error)
return
file_system.scan()
await file_system.filesystem_changed
sprite_frames_creator.create_animations(target_node, aseprite_output.content, {
"slice": _slice,
"should_round_fps": _round_fps.button_pressed,
"should_create_portable_texture": _embed_field.button_pressed,
})
wizard_config.set_source_hash(target_node, FileAccess.get_md5(source_path))
_handle_cleanup(aseprite_output.content, _embed_field.button_pressed)
func _show_specific_fields():
_animation_section.show()
func _get_current_field_values() -> Dictionary:
return {
"should_round_fps": _round_fps.button_pressed,
}
func _on_animation_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_ANIMATION)

View file

@ -0,0 +1,15 @@
[gd_scene load_steps=3 format=3 uid="uid://vej7yqkbtd5f"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/animated_sprite/animated_sprite_inspector_dock.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://uxm7b02wry10" path="res://addons/AsepriteWizard/interface/docks/dock_fields.tscn" id="2_2ilip"]
[node name="animated_sprite_inspector_dock" type="PanelContainer"]
offset_right = 14.0
offset_bottom = 14.0
script = ExtResource("1")
[node name="dock_fields" parent="." instance=ExtResource("2_2ilip")]
layout_mode = 2
[connection signal="field_changed" from="dock_fields" to="." method="_on_dock_fields_field_changed"]
[connection signal="revert_changes_requested" from="dock_fields" to="." method="_on_dock_fields_revert_changes_requested"]

View file

@ -0,0 +1,13 @@
@tool
extends EditorInspectorPlugin
const ASInspectorDock = preload("./animated_sprite_inspector_dock.tscn")
func _can_handle(object):
return object is AnimatedSprite2D || object is AnimatedSprite3D
func _parse_end(object):
var dock = ASInspectorDock.instantiate()
dock.target_node = object
add_custom_control(dock)

View file

@ -0,0 +1 @@
uid://boni725doa1qa

View file

@ -0,0 +1,571 @@
@tool
extends PanelContainer
const wizard_config = preload("../../config/wizard_config.gd")
const result_code = preload("../../config/result_codes.gd")
var _aseprite_file_exporter = preload("../../aseprite/file_exporter.gd").new()
var config = preload("../../config/config.gd").new()
var scene: Node
var target_node: Node
var file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
var _slice: String = ""
var _source: String = ""
var _file_dialog_aseprite: EditorFileDialog
var _output_folder_dialog: EditorFileDialog
var _importing := false
var _output_folder := ""
var _out_folder_default := "[Same as scene]"
var _layer_default := "[all]"
var _output_filename_text := "File Name"
var _output_filename_prefix_text := "File Name Prefix"
var _interface_section_state
@onready var _section_title := $dock_fields/VBoxContainer/title as Button
# general
@onready var _source_field := $dock_fields/VBoxContainer/source/button as Button
# layers
@onready var _layer_section_header := $dock_fields/VBoxContainer/extra/sections/layers/section_header as Button
@onready var _layer_section_container := $dock_fields/VBoxContainer/extra/sections/layers/section_content as MarginContainer
@onready var _layer_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/layer/options as VBoxContainer
@onready var _visible_layers_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/visible_layers/CheckBox as CheckBox
@onready var _ex_pattern_field := $dock_fields/VBoxContainer/extra/sections/layers/section_content/content/ex_pattern/LineEdit as LineEdit
# slice
@onready var _slice_section_header := $dock_fields/VBoxContainer/extra/sections/slices/section_header as Button
@onready var _slice_section_container := $dock_fields/VBoxContainer/extra/sections/slices/section_content as MarginContainer
@onready var _slice_field := $dock_fields/VBoxContainer/extra/sections/slices/section_content/content/slice/options as OptionButton
# output
@onready var _output_section_header := $dock_fields/VBoxContainer/extra/sections/output/section_header as Button
@onready var _output_section_container := $dock_fields/VBoxContainer/extra/sections/output/section_content as MarginContainer
@onready var _out_folder_field := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_folder/button as Button
@onready var _out_folder_container := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_folder as HBoxContainer
@onready var _out_filename_field := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_filename/LineEdit as LineEdit
@onready var _out_filename_label := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_filename/Label as Label
@onready var _out_filename_container := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/out_filename as HBoxContainer
@onready var _embed_label := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/embed/Label as Label
@onready var _embed_field := $dock_fields/VBoxContainer/extra/sections/output/section_content/content/embed/CheckBox as CheckBox
@onready var _import_button := $dock_fields/VBoxContainer/import as Button
const INTERFACE_SECTION_KEY_LAYER = "layer_section"
const INTERFACE_SECTION_KEY_SLICE = "slice_section"
const INTERFACE_SECTION_KEY_OUTPUT = "output_section"
@onready var _expandable_sections = {
INTERFACE_SECTION_KEY_LAYER: { "header": _layer_section_header, "content": _layer_section_container},
INTERFACE_SECTION_KEY_SLICE: { "header": _slice_section_header, "content": _slice_section_container},
INTERFACE_SECTION_KEY_OUTPUT: { "header": _output_section_header, "content": _output_section_container},
}
const PENDING_CHANGES_KEY := "pending_changes"
var _do_not_update_pending := false
func _ready():
_pre_setup()
_setup_interface()
_setup_config()
_setup_field_listeners()
_setup()
_check_for_changes()
_check_for_field_changes()
func _check_for_changes():
if not _source or _source == "":
return
var saved_hash = wizard_config.get_source_hash(target_node)
if saved_hash == "":
return
if saved_hash != FileAccess.get_md5(_source):
$dock_fields.show_source_change_warning()
func _check_for_field_changes():
var cfg = wizard_config.load_config(target_node)
var changed = _get_changed_config(cfg)
if changed == null or changed.is_empty():
$dock_fields.hide_fields_change_warning()
_interface_section_state[PENDING_CHANGES_KEY] = {}
else:
if cfg != null and not cfg.is_empty(): # has saved data
$dock_fields.show_fields_change_warning()
_interface_section_state[PENDING_CHANGES_KEY] = changed
func _update_pending_fields():
if _do_not_update_pending:
return
_check_for_field_changes()
wizard_config.save_interface_config(target_node, _interface_section_state)
func _get_changed_config(saved_config):
var current = _get_current_config()
if saved_config == null:
return current if current != null else {}
if current == null:
return {}
var changed := {}
for c in current:
if saved_config.get(c) != current[c]:
changed[c] = current[c]
return changed
func _setup_interface():
_hide_fields()
_show_specific_fields()
var cfg = wizard_config.load_interface_config(target_node)
_interface_section_state = cfg
_section_title.add_theme_stylebox_override("normal", _section_title.get_theme_stylebox("hover"))
for key in _expandable_sections:
_adjust_section_visibility(key)
func _load_persisted_config() -> Dictionary:
var cfg = wizard_config.load_config(target_node)
var persisted = {}
if cfg == null:
persisted = {}
else:
persisted = cfg.duplicate()
var changed = _interface_section_state.get(PENDING_CHANGES_KEY, {})
for c in changed:
persisted[c] = changed[c]
return persisted
func _setup_config():
var cfg = _load_persisted_config()
if cfg.is_empty():
_load_common_default_config()
else:
_load_common_config(cfg)
func _load_common_config(cfg):
if cfg.has("source"):
_set_source(cfg.source)
# keeping this to be backwards compatible
if cfg.get("layer", "") != "":
_layer_field.set_selected_layers([cfg.layer])
_update_pending_fields()
_out_filename_label.text = _output_filename_prefix_text
else:
var layers = cfg.get("layers", [])
_layer_field.set_selected_layers(layers)
if layers.size() > 0:
_update_pending_fields()
if layers.size() == 1:
_out_filename_label.text = _output_filename_prefix_text
if cfg.get("slice", "") != "":
_slice_field.clear()
_set_slice(cfg.slice)
else:
_slice = ""
_slice_field.select(0)
_set_out_folder(cfg.get("o_folder", ""))
_out_filename_field.text = cfg.get("o_name", "")
_visible_layers_field.button_pressed = cfg.get("only_visible", false)
_ex_pattern_field.text = cfg.get("o_ex_p", "")
_embed_field.button_pressed = cfg.get("embed_tex", false)
_load_config(cfg)
_handle_embed_visibility()
func _load_common_default_config():
_ex_pattern_field.text = config.get_default_exclusion_pattern()
_visible_layers_field.button_pressed = config.should_include_only_visible_layers_by_default()
#_cleanup_hide_unused_nodes.button_pressed = config.is_set_visible_track_automatically_enabled()
_load_default_config()
func _set_source(source):
_source = source
_source_field.text = _source
_source_field.tooltip_text = _source
_update_pending_fields()
func _set_slice(slice):
_slice = slice
_slice_field.add_item(_slice)
_update_pending_fields()
func _set_out_folder(path):
_output_folder = path
_out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default
_out_folder_field.tooltip_text = _out_folder_field.text
_update_pending_fields()
func _toggle_section_visibility(key: String) -> void:
_interface_section_state[key] = not _interface_section_state.get(key, false)
_adjust_section_visibility(key)
wizard_config.save_interface_config(target_node, _interface_section_state)
func _adjust_section_visibility(key: String) -> void:
var section = _expandable_sections[key]
var is_visible = _interface_section_state.get(key, false)
_adjust_icon(section.header, is_visible)
section.content.visible = is_visible
func _adjust_icon(section: Button, is_visible: bool = true) -> void:
var icon_name = "GuiTreeArrowDown" if is_visible else "GuiTreeArrowRight"
section.icon = get_theme_icon(icon_name, "EditorIcons")
func _setup_field_listeners():
_layer_section_header.button_down.connect(_on_layer_header_button_down)
_slice_section_header.button_down.connect(_on_slice_header_button_down)
_output_section_header.button_down.connect(_on_output_header_button_down)
_source_field.pressed.connect(_on_source_pressed)
_source_field.aseprite_file_dropped.connect(_on_source_aseprite_file_dropped)
_layer_field.data_fetcher = _fetch_layers
_layer_field.value_changed.connect(_on_layer_value_changed)
_slice_field.button_down.connect(_on_slice_button_down)
_slice_field.item_selected.connect(_on_slice_item_selected)
_out_folder_field.dir_dropped.connect(_on_out_dir_dropped)
_out_folder_field.pressed.connect(_on_out_folder_pressed)
_import_button.pressed.connect(_on_import_pressed)
_embed_field.pressed.connect(_on_embed_button_pressed)
func _on_layer_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_LAYER)
func _on_slice_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_SLICE)
func _on_output_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_OUTPUT)
func _fetch_layers():
if _source == "":
_show_message("Please. Select source file first.")
return
return _get_available_layers(ProjectSettings.globalize_path(_source))
func _on_layer_value_changed():
var layers: Array = _layer_field.get_selected_layers()
if layers.size() == 1:
_out_filename_label.text = _output_filename_prefix_text
_update_pending_fields()
return
_out_filename_label.text = _output_filename_text
_update_pending_fields()
func _on_slice_item_selected(index):
if index == 0:
_slice = ""
return
_slice = _slice_field.get_item_text(index)
_update_pending_fields()
func _on_slice_button_down():
if _source == "":
_show_message("Please, select source file first.")
return
var slices = _get_available_slices(ProjectSettings.globalize_path(_source))
var current = 0
_slice_field.clear()
_slice_field.add_item(_layer_default)
for s in slices:
if s == "":
continue
_slice_field.add_item(s)
if s == _slice:
current = _slice_field.get_item_count() - 1
_slice_field.select(current)
func _on_source_pressed():
_open_source_dialog()
func _get_current_config():
var child_config = _get_current_field_values()
var cfg := {
"source": _source,
"layers": _layer_field.get_selected_layers(),
"slice": _slice,
"o_folder": _output_folder,
"o_name": _out_filename_field.text,
"only_visible": _visible_layers_field.button_pressed,
"o_ex_p": _ex_pattern_field.text,
"embed_tex": _embed_field.button_pressed,
}
for c in child_config:
cfg[c] = child_config[c]
return cfg
##
## Save current import options to node metadata
##
func _save_config():
wizard_config.save_config(target_node, _get_current_config())
_update_pending_fields()
EditorInterface.mark_scene_as_unsaved()
func _get_import_options(default_folder: String):
return {
"output_folder": _output_folder if _output_folder != "" else default_folder,
"exception_pattern": _ex_pattern_field.text,
"only_visible_layers": _visible_layers_field.button_pressed,
"output_filename": _out_filename_field.text,
"layers": _layer_field.get_selected_layers(),
"embed_tex": _embed_field.button_pressed,
}
func _open_source_dialog():
_file_dialog_aseprite = _create_aseprite_file_selection()
get_parent().add_child(_file_dialog_aseprite)
if _source != "":
_file_dialog_aseprite.current_dir = ProjectSettings.globalize_path(_source.get_base_dir())
_file_dialog_aseprite.popup_centered_ratio()
func _create_aseprite_file_selection():
var file_dialog = EditorFileDialog.new()
file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE
file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
file_dialog.connect("file_selected",Callable(self,"_on_aseprite_file_selected"))
file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"]))
return file_dialog
func _on_aseprite_file_selected(path):
_set_source(ProjectSettings.localize_path(path))
_update_pending_fields()
_file_dialog_aseprite.queue_free()
func _on_source_aseprite_file_dropped(path):
_set_source(path)
_update_pending_fields()
## Helper method to populate field with values
func _populate_options_field(field: OptionButton, values: Array, current_name: String):
var current = 0
field.clear()
field.add_item("[all]")
for v in values:
if v == "":
continue
field.add_item(v)
if v == current_name:
current = field.get_item_count() - 1
field.select(current)
func _on_out_folder_pressed():
_output_folder_dialog = _create_output_folder_selection()
get_parent().add_child(_output_folder_dialog)
if _output_folder != _out_folder_default:
_output_folder_dialog.current_dir = _output_folder
_output_folder_dialog.popup_centered_ratio()
func _create_output_folder_selection():
var file_dialog = EditorFileDialog.new()
file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_DIR
file_dialog.access = EditorFileDialog.ACCESS_RESOURCES
file_dialog.connect("dir_selected",Callable(self,"_on_output_folder_selected"))
return file_dialog
func _on_output_folder_selected(path):
_set_out_folder(path)
_output_folder_dialog.queue_free()
_update_pending_fields()
func _on_out_dir_dropped(path):
_set_out_folder(path)
_update_pending_fields()
func _on_embed_button_pressed():
_handle_embed_visibility()
func _handle_embed_visibility():
if _embed_field.button_pressed:
_out_folder_container.hide()
_out_filename_container.hide()
else:
_out_folder_container.show()
_out_filename_container.show()
func _show_message(message: String):
var _warning_dialog = AcceptDialog.new()
get_parent().add_child(_warning_dialog)
_warning_dialog.dialog_text = message
_warning_dialog.popup_centered()
_warning_dialog.canceled.connect(_warning_dialog.queue_free)
_warning_dialog.confirmed.connect(_warning_dialog.queue_free)
func _notify_aseprite_error(aseprite_error_code):
var error = result_code.get_error_message(aseprite_error_code)
printerr(error)
_show_message(error)
func _handle_cleanup(aseprite_content, should_remove_spritesheet = false):
if config.should_remove_source_files():
DirAccess.remove_absolute(aseprite_content.data_file)
if should_remove_spritesheet:
DirAccess.remove_absolute(aseprite_content.sprite_sheet)
var import_file = "%s.import" % aseprite_content.sprite_sheet
if FileAccess.file_exists(import_file):
DirAccess.remove_absolute(import_file)
file_system.call_deferred("scan")
func _on_import_pressed():
if _importing:
return
_importing = true
if _source == "":
_show_message("Aseprite file not selected")
_importing = false
return
if get_tree().get_edited_scene_root().scene_file_path == "":
_show_message("Scene needs to be saved at least once before importing animations")
_importing = false
return
await _do_import()
_importing = false
$dock_fields.hide_source_change_warning()
EditorInterface.save_scene()
# This is a little bit leaky as this base scene contains fields only relevant to animation players.
# However, this is the simplest thing I can do without overcomplicating stuff.
func _hide_fields():
$dock_fields/VBoxContainer/modes.hide()
$dock_fields/VBoxContainer/animation_player.hide()
$dock_fields/VBoxContainer/extra/sections/animation.hide()
$dock_fields/VBoxContainer/extra/sections/animation_sf.hide()
func _on_dock_fields_revert_changes_requested() -> void:
$dock_fields.disable_change_notification = true
_do_not_update_pending = true
_interface_section_state[PENDING_CHANGES_KEY] = {}
wizard_config.save_interface_config(target_node, _interface_section_state)
_setup_config()
$dock_fields.disable_change_notification = false
$dock_fields.hide_fields_change_warning()
_do_not_update_pending = false
EditorInterface.mark_scene_as_unsaved()
func _on_dock_fields_field_changed() -> void:
_check_for_field_changes()
## this will be called before base class does its setup
func _pre_setup():
pass
## this will be called after base class setup is complete
func _setup():
pass
func _load_default_config():
pass
func _load_config(cfg: Dictionary):
pass
## Override to return available layers
func _get_available_layers(global_source_path: String) -> Array:
return []
## Override to return available slices
func _get_available_slices(global_source_path: String) -> Array:
return []
## Override this method for extra import options to add to node metadata
func _get_current_field_values() -> Dictionary:
return {}
func _do_import():
pass
func _show_specific_fields() -> void:
pass

View file

@ -0,0 +1 @@
uid://di76ywq167ex6

View file

@ -0,0 +1,85 @@
@tool
extends MarginContainer
signal revert_changes_requested
signal field_changed
@onready
var _source_changed_warning_container = $VBoxContainer/source_changed_warning
@onready
var _source_changed_warning_icon = $VBoxContainer/source_changed_warning/MarginContainer/HBoxContainer/Icon
@onready
var _fields_changed_warning_container = $VBoxContainer/fields_changed_warning
@onready
var _fields_changed_warning_icon = $VBoxContainer/fields_changed_warning/MarginContainer/VBoxContainer/HBoxContainer/Icon
var disable_change_notification := false
func _ready():
var sb = _source_changed_warning_container.get_theme_stylebox("panel")
var color = EditorInterface.get_editor_settings().get_setting("interface/theme/accent_color")
color.a = 0.2
sb.bg_color = color
_source_changed_warning_icon.texture = get_theme_icon("NodeInfo", "EditorIcons")
_fields_changed_warning_icon.texture = get_theme_icon("NodeInfo", "EditorIcons")
hide_source_change_warning()
hide_fields_change_warning()
func show_source_change_warning():
_source_changed_warning_container.show()
func hide_source_change_warning():
_source_changed_warning_container.hide()
func show_fields_change_warning():
_fields_changed_warning_container.show()
func hide_fields_change_warning():
_fields_changed_warning_container.hide()
func _on_revert_changes_button_up() -> void:
revert_changes_requested.emit()
func _on_ex_pattern_text_changed(_new_text: String) -> void:
_notify_change_with_debounce()
func _on_visible_layer_toggled(_toggled_on: bool) -> void:
_notify_change_with_debounce()
func _on_keep_length_toggled(_toggled_on: bool) -> void:
_notify_change_with_debounce()
func _on_anim_vis_toggled(_toggled_on: bool) -> void:
_notify_change_with_debounce()
func _on_line_edit_text_changed(_new_text: String) -> void:
_notify_change_with_debounce()
func _on_debounce_timer_timeout() -> void:
if not $DebounceTimer.is_stopped():
$DebounceTimer.stop()
$DebounceTimer.start()
func _notify_change_with_debounce():
if disable_change_notification:
return
field_changed.emit()
func _on_round_fps_toggled(_toggled_on: bool) -> void:
_notify_change_with_debounce()

View file

@ -0,0 +1 @@
uid://ddg74nfct8wf6

View file

@ -0,0 +1,469 @@
[gd_scene load_steps=12 format=3 uid="uid://uxm7b02wry10"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/dock_fields.gd" id="1_f3w00"]
[ext_resource type="PackedScene" uid="uid://x1f1t87m582u" path="res://addons/AsepriteWizard/interface/shared/animation_player_drop_button.tscn" id="1_gh7xg"]
[ext_resource type="PackedScene" uid="uid://dj1uo3blocr8e" path="res://addons/AsepriteWizard/interface/shared/source_drop_button.tscn" id="1_y4qrm"]
[ext_resource type="PackedScene" uid="uid://cwvgnm3o7eed2" path="res://addons/AsepriteWizard/interface/shared/dir_drop_button.tscn" id="2_pypqg"]
[ext_resource type="PackedScene" uid="uid://ddlr2qc0atcfm" path="res://addons/AsepriteWizard/interface/docks/multi_layer_select_field.tscn" id="4_1c7t4"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ptlkd"]
content_margin_left = 4.0
content_margin_top = 4.0
content_margin_right = 4.0
content_margin_bottom = 4.0
bg_color = Color(0.225, 0.225, 0.225, 0.6)
corner_radius_top_left = 3
corner_radius_top_right = 3
corner_radius_bottom_right = 3
corner_radius_bottom_left = 3
corner_detail = 5
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gu58o"]
bg_color = Color(0.53, 0.67, 0.89, 0.2)
border_width_left = 10
border_color = Color(0.8, 0.8, 0.8, 0)
corner_radius_top_left = 5
corner_radius_top_right = 5
corner_radius_bottom_right = 5
corner_radius_bottom_left = 5
[sub_resource type="Image" id="Image_r8qc8"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_wd1ne"]
image = SubResource("Image_r8qc8")
[sub_resource type="Image" id="Image_mnqfn"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_qqpl1"]
image = SubResource("Image_mnqfn")
[node name="dock_fields" type="MarginContainer"]
script = ExtResource("1_f3w00")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 2
[node name="title" type="Button" parent="VBoxContainer"]
layout_mode = 2
focus_mode = 0
theme_override_styles/normal = SubResource("StyleBoxFlat_ptlkd")
button_mask = 0
text = "Aseprite"
[node name="fields_changed_warning" type="PanelContainer" parent="VBoxContainer"]
visible = false
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_gu58o")
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/fields_changed_warning"]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_right = 10
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/fields_changed_warning/MarginContainer"]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/fields_changed_warning/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Icon" type="TextureRect" parent="VBoxContainer/fields_changed_warning/MarginContainer/VBoxContainer/HBoxContainer"]
modulate = Color(1, 1, 1, 0.486275)
custom_minimum_size = Vector2(16, 0)
layout_mode = 2
texture = SubResource("ImageTexture_wd1ne")
stretch_mode = 5
[node name="Label" type="Label" parent="VBoxContainer/fields_changed_warning/MarginContainer/VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "Configuration changed since last import"
autowrap_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/fields_changed_warning/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/margin_bottom = 4
[node name="Button" type="Button" parent="VBoxContainer/fields_changed_warning/MarginContainer/VBoxContainer/MarginContainer"]
layout_mode = 2
size_flags_horizontal = 8
text = "Revert Changes"
[node name="section_title" type="PanelContainer" parent="VBoxContainer"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/section_title"]
layout_mode = 2
alignment = 1
[node name="icon" type="TextureRect" parent="VBoxContainer/section_title/HBoxContainer"]
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
[node name="title" type="Label" parent="VBoxContainer/section_title/HBoxContainer"]
layout_mode = 2
text = "Aseprite"
horizontal_alignment = 1
[node name="modes" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
tooltip_text = "Import mode.
Animation mode (default): set spritesheet as texture and imports animations to the selected AnimationPlayer.
Image mode: Import only first frame and set as texture."
[node name="Label" type="Label" parent="VBoxContainer/modes"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Mode"
[node name="options" type="OptionButton" parent="VBoxContainer/modes"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
selected = 0
item_count = 2
popup/item_0/text = "Animation"
popup/item_1/text = "Image"
popup/item_1/id = 1
[node name="animation_player" type="HBoxContainer" parent="VBoxContainer" groups=["aw_animation_player_only"]]
layout_mode = 2
tooltip_text = "AnimationPlayer node where animations should be added to."
[node name="Label" type="Label" parent="VBoxContainer/animation_player"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "AnimationPlayer"
[node name="options" parent="VBoxContainer/animation_player" instance=ExtResource("1_gh7xg")]
layout_mode = 2
[node name="source" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
tooltip_text = "Location of the Aseprite (*.ase, *.aseprite) source file."
[node name="Label" type="Label" parent="VBoxContainer/source"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Aseprite File"
[node name="button" parent="VBoxContainer/source" instance=ExtResource("1_y4qrm")]
layout_mode = 2
[node name="source_changed_warning" type="PanelContainer" parent="VBoxContainer"]
visible = false
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_gu58o")
[node name="MarginContainer" type="MarginContainer" parent="VBoxContainer/source_changed_warning"]
layout_mode = 2
theme_override_constants/margin_left = 10
theme_override_constants/margin_right = 10
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer/source_changed_warning/MarginContainer"]
layout_mode = 2
[node name="Icon" type="TextureRect" parent="VBoxContainer/source_changed_warning/MarginContainer/HBoxContainer"]
modulate = Color(1, 1, 1, 0.486275)
custom_minimum_size = Vector2(16, 0)
layout_mode = 2
texture = SubResource("ImageTexture_wd1ne")
stretch_mode = 5
[node name="Label" type="Label" parent="VBoxContainer/source_changed_warning/MarginContainer/HBoxContainer"]
custom_minimum_size = Vector2(10, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "Source file changed since last import"
autowrap_mode = 2
[node name="extra" type="MarginContainer" parent="VBoxContainer"]
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="sections" type="VBoxContainer" parent="VBoxContainer/extra"]
layout_mode = 2
[node name="layers" type="VBoxContainer" parent="VBoxContainer/extra/sections"]
layout_mode = 2
[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/layers"]
layout_mode = 2
focus_mode = 0
text = "Layers"
icon = SubResource("ImageTexture_qqpl1")
alignment = 0
[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/layers"]
visible = false
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content"]
layout_mode = 2
[node name="layer" type="HBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content/content"]
layout_mode = 2
tooltip_text = "Aseprite layer to be used in the animation. By default all layers are included."
[node name="Label" type="Label" parent="VBoxContainer/extra/sections/layers/section_content/content/layer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Layer"
[node name="options" parent="VBoxContainer/extra/sections/layers/section_content/content/layer" instance=ExtResource("4_1c7t4")]
layout_mode = 2
[node name="ex_pattern" type="HBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content/content"]
layout_mode = 2
tooltip_text = "Exclude layers with name matching this pattern (regex)."
[node name="Label" type="Label" parent="VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Exclude Pattern"
[node name="LineEdit" type="LineEdit" parent="VBoxContainer/extra/sections/layers/section_content/content/ex_pattern"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
[node name="visible_layers" type="HBoxContainer" parent="VBoxContainer/extra/sections/layers/section_content/content"]
layout_mode = 2
tooltip_text = "If active, layers not visible in the source file won't be included in the final image."
[node name="Label" type="Label" parent="VBoxContainer/extra/sections/layers/section_content/content/visible_layers"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Only Visible Layers"
[node name="CheckBox" type="CheckBox" parent="VBoxContainer/extra/sections/layers/section_content/content/visible_layers"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "On"
[node name="slices" type="VBoxContainer" parent="VBoxContainer/extra/sections"]
layout_mode = 2
[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/slices"]
layout_mode = 2
focus_mode = 0
text = "Slices"
icon = SubResource("ImageTexture_qqpl1")
alignment = 0
[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/slices"]
visible = false
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/slices/section_content"]
layout_mode = 2
[node name="slice" type="HBoxContainer" parent="VBoxContainer/extra/sections/slices/section_content/content"]
layout_mode = 2
tooltip_text = "Aseprite slice to be used in the animation. By default, the whole file is included."
[node name="Label" type="Label" parent="VBoxContainer/extra/sections/slices/section_content/content/slice"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Slice"
[node name="options" type="OptionButton" parent="VBoxContainer/extra/sections/slices/section_content/content/slice"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
selected = 0
item_count = 1
popup/item_0/text = "[all]"
[node name="animation" type="VBoxContainer" parent="VBoxContainer/extra/sections" groups=["aw_animation_player_only"]]
layout_mode = 2
[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/animation"]
layout_mode = 2
focus_mode = 0
text = "Animation"
icon = SubResource("ImageTexture_qqpl1")
alignment = 0
[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/animation"]
visible = false
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/animation/section_content"]
layout_mode = 2
[node name="keep_length" type="HBoxContainer" parent="VBoxContainer/extra/sections/animation/section_content/content"]
layout_mode = 2
tooltip_text = "When this is active the animation length won't be adjusted if other properties were added and the resulting imported animation is shorter."
[node name="Label" type="Label" parent="VBoxContainer/extra/sections/animation/section_content/content/keep_length"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Keep Manual Animation Length"
[node name="CheckBox" type="CheckBox" parent="VBoxContainer/extra/sections/animation/section_content/content/keep_length"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "On"
[node name="auto_visible_track" type="HBoxContainer" parent="VBoxContainer/extra/sections/animation/section_content/content"]
layout_mode = 2
tooltip_text = "If active on import, this node will be set as hidden in every existing animation it is not part of"
[node name="Label" type="Label" parent="VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Hide When Unused"
[node name="CheckBox" type="CheckBox" parent="VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "On"
[node name="animation_sf" type="VBoxContainer" parent="VBoxContainer/extra/sections"]
layout_mode = 2
[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/animation_sf"]
layout_mode = 2
focus_mode = 0
text = "Animation"
icon = SubResource("ImageTexture_qqpl1")
alignment = 0
[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/animation_sf"]
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/animation_sf/section_content"]
layout_mode = 2
[node name="round_fps" type="HBoxContainer" parent="VBoxContainer/extra/sections/animation_sf/section_content/content"]
layout_mode = 2
tooltip_text = "Round FPS to the next integer number."
[node name="Label" type="Label" parent="VBoxContainer/extra/sections/animation_sf/section_content/content/round_fps"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Round FPS"
[node name="CheckBox" type="CheckBox" parent="VBoxContainer/extra/sections/animation_sf/section_content/content/round_fps"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
button_pressed = true
text = "On"
[node name="output" type="VBoxContainer" parent="VBoxContainer/extra/sections"]
layout_mode = 2
[node name="section_header" type="Button" parent="VBoxContainer/extra/sections/output"]
layout_mode = 2
focus_mode = 0
text = "Output"
icon = SubResource("ImageTexture_qqpl1")
alignment = 0
[node name="section_content" type="MarginContainer" parent="VBoxContainer/extra/sections/output"]
visible = false
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="content" type="VBoxContainer" parent="VBoxContainer/extra/sections/output/section_content"]
layout_mode = 2
[node name="embed" type="HBoxContainer" parent="VBoxContainer/extra/sections/output/section_content/content"]
layout_mode = 2
tooltip_text = "Embed texture in the SpriteFrames instead of creating an external file"
[node name="Label" type="Label" parent="VBoxContainer/extra/sections/output/section_content/content/embed"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Embed Texture"
[node name="CheckBox" type="CheckBox" parent="VBoxContainer/extra/sections/output/section_content/content/embed"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
button_pressed = true
text = "On"
[node name="out_folder" type="HBoxContainer" parent="VBoxContainer/extra/sections/output/section_content/content"]
visible = false
layout_mode = 2
tooltip_text = "Location where the spritesheet file should be saved."
[node name="Label" type="Label" parent="VBoxContainer/extra/sections/output/section_content/content/out_folder"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Folder"
[node name="button" parent="VBoxContainer/extra/sections/output/section_content/content/out_folder" instance=ExtResource("2_pypqg")]
layout_mode = 2
[node name="out_filename" type="HBoxContainer" parent="VBoxContainer/extra/sections/output/section_content/content"]
visible = false
layout_mode = 2
tooltip_text = "Base filename for spritesheet. In case the layer option is used, this works as a prefix to the layer name."
[node name="Label" type="Label" parent="VBoxContainer/extra/sections/output/section_content/content/out_filename"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "File Name"
[node name="LineEdit" type="LineEdit" parent="VBoxContainer/extra/sections/output/section_content/content/out_filename"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
[node name="import" type="Button" parent="VBoxContainer"]
layout_mode = 2
text = "Import"
[node name="DebounceTimer" type="Timer" parent="."]
wait_time = 0.6
one_shot = true
[connection signal="button_up" from="VBoxContainer/fields_changed_warning/MarginContainer/VBoxContainer/MarginContainer/Button" to="." method="_on_revert_changes_button_up"]
[connection signal="text_changed" from="VBoxContainer/extra/sections/layers/section_content/content/ex_pattern/LineEdit" to="." method="_on_ex_pattern_text_changed"]
[connection signal="toggled" from="VBoxContainer/extra/sections/layers/section_content/content/visible_layers/CheckBox" to="." method="_on_visible_layer_toggled"]
[connection signal="toggled" from="VBoxContainer/extra/sections/animation/section_content/content/keep_length/CheckBox" to="." method="_on_keep_length_toggled"]
[connection signal="toggled" from="VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track/CheckBox" to="." method="_on_anim_vis_toggled"]
[connection signal="toggled" from="VBoxContainer/extra/sections/animation_sf/section_content/content/round_fps/CheckBox" to="." method="_on_round_fps_toggled"]
[connection signal="toggled" from="VBoxContainer/extra/sections/output/section_content/content/embed/CheckBox" to="." method="_on_round_fps_toggled"]
[connection signal="text_changed" from="VBoxContainer/extra/sections/output/section_content/content/out_filename/LineEdit" to="." method="_on_line_edit_text_changed"]
[connection signal="timeout" from="DebounceTimer" to="." method="_on_debounce_timer_timeout"]

View file

@ -0,0 +1,81 @@
@tool
extends HBoxContainer
signal add_pressed
signal removed_pressed
signal data_requested
signal value_changed(value: String)
var _value: String = ""
var is_only_field := false
func _ready():
$add_btn.icon = get_theme_icon("Add", "EditorIcons")
$remove_btn.icon = get_theme_icon("Remove", "EditorIcons")
func set_add_button(visibility: bool) -> void:
$add_btn.visible = visibility
func set_remove_button(visibility: bool) -> void:
$remove_btn.visible = visibility
func _on_remove_btn_pressed() -> void:
removed_pressed.emit()
func _on_add_btn_pressed() -> void:
add_pressed.emit()
func _on_options_button_down() -> void:
data_requested.emit()
func _on_options_item_selected(index: int) -> void:
if index == 0:
_value = ""
else:
_value = $options.get_item_text(index)
value_changed.emit(_value)
func populate_field(option_values):
var current = 0
$options.clear()
set_default_item()
for v in option_values:
if v == "":
continue
$options.add_item(v)
if v == _value:
current = $options.get_item_count() - 1
$options.select(current)
func set_value(new_value: String) -> void:
_value = new_value
$options.clear()
if new_value == "":
set_default_item()
else:
$options.add_item(_value)
func get_value():
return _value
func clear_options():
$options.clear()
set_default_item()
$options.select(0)
func set_default_item():
$options.add_item("[all]" if is_only_field else "Select layer...")

View file

@ -0,0 +1 @@
uid://v3yh7cuspy4f

View file

@ -0,0 +1,43 @@
[gd_scene load_steps=4 format=3 uid="uid://b8ricawxf4kxs"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/layer_select_field.gd" id="1_6ya8h"]
[sub_resource type="Image" id="Image_g7kfe"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_t5qui"]
image = SubResource("Image_g7kfe")
[node name="layer_fields" type="HBoxContainer"]
script = ExtResource("1_6ya8h")
[node name="options" type="OptionButton" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
selected = 0
item_count = 1
popup/item_0/text = "[all]"
[node name="add_btn" type="Button" parent="."]
visible = false
layout_mode = 2
tooltip_text = "Add another layer"
icon = SubResource("ImageTexture_t5qui")
[node name="remove_btn" type="Button" parent="."]
visible = false
layout_mode = 2
tooltip_text = "Remove layer"
icon = SubResource("ImageTexture_t5qui")
[connection signal="button_down" from="options" to="." method="_on_options_button_down"]
[connection signal="item_selected" from="options" to="." method="_on_options_item_selected"]
[connection signal="pressed" from="add_btn" to="." method="_on_add_btn_pressed"]
[connection signal="pressed" from="remove_btn" to="." method="_on_remove_btn_pressed"]

View file

@ -0,0 +1,116 @@
@tool
extends VBoxContainer
const LayerField = preload("./layer_select_field.tscn")
signal value_changed
var data_fetcher: Callable
func set_selected_layers(layers: Array):
for c in get_children():
c.queue_free()
if layers.size() > 1:
var last
for layer in layers:
var field = LayerField.instantiate()
field.set_remove_button(true)
add_child(field)
field.set_value(layer)
_hook_signals(field)
last = field
last.set_add_button(true)
return
var field = LayerField.instantiate()
field.is_only_field = true
add_child(field)
if layers.is_empty():
field.set_value("")
else:
field.set_value(layers[0])
field.set_add_button(true)
_hook_signals(field)
func get_selected_layers() -> Array:
return _get_field_values()
func _get_field_values() -> Array:
var values = []
for c in get_children():
var v = c.get_value()
if v != "" and not values.has(v):
values.push_back(v)
return values
func _hook_signals(field: Control):
field.add_pressed.connect(_on_add_pressed.bind(field))
field.removed_pressed.connect(_on_removed_pressed.bind(field))
field.data_requested.connect(_on_data_requested.bind(field))
field.value_changed.connect(_on_value_changed.bind(field))
func _on_add_pressed(field: Control) -> void:
field.is_only_field = false
field.set_add_button(false)
field.set_remove_button(true)
var new_field = LayerField.instantiate()
add_child(new_field)
new_field.set_add_button(true)
new_field.set_remove_button(true)
new_field.clear_options()
_hook_signals(new_field)
func _on_removed_pressed(field: Control) -> void:
var count := get_child_count()
var node_position := field.get_index()
var should_notify_change: bool = field.get_value() != ""
remove_child(field)
field.queue_free()
if should_notify_change:
value_changed.emit()
if count == 2:
for c in get_children():
if c != field:
var last_node = c
last_node.set_add_button(true)
last_node.set_remove_button(false)
last_node.is_only_field = true
elif (count - 1) == node_position:
get_child(count - 2).set_add_button(true)
func _on_data_requested(field: Control) -> void:
var layers = data_fetcher.call()
if layers == null:
return
var selected_layers = get_selected_layers()
var valid_values := []
for l in layers:
if not selected_layers.has(l) or l == field.get_value():
valid_values.push_back(l)
field.populate_field(valid_values)
func _on_value_changed(value: String, field: Control) -> void:
if value != "" and field.get_index() == get_child_count() - 1:
field.set_add_button(true)
value_changed.emit()

View file

@ -0,0 +1 @@
uid://bh0x5rm6qj0me

View file

@ -0,0 +1,13 @@
[gd_scene load_steps=3 format=3 uid="uid://ddlr2qc0atcfm"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/multi_layer_select_field.gd" id="1_h2oy5"]
[ext_resource type="PackedScene" uid="uid://b8ricawxf4kxs" path="res://addons/AsepriteWizard/interface/docks/layer_select_field.tscn" id="2_75fqs"]
[node name="layer_multiselect" type="VBoxContainer"]
offset_right = 40.0
offset_bottom = 40.0
size_flags_horizontal = 3
script = ExtResource("1_h2oy5")
[node name="layer_fields" parent="." instance=ExtResource("2_75fqs")]
layout_mode = 2

View file

@ -0,0 +1,14 @@
@tool
extends EditorInspectorPlugin
const APInspectorDock = preload("./sprite_inspector_dock.tscn")
func _can_handle(object):
return object is Sprite2D || object is Sprite3D || object is TextureRect
func _parse_end(object):
var dock = APInspectorDock.instantiate()
dock.target_node = object
add_custom_control(dock)

View file

@ -0,0 +1 @@
uid://bq8chpgknl5ii

View file

@ -0,0 +1,262 @@
@tool
extends "../base_inspector_dock.gd"
const AnimationCreator = preload("../../../creators/animation_player/animation_creator.gd")
const SpriteAnimationCreator = preload("../../../creators/animation_player/sprite_animation_creator.gd")
const TextureRectAnimationCreator = preload("../../../creators/animation_player/texture_rect_animation_creator.gd")
const StaticTextureCreator = preload("../../../creators/static_texture/texture_creator.gd")
enum ImportMode {
ANIMATION = 0,
IMAGE = 1
}
var animation_creator: AnimationCreator
var static_texture_creator: StaticTextureCreator
var _import_mode = -1
var _animation_player_path: String
@onready var _import_mode_options_field := $dock_fields/VBoxContainer/modes/options as OptionButton
@onready var _animation_player_field := $dock_fields/VBoxContainer/animation_player/options as OptionButton
@onready var _animation_player_container := $dock_fields/VBoxContainer/animation_player as HBoxContainer
# animation
@onready var _animation_section := $dock_fields/VBoxContainer/extra/sections/animation as VBoxContainer
@onready var _animation_section_header := $dock_fields/VBoxContainer/extra/sections/animation/section_header as Button
@onready var _animation_section_container := $dock_fields/VBoxContainer/extra/sections/animation/section_content as MarginContainer
@onready var _cleanup_hide_unused_nodes := $dock_fields/VBoxContainer/extra/sections/animation/section_content/content/auto_visible_track/CheckBox as CheckBox
@onready var _keep_length := $dock_fields/VBoxContainer/extra/sections/animation/section_content/content/keep_length/CheckBox as CheckBox
const INTERFACE_SECTION_KEY_ANIMATION = "animation_section"
func _pre_setup():
_expandable_sections[INTERFACE_SECTION_KEY_ANIMATION] = { "header": _animation_section_header, "content": _animation_section_container}
func _setup():
if target_node is Sprite2D || target_node is Sprite3D:
animation_creator = SpriteAnimationCreator.new()
if target_node is TextureRect:
animation_creator = TextureRectAnimationCreator.new()
static_texture_creator = StaticTextureCreator.new()
_setup_animation_fields_listeners()
func _load_config(cfg):
if cfg.has("player"):
_animation_player_field.clear()
_set_animation_player(cfg.player)
_cleanup_hide_unused_nodes.button_pressed = cfg.get("set_vis_track", config.is_set_visible_track_automatically_enabled())
_keep_length.button_pressed = cfg.get("keep_anim_length", false)
_set_import_mode(int(cfg.get("i_mode", 0)))
func _load_default_config():
_cleanup_hide_unused_nodes.button_pressed = config.is_set_visible_track_automatically_enabled()
_set_import_mode(0)
func _set_animation_player(player):
_animation_player_path = player
_animation_player_field.add_item(_animation_player_path)
func _set_import_mode(import_mode):
if _import_mode == import_mode:
return
_import_mode = import_mode
var index = _import_mode_options_field.get_item_index(import_mode)
_import_mode_options_field.select(index)
_handle_import_mode()
func _handle_import_mode():
match _import_mode:
ImportMode.ANIMATION:
_animation_player_container.show()
_animation_section.show()
ImportMode.IMAGE:
_animation_player_container.hide()
_animation_section.hide()
func _setup_animation_fields_listeners():
_animation_section_header.button_down.connect(_on_animation_header_button_down)
_animation_player_field.node_dropped.connect(_on_animation_player_node_dropped)
_animation_player_field.button_down.connect(_on_animation_player_button_down)
_animation_player_field.item_selected.connect(_on_animation_player_item_selected)
_import_mode_options_field.item_selected.connect(_on_modes_item_selected)
func _on_animation_player_button_down():
_refresh_animation_players()
func _refresh_animation_players():
var animation_players = []
var root = get_tree().get_edited_scene_root()
_find_animation_players(root, root, animation_players)
var current = 0
_animation_player_field.clear()
_animation_player_field.add_item("[empty]")
for ap in animation_players:
_animation_player_field.add_item(ap)
if ap.get_concatenated_names() == _animation_player_path:
current = _animation_player_field.get_item_count() - 1
_animation_player_field.select(current)
func _find_animation_players(root: Node, node: Node, players: Array):
if node is AnimationPlayer:
players.push_back(root.get_path_to(node))
for c in node.get_children():
_find_animation_players(root, c, players)
func _on_animation_player_item_selected(index):
if index == 0:
_animation_player_path = ""
return
_animation_player_path = _animation_player_field.get_item_text(index)
#_save_config()
_update_pending_fields()
func _do_import():
if _import_mode == ImportMode.IMAGE:
await _import_static()
return
await _import_for_animation_player()
##
## Import aseprite animations to target AnimationPlayer and set
## spritesheet as the node's texture
##
func _import_for_animation_player():
var root = get_tree().get_edited_scene_root()
if _animation_player_path == "" or not root.has_node(_animation_player_path):
_show_message("AnimationPlayer not found")
_importing = false
return
var source_path = ProjectSettings.globalize_path(_source)
var options = _get_import_options(root.scene_file_path.get_base_dir())
_save_config()
var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options)
if not aseprite_output.is_ok:
_notify_aseprite_error(aseprite_output.code)
return
file_system.scan()
await file_system.filesystem_changed
var anim_options = {
"keep_anim_length": _keep_length.button_pressed,
"cleanup_hide_unused_nodes": _cleanup_hide_unused_nodes.button_pressed,
"slice": _slice,
"should_create_portable_texture": _embed_field.button_pressed,
}
animation_creator.create_animations(target_node, root.get_node(_animation_player_path), aseprite_output.content, anim_options)
_importing = false
wizard_config.set_source_hash(target_node, FileAccess.get_md5(source_path))
_handle_cleanup(aseprite_output.content, _embed_field.button_pressed)
##
## Import first frame from aseprite file as node texture
##
func _import_static():
var source_path = ProjectSettings.globalize_path(_source)
var root = get_tree().get_edited_scene_root()
var options = _get_import_options(root.scene_file_path.get_base_dir())
options["first_frame_only"] = true
_save_config()
var aseprite_output = _aseprite_file_exporter.generate_aseprite_file(source_path, options)
if not aseprite_output.is_ok:
_notify_aseprite_error(aseprite_output.code)
return
file_system.scan()
await file_system.filesystem_changed
static_texture_creator.load_texture(target_node, aseprite_output.content, {
"slice": _slice,
"should_create_portable_texture": _embed_field.button_pressed,
})
_importing = false
wizard_config.set_source_hash(target_node, FileAccess.get_md5(source_path))
_handle_cleanup(aseprite_output.content, _embed_field.button_pressed)
func _get_current_field_values() -> Dictionary:
var cfg := {
"i_mode": _import_mode,
"player": _animation_player_path,
"keep_anim_length": _keep_length.button_pressed,
}
if _cleanup_hide_unused_nodes.button_pressed != config.is_set_visible_track_automatically_enabled():
cfg["set_vis_track"] = _cleanup_hide_unused_nodes.button_pressed
return cfg
func _get_available_layers(global_source_path: String) -> Array:
return animation_creator.list_layers(global_source_path)
func _get_available_slices(global_source_path: String) -> Array:
return animation_creator.list_slices(global_source_path)
func _on_animation_player_node_dropped(node_path):
var node = get_node(node_path)
var root = get_tree().get_edited_scene_root()
_animation_player_path = root.get_path_to(node)
for i in range(_animation_player_field.get_item_count()):
if _animation_player_field.get_item_text(i) == _animation_player_path:
_animation_player_field.select(i)
break
#_save_config()
_update_pending_fields()
func _on_modes_item_selected(index):
var id = _import_mode_options_field.get_item_id(index)
_import_mode = id
_handle_import_mode()
func _on_animation_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_ANIMATION)
func _show_specific_fields():
_import_mode_options_field.get_parent().show()
_animation_player_container.show()
_animation_section.show()

View file

@ -0,0 +1 @@
uid://d7s0sepqaskq

View file

@ -0,0 +1,15 @@
[gd_scene load_steps=3 format=3 uid="uid://biyshalfalqqw"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/sprite/sprite_inspector_dock.gd" id="1_ku6wj"]
[ext_resource type="PackedScene" uid="uid://uxm7b02wry10" path="res://addons/AsepriteWizard/interface/docks/dock_fields.tscn" id="2_wkqx4"]
[node name="sprite_inspector_dock" type="PanelContainer"]
offset_right = 14.0
offset_bottom = 14.0
script = ExtResource("1_ku6wj")
[node name="dock_fields" parent="." instance=ExtResource("2_wkqx4")]
layout_mode = 2
[connection signal="field_changed" from="dock_fields" to="." method="_on_dock_fields_field_changed"]
[connection signal="revert_changes_requested" from="dock_fields" to="." method="_on_dock_fields_revert_changes_requested"]

View file

@ -0,0 +1,39 @@
@tool
extends TabContainer
signal close_requested
const WizardWindow = preload("./as_wizard_window.tscn")
func _ready():
$Import.close_requested.connect(emit_signal.bind("close_requested"))
$Import.import_success.connect(_on_import_success)
$History.request_edit.connect(_on_edit_request)
$History.request_import.connect(_on_import_request)
$ImportedSpriteFrames.import_success.connect($History.add_entry)
self.set_tab_title(1, "Imported Resources")
func _on_AsWizardDockContainer_tab_changed(tab: int):
match tab:
1:
$ImportedSpriteFrames.init_resources()
2:
$History.reload()
func _on_edit_request(import_cfg: Dictionary):
$Import.load_import_config(import_cfg)
self.current_tab = 0
func _on_import_request(import_cfg: Dictionary):
$Import.load_import_config(import_cfg)
$Import.trigger_import()
func _on_import_success(settings: Dictionary):
$ImportedSpriteFrames.init_resources()
$ImportedSpriteFrames.reload_tree()
$History.add_entry(settings)

View file

@ -0,0 +1 @@
uid://ds73e5bh5alse

View file

@ -0,0 +1,28 @@
[gd_scene load_steps=5 format=3 uid="uid://b844j1tk3vxer"]
[ext_resource type="PackedScene" uid="uid://c5dwobjd34w3p" path="res://addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.tscn" id="1"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/as_wizard_dock_container.gd" id="2"]
[ext_resource type="PackedScene" uid="uid://cyoin5ncul0fm" path="res://addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.tscn" id="3"]
[ext_resource type="PackedScene" uid="uid://bfhsdslj8kt7b" path="res://addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.tscn" id="3_25qb4"]
[node name="AsWizardDockContainer" type="TabContainer"]
offset_right = 281.0
offset_bottom = 36.0
tab_alignment = 2
use_hidden_tabs_for_min_size = true
script = ExtResource("2")
[node name="Import" parent="." instance=ExtResource("1")]
layout_mode = 2
tooltip_text = "SpriteFrames Importer"
[node name="ImportedSpriteFrames" parent="." instance=ExtResource("3_25qb4")]
visible = false
layout_mode = 2
metadata/_tab_name = "Imported Resources"
[node name="History" parent="." instance=ExtResource("3")]
visible = false
layout_mode = 2
[connection signal="tab_changed" from="." to="." method="_on_AsWizardDockContainer_tab_changed"]

View file

@ -0,0 +1,288 @@
@tool
extends PanelContainer
signal close_requested
signal import_success(file_settings)
const result_code = preload("../../../config/result_codes.gd")
var _config = preload("../../../config/config.gd").new()
var _import_helper = preload("./wizard_import_helper.gd").new()
var _file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
var _file_dialog_aseprite: EditorFileDialog
var _output_folder_dialog: EditorFileDialog
var _warning_dialog: AcceptDialog
@onready var _layer_section_btn: Button = $container/options/layer_section/header/section_header
@onready var _layer_section_content: MarginContainer = $container/options/layer_section/section_content
@onready var _animation_section_btn: Button = $container/options/animation_section/header/section_header
@onready var _animation_section_content: MarginContainer = $container/options/animation_section/section_content
@onready var _output_section_btn: Button = $container/options/output_section/header/section_header
@onready var _output_section_content: MarginContainer = $container/options/output_section/section_content
@onready var _file_location_field: LineEdit = $container/options/file_location/HBoxContainer/file_location_path
@onready var _output_folder_field: LineEdit = $container/options/output_section/section_content/items/output_folder/HBoxContainer/file_location_path
@onready var _exception_pattern_field: LineEdit = $container/options/layer_section/section_content/items/exclude_pattern/pattern
@onready var _split_mode_field: CheckBox = $container/options/layer_section/section_content/items/split_layers/field
@onready var _only_visible_layers_field: CheckBox = $container/options/layer_section/section_content/items/visible_layers/field
@onready var _custom_name_field: LineEdit = $container/options/output_section/section_content/items/custom_filename/pattern
@onready var _round_fps_field: CheckBox = $container/options/animation_section/section_content/items/round_fps/field
@onready var _do_not_create_res_field: CheckBox = $container/options/output_section/section_content/items/disable_resource_creation/field
const INTERFACE_SECTION_KEY_LAYER = "layer_section"
const INTERFACE_SECTION_KEY_ANIMATION = "animation_section"
const INTERFACE_SECTION_KEY_OUTPUT = "output_section"
@onready var _expandable_sections = {
INTERFACE_SECTION_KEY_LAYER: { "header": _layer_section_btn, "content": _layer_section_content},
INTERFACE_SECTION_KEY_ANIMATION: { "header": _animation_section_btn, "content": _animation_section_content},
INTERFACE_SECTION_KEY_OUTPUT: { "header": _output_section_btn, "content": _output_section_content},
}
var _interface_section_state = {}
func _ready():
_configure_sections()
_load_persisted_config()
func _exit_tree():
if is_instance_valid(_file_dialog_aseprite):
_file_dialog_aseprite.queue_free()
if is_instance_valid(_output_folder_dialog):
_output_folder_dialog.queue_free()
if is_instance_valid(_warning_dialog):
_warning_dialog.queue_free()
func _init_aseprite_file_selection_dialog():
_file_dialog_aseprite = _create_aseprite_file_selection()
get_parent().get_parent().add_child(_file_dialog_aseprite)
func _init_output_folder_selection_dialog():
_output_folder_dialog = _create_outuput_folder_selection()
get_parent().get_parent().add_child(_output_folder_dialog)
func _init_warning_dialog():
_warning_dialog = AcceptDialog.new()
_warning_dialog.exclusive = false
get_parent().get_parent().add_child(_warning_dialog)
func _load_persisted_config() -> void:
var cfg = _load_last_import_cfg()
_load_config(cfg)
func _load_config(cfg: Dictionary) -> void:
_split_mode_field.button_pressed = cfg.split_layers
_only_visible_layers_field.button_pressed = cfg.only_visible_layers
_exception_pattern_field.text = cfg.layer_exclusion_pattern
_custom_name_field.text = cfg.output_name
_file_location_field.text = cfg.source_file
_do_not_create_res_field.button_pressed = cfg.do_not_create_resource
_round_fps_field.button_pressed = cfg.get("should_round_fps", true)
_output_folder_field.text = cfg.output_location if cfg.output_location != "" else "res://"
func _save_config() -> void:
_config.set_standalone_spriteframes_last_import_config(_get_field_values())
func _get_field_values() -> Dictionary:
return {
"split_layers": _split_mode_field.button_pressed,
"only_visible_layers": _only_visible_layers_field.button_pressed,
"layer_exclusion_pattern": _exception_pattern_field.text,
"output_name": _custom_name_field.text,
"source_file": _file_location_field.text,
"do_not_create_resource": _do_not_create_res_field.button_pressed,
"output_location": _output_folder_field.text if _output_folder_field.text != "" else "res://",
"should_round_fps": _round_fps_field.button_pressed,
}
func _clear_config() -> void:
_config.clear_standalone_spriteframes_last_import_config()
var default = _get_default_config()
_load_config(default)
## This is used by the other tabs to set the form fields
func load_import_config(field_values: Dictionary):
_split_mode_field.button_pressed = field_values.split_layers
_only_visible_layers_field.button_pressed = field_values.only_visible_layers
_exception_pattern_field.text = field_values.layer_exclusion_pattern
_custom_name_field.text = field_values.output_name
_file_location_field.text = field_values.source_file
_do_not_create_res_field.button_pressed = field_values.do_not_create_resource
_output_folder_field.text = field_values.output_location
_round_fps_field.button_pressed = field_values.get("should_round_fps", true)
func _open_aseprite_file_selection_dialog():
if not is_instance_valid(_file_dialog_aseprite):
_init_aseprite_file_selection_dialog()
var current_selection = _file_location_field.text
if current_selection != "":
_file_dialog_aseprite.current_dir = current_selection.get_base_dir()
_file_dialog_aseprite.popup_centered_ratio()
func _open_output_folder_selection_dialog():
if not is_instance_valid(_output_folder_dialog):
_init_output_folder_selection_dialog()
var current_selection = _output_folder_field.text
if current_selection != "":
_output_folder_dialog.current_dir = current_selection
_output_folder_dialog.popup_centered_ratio()
func _create_aseprite_file_selection():
var file_dialog = EditorFileDialog.new()
file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_FILE
file_dialog.access = EditorFileDialog.ACCESS_FILESYSTEM
file_dialog.connect("file_selected", _on_aseprite_file_selected)
file_dialog.set_filters(PackedStringArray(["*.ase","*.aseprite"]))
return file_dialog
func _create_outuput_folder_selection():
var file_dialog = EditorFileDialog.new()
file_dialog.file_mode = EditorFileDialog.FILE_MODE_OPEN_DIR
file_dialog.access = EditorFileDialog.ACCESS_RESOURCES
file_dialog.connect("dir_selected", _on_output_folder_selected)
return file_dialog
func _on_aseprite_file_selected(path):
var localized_path = ProjectSettings.localize_path(path)
_file_location_field.text = localized_path
func _on_output_folder_selected(path):
_output_folder_field.text = path
func _on_next_btn_up():
var aseprite_file = _file_location_field.text
var fields = _get_field_values()
var exit_code = await _import_helper.import_and_create_resources(aseprite_file, _get_field_values())
if exit_code != OK:
_show_error(exit_code)
return
emit_signal("import_success", fields)
if _config.should_remove_source_files():
_file_system.call_deferred("scan")
_show_import_success_message()
func trigger_import():
_on_next_btn_up()
func _on_close_btn_up():
_close_window()
func _close_window():
_save_config()
self.emit_signal("close_requested")
func _on_clear_button_up():
_clear_config()
func _show_error(code: int):
_show_error_message(result_code.get_error_message(code))
func _show_error_message(message: String):
if not is_instance_valid(_warning_dialog):
_init_warning_dialog()
_warning_dialog.dialog_text = "Error: %s" % message
_warning_dialog.popup_centered()
func _show_import_success_message():
if not is_instance_valid(_warning_dialog):
_init_warning_dialog()
_warning_dialog.dialog_text = "Aseprite import succeeded"
_warning_dialog.popup_centered()
_save_config()
func _configure_sections():
for key in _expandable_sections:
_adjust_section_visibility(key)
func _adjust_section_visibility(key: String) -> void:
var section = _expandable_sections[key]
var is_visible = _interface_section_state.get(key, true)
_adjust_icon(section.header, is_visible)
section.content.visible = is_visible
func _adjust_icon(section: Button, is_visible: bool = true) -> void:
var icon_name = "GuiTreeArrowDown" if is_visible else "GuiTreeArrowRight"
section.icon = get_theme_icon(icon_name, "EditorIcons")
func _toggle_section_visibility(key: String) -> void:
_interface_section_state[key] = not _interface_section_state.get(key, true)
_adjust_section_visibility(key)
func _on_layer_section_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_LAYER)
func _on_output_section_header_button_down():
_toggle_section_visibility(INTERFACE_SECTION_KEY_OUTPUT)
func _on_animation_header_button_down() -> void:
_toggle_section_visibility(INTERFACE_SECTION_KEY_ANIMATION)
func _load_last_import_cfg() -> Dictionary:
var cfg = _config.get_standalone_spriteframes_last_import_config()
if cfg.is_empty():
return _get_default_config()
return cfg
func _get_default_config() -> Dictionary:
return {
"split_layers": false,
"only_visible_layers": _config.should_include_only_visible_layers_by_default(),
"layer_exclusion_pattern": _config.get_default_exclusion_pattern(),
"output_name": "",
"source_file": "",
"do_not_create_resource": false,
"output_location": "res://",
"should_round_fps": true,
}

View file

@ -0,0 +1 @@
uid://b7fmruj4lfcae

View file

@ -0,0 +1,233 @@
[gd_scene load_steps=4 format=3 uid="uid://c5dwobjd34w3p"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/as_wizard_window.gd" id="1"]
[sub_resource type="Image" id="Image_1ofm5"]
data = {
"data": PackedByteArray(255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 94, 94, 127, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 231, 255, 94, 94, 54, 255, 94, 94, 57, 255, 93, 93, 233, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 93, 93, 41, 255, 255, 255, 0, 255, 255, 255, 0, 255, 97, 97, 42, 255, 93, 93, 233, 255, 93, 93, 232, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 44, 255, 255, 255, 0, 255, 97, 97, 42, 255, 97, 97, 42, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 96, 96, 45, 255, 93, 93, 235, 255, 94, 94, 234, 255, 95, 95, 43, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 93, 93, 235, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 233, 255, 95, 95, 59, 255, 96, 96, 61, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 93, 93, 255, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0, 255, 255, 255, 0),
"format": "RGBA8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="ImageTexture_r01jd"]
image = SubResource("Image_1ofm5")
[node name="ASWizardWindow" type="PanelContainer"]
offset_right = 600.0
offset_bottom = 600.0
size_flags_horizontal = 3
size_flags_vertical = 0
script = ExtResource("1")
[node name="container" type="MarginContainer" parent="."]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="options" type="VBoxContainer" parent="container"]
layout_mode = 2
theme_override_constants/separation = 15
[node name="file_location" type="VBoxContainer" parent="container/options"]
layout_mode = 2
[node name="file_location_label" type="Label" parent="container/options/file_location"]
layout_mode = 2
tooltip_text = "Location of the Aseprite *.ase source file"
mouse_filter = 1
text = "Aseprite File Location:"
clip_text = true
[node name="HBoxContainer" type="HBoxContainer" parent="container/options/file_location"]
layout_mode = 2
[node name="file_location_path" type="LineEdit" parent="container/options/file_location/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "res://test/llama_import.aseprite"
caret_blink = true
[node name="file_location_btn" type="Button" parent="container/options/file_location/HBoxContainer"]
layout_mode = 2
text = "Select"
[node name="layer_section" type="VBoxContainer" parent="container/options"]
layout_mode = 2
[node name="header" type="HBoxContainer" parent="container/options/layer_section"]
layout_mode = 2
[node name="section_header" type="Button" parent="container/options/layer_section/header"]
layout_mode = 2
text = "Layers"
icon = SubResource("ImageTexture_r01jd")
flat = true
alignment = 0
[node name="HSeparator" type="HSeparator" parent="container/options/layer_section/header"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="section_content" type="MarginContainer" parent="container/options/layer_section"]
layout_mode = 2
theme_override_constants/margin_left = 20
[node name="items" type="VBoxContainer" parent="container/options/layer_section/section_content"]
layout_mode = 2
[node name="exclude_pattern" type="VBoxContainer" parent="container/options/layer_section/section_content/items"]
layout_mode = 2
[node name="label" type="Label" parent="container/options/layer_section/section_content/items/exclude_pattern"]
layout_mode = 2
tooltip_text = "If layer name matches this pattern, it won't be exported."
mouse_filter = 1
text = "Exclude layers with name matching pattern:"
[node name="pattern" type="LineEdit" parent="container/options/layer_section/section_content/items/exclude_pattern"]
layout_mode = 2
text = "_$"
caret_blink = true
[node name="split_layers" type="HBoxContainer" parent="container/options/layer_section/section_content/items"]
layout_mode = 2
[node name="field" type="CheckBox" parent="container/options/layer_section/section_content/items/split_layers"]
layout_mode = 2
tooltip_text = "If selected, one resource will be created for each layer.
If not selected, layers will be merged and exported as one SpriteSheet."
text = "Split layers in multiple resources"
[node name="visible_layers" type="HBoxContainer" parent="container/options/layer_section/section_content/items"]
layout_mode = 2
[node name="field" type="CheckBox" parent="container/options/layer_section/section_content/items/visible_layers"]
layout_mode = 2
tooltip_text = "If selected, layers not visible in the source file won't be included in the final image."
text = "Only include visible layers"
[node name="animation_section" type="VBoxContainer" parent="container/options"]
layout_mode = 2
[node name="header" type="HBoxContainer" parent="container/options/animation_section"]
layout_mode = 2
[node name="section_header" type="Button" parent="container/options/animation_section/header"]
layout_mode = 2
text = "Animation"
icon = SubResource("ImageTexture_r01jd")
flat = true
alignment = 0
[node name="section_content" type="MarginContainer" parent="container/options/animation_section"]
layout_mode = 2
theme_override_constants/margin_left = 20
[node name="items" type="VBoxContainer" parent="container/options/animation_section/section_content"]
layout_mode = 2
[node name="round_fps" type="HBoxContainer" parent="container/options/animation_section/section_content/items"]
layout_mode = 2
[node name="field" type="CheckBox" parent="container/options/animation_section/section_content/items/round_fps"]
layout_mode = 2
tooltip_text = "If selected, rounds FPS to next integer"
button_pressed = true
text = "Round FPS"
[node name="output_section" type="VBoxContainer" parent="container/options"]
layout_mode = 2
[node name="header" type="HBoxContainer" parent="container/options/output_section"]
layout_mode = 2
[node name="section_header" type="Button" parent="container/options/output_section/header"]
layout_mode = 2
text = "Output"
icon = SubResource("ImageTexture_r01jd")
flat = true
alignment = 0
[node name="HSeparator" type="HSeparator" parent="container/options/output_section/header"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="section_content" type="MarginContainer" parent="container/options/output_section"]
layout_mode = 2
theme_override_constants/margin_left = 20
[node name="items" type="VBoxContainer" parent="container/options/output_section/section_content"]
layout_mode = 2
[node name="output_folder" type="VBoxContainer" parent="container/options/output_section/section_content/items"]
layout_mode = 2
[node name="label" type="Label" parent="container/options/output_section/section_content/items/output_folder"]
layout_mode = 2
tooltip_text = "Folder where the SpriteSheet resource is going to be saved"
mouse_filter = 1
text = "Output Folder:"
[node name="HBoxContainer" type="HBoxContainer" parent="container/options/output_section/section_content/items/output_folder"]
layout_mode = 2
[node name="file_location_path" type="LineEdit" parent="container/options/output_section/section_content/items/output_folder/HBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
text = "res://"
caret_blink = true
[node name="output_folder_btn" type="Button" parent="container/options/output_section/section_content/items/output_folder/HBoxContainer"]
layout_mode = 2
text = "Select"
[node name="custom_filename" type="VBoxContainer" parent="container/options/output_section/section_content/items"]
layout_mode = 2
[node name="label" type="Label" parent="container/options/output_section/section_content/items/custom_filename"]
layout_mode = 2
tooltip_text = "Output filename. In case layers are not being merged, this is used as file prefix.
If not set, source filename is used."
mouse_filter = 1
text = "Output File Name / Prefix:"
[node name="pattern" type="LineEdit" parent="container/options/output_section/section_content/items/custom_filename"]
layout_mode = 2
caret_blink = true
[node name="disable_resource_creation" type="HBoxContainer" parent="container/options/output_section/section_content/items"]
layout_mode = 2
[node name="field" type="CheckBox" parent="container/options/output_section/section_content/items/disable_resource_creation"]
layout_mode = 2
tooltip_text = "Does not create SpriteFrames resource. Useful if you are only interested in the .json and .png output from Aseprite."
text = "Do not create resource file"
[node name="buttons" type="HBoxContainer" parent="container/options"]
layout_mode = 2
alignment = 2
[node name="close" type="Button" parent="container/options/buttons"]
layout_mode = 2
text = "Close"
[node name="clear" type="Button" parent="container/options/buttons"]
layout_mode = 2
text = "Clear"
[node name="next" type="Button" parent="container/options/buttons"]
layout_mode = 2
text = "Import"
[connection signal="button_up" from="container/options/file_location/HBoxContainer/file_location_btn" to="." method="_open_aseprite_file_selection_dialog"]
[connection signal="button_down" from="container/options/layer_section/header/section_header" to="." method="_on_layer_section_header_button_down"]
[connection signal="button_down" from="container/options/animation_section/header/section_header" to="." method="_on_animation_header_button_down"]
[connection signal="button_down" from="container/options/output_section/header/section_header" to="." method="_on_output_section_header_button_down"]
[connection signal="button_up" from="container/options/output_section/section_content/items/output_folder/HBoxContainer/output_folder_btn" to="." method="_open_output_folder_selection_dialog"]
[connection signal="button_up" from="container/options/buttons/close" to="." method="_on_close_btn_up"]
[connection signal="button_up" from="container/options/buttons/clear" to="." method="_on_clear_button_up"]
[connection signal="button_up" from="container/options/buttons/next" to="." method="_on_next_btn_up"]

View file

@ -0,0 +1,304 @@
@tool
extends PanelContainer
signal import_success(fields)
const result_code = preload("../../../config/result_codes.gd")
const wizard_config = preload("../../../config/wizard_config.gd")
var _import_helper = preload("./wizard_import_helper.gd").new()
@onready var _tree_container = $MarginContainer/HSplitContainer/tree
@onready var _resource_tree = _tree_container.get_resource_tree()
@onready var _nothing_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/nothing
@onready var _single_item_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item
@onready var _multiple_items_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/multiple_items
@onready var _confirmation_warning_container = $MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning
var _selection_count = 0
var _current_container = null
var _resources_to_process = null
var _is_loaded = false
var _groups = {}
func init_resources():
if _is_loaded:
return
_is_loaded = true
var file_tree = _get_file_tree("res://")
_setup_tree(file_tree)
func _get_file_tree(base_path: String, dir_name: String = "") -> Dictionary:
var dir_path = base_path.path_join(dir_name)
var dir = DirAccess.open(dir_path)
var dir_data = { "path": dir_path, "name": dir_name, "children": [], "type": "dir", }
if not dir:
return dir_data
dir.list_dir_begin()
var file_name = dir.get_next()
while file_name != "":
if dir.current_is_dir() and _is_importable_folder(dir_path, file_name):
var child_data = _get_file_tree(dir_path, file_name)
if not child_data.children.is_empty():
dir_data.children.push_back(child_data)
elif file_name.ends_with(".res"):
var resource_path = dir_path.path_join(file_name)
var resource = ResourceLoader.load(resource_path)
if resource is SpriteFrames:
if resource.has_meta(wizard_config.WIZARD_CONFIG_META_NAME):
var meta = resource.get_meta(wizard_config.WIZARD_CONFIG_META_NAME)
var parent_node = dir_data
if meta.group != "":
var group
if _groups.has(meta.group):
group = _groups[meta.group]
else:
group = { "folders": {} }
_groups[meta.group] = group
if not group.folders.has(dir_path):
group.folders[dir_path] = {
"node": {
"type": "group",
"name": "Split: %s" % meta.fields.source_file.get_file(),
"has_moved": meta.fields.output_location != dir_path,
"path": meta.fields.source_file,
"folder": dir_path,
"has_changes": _has_source_changes(resource, meta),
"children": [],
}
}
parent_node.children.push_back(group.folders[dir_path].node)
parent_node = group.folders[dir_path].node
parent_node.children.push_back({
"type": "resource",
"resource_type": "SpriteFrames",
"name": resource.resource_path.get_file(),
"path": resource.resource_path,
"meta": meta,
"has_changes": _has_source_changes(resource, meta),
"has_moved": meta.fields.output_location != dir_path,
})
file_name = dir.get_next()
return dir_data
func _is_importable_folder(dir_path: String, dir_name: String) -> bool:
return dir_path != "res://" or dir_name != "addons"
func _setup_tree(resource_tree: Dictionary) -> void:
var root = _resource_tree.create_item()
_add_items_to_tree(root, resource_tree.children)
func _add_items_to_tree(root: TreeItem, children: Array):
for node in children:
var item: TreeItem = _resource_tree.create_item(root)
item.set_text(0, node.name)
item.set_meta("node", node)
match node.type:
"dir":
item.set_icon(0, get_theme_icon("Folder", "EditorIcons"))
_add_items_to_tree(item, node.children)
#
"group":
item.set_icon(0, get_theme_icon("CompressedTexture2D", "EditorIcons"))
_add_items_to_tree(item, node.children)
if node.has_changes:
item.set_text(0, "%s (*)" % node.name)
if node.has_moved:
item.set_suffix(0, "(moved)")
"resource":
item.set_icon(0, get_theme_icon(node.resource_type, "EditorIcons"))
if node.has_changes:
item.set_text(0, "%s (*)" % node.name)
if node.meta.group != "":
item.set_custom_color(0, item.get_icon_modulate(0).darkened(0.5))
item.set_selectable(0, false)
elif node.has_moved:
item.set_suffix(0, "(moved)")
func _has_source_changes(resource: Object, meta: Dictionary) -> bool:
var current_hash = FileAccess.get_md5(meta.fields.source_file)
var saved_hash = wizard_config.get_source_hash(resource)
return saved_hash != current_hash
func _is_supported_type(resource_type: String) -> bool:
return resource_type == "SpriteFrames"
func reload_tree():
_confirmation_warning_container.hide()
_resources_to_process = null
if _current_container != null:
_current_container.show_buttons()
_current_container = null
_groups = {}
_selection_count = 0
_resource_tree.clear()
var file_tree = _get_file_tree("res://")
_setup_tree(file_tree)
func _set_empty_details_state():
_nothing_container.show()
_single_item_container.hide()
_multiple_items_container.hide()
func _on_tree_multi_selected(item: TreeItem, column: int, selected: bool):
_confirmation_warning_container.hide()
_resources_to_process = null
if _current_container != null:
_current_container.show_buttons()
#
if selected:
_selection_count += 1
else:
_selection_count -= 1
#
_nothing_container.hide()
_single_item_container.hide()
_multiple_items_container.hide()
#
match _selection_count:
0:
_nothing_container.show()
1:
_single_item_container.show()
_set_item_details(_resource_tree.get_selected())
_current_container = _single_item_container
_:
_multiple_items_container.show()
_multiple_items_container.set_selected_count(_selection_count)
_current_container = _multiple_items_container
func _set_item_details(item: TreeItem) -> void:
if not item.has_meta("node"):
return
var data = item.get_meta("node")
_single_item_container.set_resource_details(data)
func _on_single_item_import_triggered():
var selected = _resource_tree.get_selected()
var meta = selected.get_meta("node")
match meta.type:
"dir":
var selected_item = _resource_tree.get_selected()
var all_resources = []
var scenes_to_open = _set_all_resources(selected_item.get_meta("node"), all_resources)
_resources_to_process = all_resources
_show_confirmation_message(all_resources.size())
"resource":
var code = await _do_import(meta.path, meta.meta)
_set_tree_item_as_saved(_resource_tree.get_selected())
_single_item_container.hide_source_change_warning()
if code == OK:
EditorInterface.get_resource_filesystem().scan()
"group":
var first_item = meta.children[0]
var code = await _do_import(first_item.path, first_item.meta)
_set_tree_item_as_saved(selected)
_single_item_container.hide_source_change_warning()
if code == OK:
EditorInterface.get_resource_filesystem().scan()
_on_tree_refresh_triggered()
func _on_confirmation_warning_warning_confirmed():
_confirmation_warning_container.hide()
_current_container.show_buttons()
for resource in _resources_to_process:
await _do_import(resource.path, resource.meta)
_resources_to_process = null
EditorInterface.get_resource_filesystem().scan()
_on_tree_refresh_triggered()
func _on_confirmation_warning_warning_declined():
_confirmation_warning_container.hide()
_current_container.show_buttons()
_resources_to_process = null
func _on_tree_refresh_triggered():
_set_empty_details_state()
reload_tree()
func _set_tree_item_as_saved(item: TreeItem) -> void:
var meta = item.get_meta("node")
meta.has_changes = false
meta.has_moved = false
item.set_meta("node", meta)
item.set_text(0, meta.name)
item.set_suffix(0, "")
func _do_import(resource_path: String, metadata: Dictionary) -> int:
var resource_base_dir = resource_path.get_base_dir()
if resource_base_dir != metadata.fields.output_location:
print("Resource has moved. Changing output folder from %s to %s" % [resource_base_dir, metadata.fields.output_location])
metadata.fields.output_location = resource_base_dir
var exit_code := await _import_helper.import_and_create_resources(metadata.fields.source_file, metadata.fields)
if exit_code == OK:
print("Import complete: %s" % resource_path)
import_success.emit(metadata.fields)
else:
printerr("Failed to import %s. Error: %s" % [resource_path, result_code.get_error_message(exit_code)])
return exit_code
func _set_all_resources(meta: Dictionary, resources: Array):
match meta.type:
"dir":
for c in meta.children:
_set_all_resources(c, resources)
"resource":
if not resources.has(meta):
resources.push_back(meta)
"group":
var first_item = meta.children[0]
resources.push_back(first_item)
func _show_confirmation_message(resources: int):
_current_container.hide_buttons()
_confirmation_warning_container.set_message("You are about to re-import %s resources. Do you wish to continue?" % resources)
_confirmation_warning_container.show()
func _on_multiple_items_import_triggered():
var selected_item = _resource_tree.get_next_selected(null)
var all_resources = []
while selected_item != null:
_set_all_resources(selected_item.get_meta("node"), all_resources)
selected_item = _resource_tree.get_next_selected(selected_item)
_resources_to_process = all_resources
_show_confirmation_message(all_resources.size())

View file

@ -0,0 +1 @@
uid://dks0ne1sw10ni

View file

@ -0,0 +1,67 @@
[gd_scene load_steps=6 format=3 uid="uid://bfhsdslj8kt7b"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/imported_sprite_frames.gd" id="1_dk2ra"]
[ext_resource type="PackedScene" uid="uid://cisgsfvp4nf1g" path="res://addons/AsepriteWizard/interface/shared/tree/resource_tree.tscn" id="2_svrqo"]
[ext_resource type="PackedScene" uid="uid://q7eyyg2kvvv2" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.tscn" id="3_pe1cg"]
[ext_resource type="PackedScene" uid="uid://fscemkx5w1dw" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.tscn" id="4_vteew"]
[ext_resource type="PackedScene" uid="uid://qgmln507kjnm" path="res://addons/AsepriteWizard/interface/shared/tree/tree_selection_confirmation_warning.tscn" id="5_jigim"]
[node name="ImportedSpriteFrames" type="PanelContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_dk2ra")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
[node name="HSplitContainer" type="HSplitContainer" parent="MarginContainer"]
layout_mode = 2
[node name="tree" parent="MarginContainer/HSplitContainer" instance=ExtResource("2_svrqo")]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/HSplitContainer"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer/HSplitContainer/MarginContainer"]
layout_mode = 2
[node name="MarginContainer" type="MarginContainer" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/MarginContainer"]
layout_mode = 2
text = "Details"
horizontal_alignment = 1
[node name="HSeparator" type="HSeparator" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
[node name="nothing" type="Label" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 6
text = "Nothing selected"
horizontal_alignment = 1
[node name="single_item" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("3_pe1cg")]
visible = false
layout_mode = 2
[node name="multiple_items" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("4_vteew")]
visible = false
layout_mode = 2
[node name="confirmation_warning" parent="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer" instance=ExtResource("5_jigim")]
visible = false
layout_mode = 2
[connection signal="multi_selected" from="MarginContainer/HSplitContainer/tree" to="." method="_on_tree_multi_selected"]
[connection signal="refresh_triggered" from="MarginContainer/HSplitContainer/tree" to="." method="_on_tree_refresh_triggered"]
[connection signal="import_triggered" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/single_item" to="." method="_on_single_item_import_triggered"]
[connection signal="import_triggered" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/multiple_items" to="." method="_on_multiple_items_import_triggered"]
[connection signal="warning_confirmed" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning" to="." method="_on_confirmation_warning_warning_confirmed"]
[connection signal="warning_declined" from="MarginContainer/HSplitContainer/MarginContainer/VBoxContainer/confirmation_warning" to="." method="_on_confirmation_warning_warning_declined"]

View file

@ -0,0 +1,22 @@
@tool
extends VBoxContainer
signal import_triggered
@onready var _import_message = $message
@onready var _import_button = $buttons
func set_selected_count(number_of_items: int) -> void:
_import_message.text = "%2d items selected" % number_of_items
func show_buttons():
_import_button.show()
func hide_buttons():
_import_button.hide()
func _on_import_selected_button_up():
import_triggered.emit()

View file

@ -0,0 +1,24 @@
[gd_scene load_steps=2 format=3 uid="uid://fscemkx5w1dw"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_multiple_items.gd" id="1_lg81l"]
[node name="multiple_items" type="VBoxContainer"]
size_flags_vertical = 3
script = ExtResource("1_lg81l")
[node name="message" type="Label" parent="."]
layout_mode = 2
size_flags_vertical = 6
text = "Multiple items selected
"
horizontal_alignment = 1
[node name="buttons" type="HFlowContainer" parent="."]
layout_mode = 2
[node name="import_selected" type="Button" parent="buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import all"
[connection signal="button_up" from="buttons/import_selected" to="." method="_on_import_selected_button_up"]

View file

@ -0,0 +1,171 @@
@tool
extends VBoxContainer
signal import_triggered
@onready var _file_name = $GridContainer/file_name_value
@onready var _type = $GridContainer/type_value
@onready var _path = $GridContainer/path_value
@onready var _source_label = $GridContainer/source_file_label
@onready var _source = $GridContainer/source_file_value
@onready var _only_visible_layers_label = $GridContainer/only_visible_layers_label
@onready var _only_visible_layers = $GridContainer/only_visible_layers_value
@onready var _layer_ex_pattern_label = $GridContainer/layer_ex_patt_label
@onready var _layer_ex_pattern = $GridContainer/layer_ex_patt_value
@onready var _o_name_label = $GridContainer/o_name_label
@onready var _o_name = $GridContainer/o_name_value
@onready var _o_folder_label = $GridContainer/o_folder_label
@onready var _o_folder_value = $GridContainer/o_folder_value
@onready var _resource_list_label = $GridContainer/resource_list_label
@onready var _resource_list = $GridContainer/resource_list
@onready var _resource_list_separator_1 = $GridContainer/HSeparator3
@onready var _resource_list_separator_2 = $GridContainer/HSeparator4
@onready var _resource_buttons = $resource_buttons
@onready var _dir_buttons = $dir_buttons
@onready var _group_buttons = $group_buttons
@onready var _source_change_warning = $source_changed_warning
@onready var _resource_only_fields = [
_source_label,
_source,
_only_visible_layers_label,
_only_visible_layers,
_layer_ex_pattern_label,
_layer_ex_pattern,
_o_name_label,
_o_name,
_o_folder_label,
_o_folder_value,
_source_change_warning,
]
var _current_resource_type = "resource"
var _resource_config: Dictionary = {}
func _ready():
_source_change_warning.set_text("Source file changed since last import")
_source_change_warning.hide()
func set_resource_details(resource_details: Dictionary) -> void:
_resource_config = resource_details
_resource_buttons.hide()
_dir_buttons.hide()
_group_buttons.hide()
_hide_resource_list()
_source_change_warning.hide()
_file_name.text = resource_details.name
_path.text = resource_details.path
_current_resource_type = resource_details.type
match resource_details.type:
"resource":
_type.text = resource_details.resource_type
_show_resource_fields()
_resource_buttons.show()
var fields = resource_details.meta.fields
_load_fields(fields)
_resource_buttons.show()
_source_change_warning.visible = resource_details.has_changes
"group":
_type.text = "Split Group"
_show_resource_fields()
_load_fields(resource_details.children[0].meta.fields)
_source_change_warning.visible = resource_details.children[0].has_changes
_group_buttons.show()
_show_resource_list()
for c in _resource_list.get_children():
c.queue_free()
for child_resource in resource_details.children:
var label = Label.new()
label.text = child_resource.name
_resource_list.add_child(label)
_:
_type.text = "Folder"
_hide_resource_fields()
_dir_buttons.show()
return
func _load_fields(fields: Dictionary):
_only_visible_layers.text = "Yes" if fields.only_visible_layers else "No"
_layer_ex_pattern.text = fields.layer_exclusion_pattern
_o_name.text = fields.output_name
_o_folder_value.text = fields.output_location
_source.text = fields.source_file
func _hide_resource_fields():
for f in _resource_only_fields:
f.hide()
func _show_resource_fields():
for f in _resource_only_fields:
f.show()
func show_buttons():
match _current_resource_type:
"resource":
_resource_buttons.show()
_:
_dir_buttons.show()
func hide_buttons():
_resource_buttons.hide()
_dir_buttons.hide()
func hide_source_change_warning():
_source_change_warning.hide()
func _on_show_in_fs_button_up():
EditorInterface.get_file_system_dock().navigate_to_path(_path.text)
func _on_show_dir_in_fs_button_up():
EditorInterface.get_file_system_dock().navigate_to_path(_path.text)
func _on_import_all_button_up():
import_triggered.emit()
func _on_import_button_up():
import_triggered.emit()
func _hide_resource_list():
_resource_list_separator_1.hide()
_resource_list_separator_2.hide()
_resource_list_label.hide()
_resource_list.hide()
func _show_resource_list():
_resource_list_separator_1.show()
_resource_list_separator_2.show()
_resource_list_label.show()
_resource_list.show()
func _on_import_all_pressed():
import_triggered.emit()
func _on_show_in_fs_pressed():
EditorInterface.get_file_system_dock().navigate_to_path(_resource_config.children[0].path)

View file

@ -0,0 +1 @@
uid://bqm21qpd6pl3f

View file

@ -0,0 +1,172 @@
[gd_scene load_steps=3 format=3 uid="uid://q7eyyg2kvvv2"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/resource_tree_single_item.gd" id="1_h1q4t"]
[ext_resource type="PackedScene" uid="uid://c1l0bk12iwln3" path="res://addons/AsepriteWizard/interface/shared/tree/inline_warning_panel.tscn" id="2_hmv61"]
[node name="single_item" type="VBoxContainer"]
script = ExtResource("1_h1q4t")
[node name="GridContainer" type="GridContainer" parent="."]
layout_mode = 2
size_flags_vertical = 3
theme_override_constants/h_separation = 10
columns = 2
[node name="type_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Type"
[node name="type_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="file_name_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "File"
[node name="file_name_value" type="Label" parent="GridContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
text = "-"
autowrap_mode = 1
[node name="path_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Path"
[node name="path_value" type="Label" parent="GridContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "-"
autowrap_mode = 1
[node name="HSeparator" type="HSeparator" parent="GridContainer"]
layout_mode = 2
[node name="HSeparator2" type="HSeparator" parent="GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="source_file_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Aseprite File"
[node name="source_file_value" type="Label" parent="GridContainer"]
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "-"
autowrap_mode = 1
[node name="only_visible_layers_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Only Visible Layers"
[node name="only_visible_layers_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="layer_ex_patt_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Slice"
[node name="layer_ex_patt_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="o_folder_label" type="Label" parent="GridContainer"]
visible = false
layout_mode = 2
size_flags_vertical = 0
text = "Output Folder"
[node name="o_folder_value" type="Label" parent="GridContainer"]
visible = false
custom_minimum_size = Vector2(50, 0)
layout_mode = 2
size_flags_horizontal = 3
text = "-"
autowrap_mode = 2
[node name="o_name_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Output Name"
[node name="o_name_value" type="Label" parent="GridContainer"]
layout_mode = 2
text = "-"
[node name="HSeparator3" type="HSeparator" parent="GridContainer"]
layout_mode = 2
[node name="HSeparator4" type="HSeparator" parent="GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="resource_list_label" type="Label" parent="GridContainer"]
layout_mode = 2
size_flags_vertical = 0
text = "Resources"
[node name="resource_list" type="VBoxContainer" parent="GridContainer"]
layout_mode = 2
[node name="source_changed_warning" parent="." instance=ExtResource("2_hmv61")]
layout_mode = 2
[node name="resource_buttons" type="HFlowContainer" parent="."]
visible = false
layout_mode = 2
[node name="import" type="Button" parent="resource_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import"
[node name="show_in_fs" type="Button" parent="resource_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Show In FileSystem"
[node name="group_buttons" type="HFlowContainer" parent="."]
visible = false
layout_mode = 2
[node name="import_all" type="Button" parent="group_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import all"
[node name="show_in_fs" type="Button" parent="group_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Show In FileSystem"
[node name="dir_buttons" type="HFlowContainer" parent="."]
visible = false
layout_mode = 2
[node name="import_all" type="Button" parent="dir_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Re-Import all"
[node name="show_dir_in_fs" type="Button" parent="dir_buttons"]
layout_mode = 2
size_flags_horizontal = 3
text = "Show In FileSystem"
[connection signal="button_up" from="resource_buttons/import" to="." method="_on_import_button_up"]
[connection signal="button_up" from="resource_buttons/show_in_fs" to="." method="_on_show_in_fs_button_up"]
[connection signal="pressed" from="group_buttons/import_all" to="." method="_on_import_all_pressed"]
[connection signal="pressed" from="group_buttons/show_in_fs" to="." method="_on_show_in_fs_pressed"]
[connection signal="button_up" from="dir_buttons/import_all" to="." method="_on_import_all_button_up"]
[connection signal="button_up" from="dir_buttons/show_dir_in_fs" to="." method="_on_show_dir_in_fs_button_up"]

View file

@ -0,0 +1,236 @@
@tool
extends PanelContainer
signal request_edit(import_cfg)
signal request_import(import_cfg)
const SourcePathField = preload("./wizard_nodes/source_path.tscn")
const OutputPathField = preload("./wizard_nodes/output_path.tscn")
const ImportDateField = preload("./wizard_nodes/import_date.tscn")
const ItemActions = preload("./wizard_nodes/list_actions.tscn")
const DetailsField = preload("./wizard_nodes/details.tscn")
const SORT_BY_DATE := 0
const SORT_BY_PATH := 1
const INITIAL_GRID_INDEX := 4
var _config = preload("../../../config/config.gd").new()
var _history: Array
var _history_nodes := {}
var _history_nodes_list := []
var _is_busy := false
var _import_requested_for := -1
var _sort_by = SORT_BY_DATE
@onready var grid = $MarginContainer/VBoxContainer/ScrollContainer/GridContainer
@onready var loading_warning = $MarginContainer/VBoxContainer/loading_warning
@onready var no_history_warning = $MarginContainer/VBoxContainer/no_history_warning
func reload():
if _history:
return
_history = _config.get_import_history()
for index in range(_history.size()):
var entry = _history[index]
_create_node_list_entry(entry, index)
loading_warning.hide()
if _history.is_empty():
no_history_warning.show()
else:
grid.get_parent().show()
func _create_node_list_entry(entry: Dictionary, index: int):
_add_to_node_list(entry, _create_nodes(entry, index))
func _create_nodes(entry: Dictionary, index: int) -> Dictionary:
var import_date = ImportDateField.instantiate()
import_date.set_date(entry.import_date)
var source_path = SourcePathField.instantiate()
source_path.set_entry(entry)
var output_path = OutputPathField.instantiate()
output_path.text = entry.output_location
output_path.tooltip_text = entry.output_location
var details = DetailsField.instantiate()
details.set_details(entry)
var actions = ItemActions.instantiate()
actions.history_index = index
actions.connect("import_clicked",Callable(self,"_on_entry_reimport_clicked"))
actions.connect("edit_clicked",Callable(self,"_on_entry_edit_clicked"))
actions.connect("removed_clicked",Callable(self,"_on_entry_remove_clicked"))
grid.get_child(INITIAL_GRID_INDEX).add_sibling(import_date)
import_date.add_sibling(source_path)
source_path.add_sibling(output_path)
output_path.add_sibling(details)
details.add_sibling(actions)
return {
"history_index": index,
"timestamp": entry.import_date,
"source_file": entry.source_file,
"source_path_node": source_path,
"output_path_node": output_path,
"import_date_node": import_date,
"actions_node": actions,
"details_node": details,
}
func _add_to_node_list(entry: Dictionary, node: Dictionary):
if not _history_nodes.has(entry.source_file):
_history_nodes[entry.source_file] = []
_history_nodes[entry.source_file].push_front(node)
_history_nodes_list.push_front(node)
func add_entry(file_settings: Dictionary):
if _history == null:
reload()
#
var dt = Time.get_datetime_dict_from_system()
file_settings["import_date"] = "%04d-%02d-%02d %02d:%02d:%02d" % [dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second]
#
if _import_requested_for != -1:
_remove_item(_import_requested_for)
_import_requested_for = -1
elif _history.size() > _config.get_history_max_entries():
_remove_entries(_history[0].source_file, 0)
#
_history.push_back(file_settings)
_config.save_import_history(_history)
_create_node_list_entry(file_settings, _history.size() - 1)
if _sort_by == SORT_BY_PATH:
_trigger_sort()
no_history_warning.hide()
loading_warning.hide()
grid.get_parent().show()
_is_busy = false
func _on_entry_reimport_clicked(entry_index: int):
if _is_busy:
return
_is_busy = true
_import_requested_for = entry_index
emit_signal("request_import", _history[entry_index])
func _on_entry_edit_clicked(entry_index: int):
if _is_busy:
return
emit_signal("request_edit", _history[entry_index])
func _on_entry_remove_clicked(entry_index: int):
if _is_busy:
return
_is_busy = true
_remove_item(entry_index)
_config.save_import_history(_history)
if (_history.is_empty()):
grid.get_parent().hide()
no_history_warning.show()
_is_busy = false
func _remove_item(entry_index: int):
var entry = _history[entry_index]
_remove_entries(entry.source_file, entry_index)
# removes nodes and entry from history. If entry_index is not provided, all
# entries for path are removed.
func _remove_entries(source_file_path: String, entry_index: int = -1):
var files_entries = _history_nodes[source_file_path]
var indexes_to_remove = []
for f in files_entries:
if entry_index == -1 or f.history_index == entry_index:
_free_entry_nodes(f)
_history_nodes_list.erase(f)
if entry_index != -1:
files_entries.erase(f)
_remove_from_history(f.history_index)
return
indexes_to_remove.push_back(f.history_index)
for i in indexes_to_remove:
_remove_from_history(i)
_history_nodes[source_file_path] = []
func _remove_from_history(entry_index: int):
var _already_adjusted = []
# to avoid re-creating the whole nodes list, I just decrement
# the index from items newer than the excluded one
for index in range(entry_index + 1, _history.size()):
if _already_adjusted.has(_history[index].source_file):
continue
_already_adjusted.push_back(_history[index].source_file)
var nodes = _history_nodes[_history[index].source_file]
for f in nodes:
if f.history_index > entry_index:
f.history_index -= 1
f.actions_node.history_index = f.history_index
_history.remove_at(entry_index)
func _free_entry_nodes(entry_history_node: Dictionary):
entry_history_node.source_path_node.queue_free()
entry_history_node.output_path_node.queue_free()
entry_history_node.import_date_node.queue_free()
entry_history_node.actions_node.queue_free()
entry_history_node.details_node.queue_free()
func _on_SortOptions_item_selected(index):
if index == _sort_by:
return
_trigger_sort(index)
func _trigger_sort(sort_type: int = _sort_by):
if sort_type == SORT_BY_DATE:
_history_nodes_list.sort_custom(Callable(self,"_sort_by_date"))
else:
_history_nodes_list.sort_custom(Callable(self,"_sort_by_path"))
_reorganise_nodes()
_sort_by = sort_type
func _sort_by_date(a, b):
return a.timestamp < b.timestamp
func _sort_by_path(a, b):
return a.source_file > b.source_file
func _reorganise_nodes():
for entry in _history_nodes_list:
grid.move_child(entry.import_date_node, INITIAL_GRID_INDEX + 1)
grid.move_child(entry.source_path_node, INITIAL_GRID_INDEX + 2)
grid.move_child(entry.output_path_node, INITIAL_GRID_INDEX + 3)
grid.move_child(entry.details_node, INITIAL_GRID_INDEX + 4)
grid.move_child(entry.actions_node, INITIAL_GRID_INDEX + 5)

View file

@ -0,0 +1 @@
uid://cm8y8ip85no0w

View file

@ -0,0 +1,85 @@
[gd_scene load_steps=2 format=3 uid="uid://cyoin5ncul0fm"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/sprite_frames_import_history.gd" id="1"]
[node name="SpriteFramesImportHistory" type="PanelContainer"]
anchors_preset = 10
anchor_right = 1.0
grow_horizontal = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1")
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"]
layout_mode = 2
[node name="list actions" type="HBoxContainer" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 0
alignment = 2
[node name="divider" type="Label" parent="MarginContainer/VBoxContainer/list actions"]
layout_mode = 2
[node name="sort_label" type="Label" parent="MarginContainer/VBoxContainer/list actions"]
layout_mode = 2
text = "Sort by:"
[node name="SortOptions" type="OptionButton" parent="MarginContainer/VBoxContainer/list actions"]
layout_mode = 2
item_count = 2
selected = 0
popup/item_0/text = "Date"
popup/item_0/id = 0
popup/item_1/text = "Source File"
popup/item_1/id = 1
[node name="loading_warning" type="Label" parent="MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 6
text = "Loading..."
[node name="no_history_warning" type="Label" parent="MarginContainer/VBoxContainer"]
visible = false
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 6
text = "No import history"
[node name="ScrollContainer" type="ScrollContainer" parent="MarginContainer/VBoxContainer"]
visible = false
layout_mode = 2
size_flags_vertical = 3
[node name="GridContainer" type="GridContainer" parent="MarginContainer/VBoxContainer/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
columns = 5
[node name="date_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
layout_mode = 2
text = "Date"
[node name="source_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 2.0
text = "Source File"
[node name="output_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
layout_mode = 2
text = "Output Folder"
[node name="details_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
layout_mode = 2
text = "Details"
[node name="actions_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
layout_mode = 2
text = "Actions"
[connection signal="item_selected" from="MarginContainer/VBoxContainer/list actions/SortOptions" to="." method="_on_SortOptions_item_selected"]

View file

@ -0,0 +1,70 @@
@tool
extends Node
const wizard_meta = preload("../../../config/wizard_config.gd")
const result_code = preload("../../../config/result_codes.gd")
var _aseprite_file_exporter = preload("../../../aseprite/file_exporter.gd").new()
var _sf_creator = preload("../../../creators/sprite_frames/sprite_frames_creator.gd").new()
var _config = preload("../../../config/config.gd").new()
var _file_system: EditorFileSystem = EditorInterface.get_resource_filesystem()
# fields
# "split_layers"
# "only_visible_layers"
# "layer_exclusion_pattern"
# "output_name"
# "source_file"
# "do_not_create_resource"
# "output_location"
# "should_round_fps"
func import_and_create_resources(aseprite_file: String, fields: Dictionary) -> int:
var export_mode = _aseprite_file_exporter.LAYERS_EXPORT_MODE if fields.split_layers else _aseprite_file_exporter.FILE_EXPORT_MODE
var options = {
"export_mode": export_mode,
"exception_pattern": fields.layer_exclusion_pattern,
"only_visible_layers": fields.only_visible_layers,
"output_filename": fields.output_name,
"do_not_create_resource": fields.do_not_create_resource,
"output_folder": fields.output_location,
}
var aseprite_output = _aseprite_file_exporter.generate_aseprite_files(
ProjectSettings.globalize_path(aseprite_file),
options
)
if not aseprite_output.is_ok:
return aseprite_output.code
_file_system.scan()
await _file_system.filesystem_changed
var exit_code = OK
if !options.get("do_not_create_resource", false):
var resources = _sf_creator.create_resources(aseprite_output.content, { "should_round_fps": fields.get("should_round_fps", true) })
if resources.is_ok:
_add_metadata(resources.content, aseprite_file, fields, options)
exit_code = _sf_creator.save_resources(resources.content)
if _config.should_remove_source_files():
_remove_source_files(aseprite_output.content)
return exit_code
func _add_metadata(resources: Array, aseprite_file: String, fields: Dictionary, options: Dictionary) -> void:
var source_hash = FileAccess.get_md5(aseprite_file)
var group = str(ResourceUID.create_id()) if options.export_mode == _aseprite_file_exporter.LAYERS_EXPORT_MODE else ""
for r in resources:
wizard_meta.set_source_hash(r.resource, source_hash)
wizard_meta.save_config(r.resource, { "fields": fields, "group": group })
func _remove_source_files(source_files: Array):
for s in source_files:
DirAccess.remove_absolute(s.data_file)

View file

@ -0,0 +1 @@
uid://c7x0ejkherx43

View file

@ -0,0 +1,42 @@
@tool
extends VBoxContainer
@onready var _details_btn = $label
@onready var _details_container = $MarginContainer/GridContainer
@onready var _split_layers_field = $MarginContainer/GridContainer/split_layers
@onready var _only_visible_layers = $MarginContainer/GridContainer/only_visible_layers
@onready var _layer_exclusion_pattern = $MarginContainer/GridContainer/layer_exclusion_pattern
@onready var _output_name = $MarginContainer/GridContainer/output_name
@onready var _do_not_create_resource = $MarginContainer/GridContainer/do_not_create_resource
var _entry
func _ready():
_adjust_icon(false)
_details_container.hide()
_load_fields()
func set_details(entry: Dictionary):
_entry = entry
func _load_fields():
_split_layers_field.text = "Yes" if _entry.split_layers else "No"
_only_visible_layers.text = "Yes" if _entry.only_visible_layers else "No"
_layer_exclusion_pattern.text = _entry.layer_exclusion_pattern
_output_name.text = _entry.output_name
_output_name.text = _entry.output_name
_do_not_create_resource.text = "Yes" if _entry.do_not_create_resource else "No"
func _adjust_icon(is_visible: bool) -> void:
var icon_name = "GuiTreeArrowDown" if is_visible else "GuiTreeArrowRight"
_details_btn.icon = get_theme_icon(icon_name, "EditorIcons")
func _on_label_pressed():
_details_container.visible = not _details_container.visible
_adjust_icon(_details_container.visible)

View file

@ -0,0 +1 @@
uid://xhqjfqh7svgs

View file

@ -0,0 +1,60 @@
[gd_scene load_steps=2 format=3 uid="uid://b1jb5yierm2bq"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/details.gd" id="1_1v4dc"]
[node name="Details" type="VBoxContainer"]
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("1_1v4dc")
[node name="label" type="Button" parent="."]
layout_mode = 2
text = "Details"
flat = true
alignment = 0
[node name="MarginContainer" type="MarginContainer" parent="."]
layout_mode = 2
theme_override_constants/margin_left = 10
[node name="GridContainer" type="GridContainer" parent="MarginContainer"]
layout_mode = 2
theme_override_constants/h_separation = 5
columns = 2
[node name="split_layers_label" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
text = "Split layers:"
[node name="split_layers" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
[node name="only_visible_layers_label" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
text = "Only visible layers:"
[node name="only_visible_layers" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
[node name="layer_exclusion_pattern_label" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
text = "Layer Ex Pattern:"
[node name="layer_exclusion_pattern" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
[node name="output_name_label" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
text = "Output name:"
[node name="output_name" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
[node name="do_not_create_resource_label" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
text = "Do not create resource:"
[node name="do_not_create_resource" type="Label" parent="MarginContainer/GridContainer"]
layout_mode = 2
[connection signal="pressed" from="label" to="." method="_on_label_pressed"]

View file

@ -0,0 +1,6 @@
@tool
extends Label
func set_date(timestamp: String):
self.text = timestamp

View file

@ -0,0 +1 @@
uid://dktui4m0vumbc

View file

@ -0,0 +1,12 @@
[gd_scene load_steps=2 format=3 uid="uid://bs0i357m5d7ho"]
[ext_resource type="Script" path="res://addons/AsepriteWizard/interface/docks/wizard/wizard_nodes/import_date.gd" id="1"]
[node name="import_date" type="Label"]
offset_left = 660.0
offset_top = 39.0
offset_right = 774.0
offset_bottom = 53.0
size_flags_vertical = 0
text = "2022-07-18 18:01"
script = ExtResource("1")

Some files were not shown because too many files have changed in this diff Show more