Add addons
This commit is contained in:
parent
2c862b2235
commit
3367bad24f
527 changed files with 36197 additions and 0 deletions
|
|
@ -0,0 +1,208 @@
|
|||
@tool
|
||||
extends PanelContainer
|
||||
|
||||
const wizard_config = preload("../../config/wizard_config.gd")
|
||||
const result_code = preload("../../config/result_codes.gd")
|
||||
var sprite_frames_creator = preload("../sprite_frames_creator.gd").new()
|
||||
|
||||
var scene: Node
|
||||
var sprite: Node
|
||||
|
||||
var config
|
||||
var file_system: EditorFileSystem
|
||||
|
||||
var _layer: String = ""
|
||||
var _source: String = ""
|
||||
var _file_dialog_aseprite: FileDialog
|
||||
var _output_folder_dialog: FileDialog
|
||||
var _importing := false
|
||||
|
||||
var _output_folder := ""
|
||||
var _out_folder_default := "[Same as scene]"
|
||||
var _layer_default := "[all]"
|
||||
|
||||
@onready var _source_field = $margin/VBoxContainer/source/button
|
||||
@onready var _layer_field = $margin/VBoxContainer/layer/options
|
||||
@onready var _options_title = $margin/VBoxContainer/options_title/options_title
|
||||
@onready var _options_container = $margin/VBoxContainer/options
|
||||
@onready var _out_folder_field = $margin/VBoxContainer/options/out_folder/button
|
||||
@onready var _out_filename_field = $margin/VBoxContainer/options/out_filename/LineEdit
|
||||
@onready var _visible_layers_field = $margin/VBoxContainer/options/visible_layers/CheckButton
|
||||
@onready var _ex_pattern_field = $margin/VBoxContainer/options/ex_pattern/LineEdit
|
||||
|
||||
func _ready():
|
||||
var cfg = wizard_config.load_config(sprite)
|
||||
|
||||
if cfg == null:
|
||||
_load_default_config()
|
||||
else:
|
||||
_load_config(cfg)
|
||||
|
||||
sprite_frames_creator.init(config, file_system)
|
||||
|
||||
|
||||
func _load_config(cfg):
|
||||
if cfg.has("source"):
|
||||
_set_source(cfg.source)
|
||||
|
||||
if cfg.get("layer", "") != "":
|
||||
_layer_field.clear()
|
||||
_set_layer(cfg.layer)
|
||||
|
||||
_output_folder = cfg.get("o_folder", "")
|
||||
_out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default
|
||||
_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", "")
|
||||
|
||||
_set_options_visible(cfg.get("op_exp", false))
|
||||
|
||||
|
||||
func _load_default_config():
|
||||
_ex_pattern_field.text = config.get_default_exclusion_pattern()
|
||||
_set_options_visible(false)
|
||||
|
||||
|
||||
func _set_source(source):
|
||||
_source = source
|
||||
_source_field.text = _source
|
||||
_source_field.tooltip_text = _source
|
||||
|
||||
|
||||
func _set_layer(layer):
|
||||
_layer = layer
|
||||
_layer_field.add_item(_layer)
|
||||
|
||||
|
||||
func _on_layer_button_down():
|
||||
if _source == "":
|
||||
_show_message("Please. Select source file first.")
|
||||
return
|
||||
|
||||
var layers = sprite_frames_creator.list_layers(ProjectSettings.globalize_path(_source))
|
||||
var current = 0
|
||||
_layer_field.clear()
|
||||
_layer_field.add_item(_layer_default)
|
||||
|
||||
for l in layers:
|
||||
if l == "":
|
||||
continue
|
||||
|
||||
_layer_field.add_item(l)
|
||||
if l == _layer:
|
||||
current = _layer_field.get_item_count() - 1
|
||||
_layer_field.select(current)
|
||||
|
||||
|
||||
func _on_layer_item_selected(index):
|
||||
if index == 0:
|
||||
_layer = ""
|
||||
return
|
||||
_layer = _layer_field.get_item_text(index)
|
||||
_save_config()
|
||||
|
||||
|
||||
func _on_source_pressed():
|
||||
_open_source_dialog()
|
||||
|
||||
|
||||
func _on_import_pressed():
|
||||
if _importing:
|
||||
return
|
||||
_importing = true
|
||||
|
||||
var root = get_tree().get_edited_scene_root()
|
||||
|
||||
if _source == "":
|
||||
_show_message("Aseprite file not selected.")
|
||||
_importing = false
|
||||
return
|
||||
|
||||
var options = {
|
||||
"source": ProjectSettings.globalize_path(_source),
|
||||
"output_folder": _output_folder if _output_folder != "" else root.scene_file_path.get_base_dir(),
|
||||
"exception_pattern": _ex_pattern_field.text,
|
||||
"only_visible_layers": _visible_layers_field.button_pressed,
|
||||
"output_filename": _out_filename_field.text,
|
||||
"layer": _layer
|
||||
}
|
||||
|
||||
_save_config()
|
||||
|
||||
await sprite_frames_creator.create_animations(sprite, options)
|
||||
_importing = false
|
||||
|
||||
|
||||
func _save_config():
|
||||
wizard_config.save_config(sprite, config.is_use_metadata_enabled(), {
|
||||
"source": _source,
|
||||
"layer": _layer,
|
||||
"op_exp": _options_title.button_pressed,
|
||||
"o_folder": _output_folder,
|
||||
"o_name": _out_filename_field.text,
|
||||
"only_visible": _visible_layers_field.button_pressed,
|
||||
"o_ex_p": _ex_pattern_field.text
|
||||
})
|
||||
|
||||
|
||||
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 = _source.get_base_dir()
|
||||
_file_dialog_aseprite.popup_centered_ratio()
|
||||
|
||||
|
||||
func _create_aseprite_file_selection():
|
||||
var file_dialog = FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
file_dialog.access = FileDialog.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))
|
||||
_save_config()
|
||||
_file_dialog_aseprite.queue_free()
|
||||
|
||||
|
||||
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.connect("popup_hide",Callable(_warning_dialog,"queue_free"))
|
||||
|
||||
|
||||
func _on_options_title_toggled(button_pressed):
|
||||
_set_options_visible(button_pressed)
|
||||
_save_config()
|
||||
|
||||
|
||||
func _set_options_visible(is_visible):
|
||||
_options_container.visible = is_visible
|
||||
_options_title.icon = config.get_icon("expanded") if is_visible else config.get_icon("collapsed")
|
||||
|
||||
|
||||
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 = FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||
file_dialog.access = FileDialog.ACCESS_RESOURCES
|
||||
file_dialog.connect("dir_selected",Callable(self,"_on_output_folder_selected"))
|
||||
return file_dialog
|
||||
|
||||
|
||||
func _on_output_folder_selected(path):
|
||||
_output_folder = path
|
||||
_out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default
|
||||
_output_folder_dialog.queue_free()
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://vej7yqkbtd5f"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/AsepriteWizard/animated_sprite/docks/animated_sprite_inspector_dock.gd" id="1"]
|
||||
|
||||
[node name="animated_sprite_inspector_dock" type="PanelContainer"]
|
||||
offset_right = 14.0
|
||||
offset_bottom = 14.0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="margin" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="margin"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="section_title" type="PanelContainer" parent="margin/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="title" type="Label" parent="margin/VBoxContainer/section_title"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Aseprite"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="source" type="HBoxContainer" parent="margin/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Location of the Aseprite (*.ase, *.aseprite) source file."
|
||||
|
||||
[node name="Label" type="Label" parent="margin/VBoxContainer/source"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Aseprite File"
|
||||
|
||||
[node name="button" type="Button" parent="margin/VBoxContainer/source"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "[empty]"
|
||||
clip_text = true
|
||||
|
||||
[node name="layer" type="HBoxContainer" parent="margin/VBoxContainer"]
|
||||
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="margin/VBoxContainer/layer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Layer"
|
||||
|
||||
[node name="options" type="OptionButton" parent="margin/VBoxContainer/layer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
item_count = 1
|
||||
selected = 0
|
||||
popup/item_0/text = "[all]"
|
||||
popup/item_0/id = 0
|
||||
|
||||
[node name="options_title" type="PanelContainer" parent="margin/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="options_title" type="Button" parent="margin/VBoxContainer/options_title"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Options"
|
||||
|
||||
[node name="options" type="VBoxContainer" parent="margin/VBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="out_folder" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Location where the spritesheet file should be saved."
|
||||
|
||||
[node name="Label" type="Label" parent="margin/VBoxContainer/options/out_folder"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Output folder"
|
||||
|
||||
[node name="button" type="Button" parent="margin/VBoxContainer/options/out_folder"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "[empty]"
|
||||
clip_text = true
|
||||
|
||||
[node name="out_filename" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
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="margin/VBoxContainer/options/out_filename"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Output file name"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/options/out_filename"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
|
||||
[node name="ex_pattern" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Exclude layers with name matching this pattern (regex)."
|
||||
|
||||
[node name="Label" type="Label" parent="margin/VBoxContainer/options/ex_pattern"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Exclude pattern"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/options/ex_pattern"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
|
||||
[node name="visible_layers" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
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="margin/VBoxContainer/options/visible_layers"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Only visible layers"
|
||||
|
||||
[node name="CheckButton" type="CheckButton" parent="margin/VBoxContainer/options/visible_layers"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
|
||||
[node name="import" type="Button" parent="margin/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Import"
|
||||
|
||||
[connection signal="pressed" from="margin/VBoxContainer/source/button" to="." method="_on_source_pressed"]
|
||||
[connection signal="button_down" from="margin/VBoxContainer/layer/options" to="." method="_on_layer_button_down"]
|
||||
[connection signal="item_selected" from="margin/VBoxContainer/layer/options" to="." method="_on_layer_item_selected"]
|
||||
[connection signal="toggled" from="margin/VBoxContainer/options_title/options_title" to="." method="_on_options_title_toggled"]
|
||||
[connection signal="pressed" from="margin/VBoxContainer/options/out_folder/button" to="." method="_on_out_folder_pressed"]
|
||||
[connection signal="pressed" from="margin/VBoxContainer/import" to="." method="_on_import_pressed"]
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
@tool
|
||||
extends TabContainer
|
||||
|
||||
signal close_requested
|
||||
|
||||
const WizardWindow = preload("./as_wizard_window.tscn")
|
||||
|
||||
var _config
|
||||
var _file_system: EditorFileSystem
|
||||
|
||||
func _ready():
|
||||
$Import.init(_config, _file_system)
|
||||
$Import.connect("close_requested",Callable(self,"emit_signal").bind("close_requested"))
|
||||
$Import.connect("import_success",Callable($History,"add_entry"))
|
||||
$History.init(_config)
|
||||
$History.connect("request_edit",Callable(self,"_on_edit_request"))
|
||||
$History.connect("request_import",Callable(self,"_on_import_request"))
|
||||
|
||||
|
||||
func init(config, editor_file_system: EditorFileSystem):
|
||||
_config = config
|
||||
_file_system = editor_file_system
|
||||
|
||||
|
||||
func _on_AsWizardDockContainer_tab_changed(tab: int):
|
||||
if tab == 1:
|
||||
$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()
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
[gd_scene load_steps=4 format=2]
|
||||
|
||||
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/as_wizard_window.tscn" type="PackedScene" id=1]
|
||||
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/as_wizard_dock_container.gd" type="Script" id=2]
|
||||
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/sprite_frames_import_history.tscn" type="PackedScene" id=3]
|
||||
|
||||
[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 )]
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_left = 4.0
|
||||
offset_top = 32.0
|
||||
offset_right = -4.0
|
||||
offset_bottom = -4.0
|
||||
tooltip_text = "SpriteFrames Importer"
|
||||
|
||||
[node name="History" parent="." instance=ExtResource( 3 )]
|
||||
visible = false
|
||||
|
||||
[connection signal="tab_changed" from="." to="." method="_on_AsWizardDockContainer_tab_changed"]
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
@tool
|
||||
extends PanelContainer
|
||||
|
||||
signal close_requested
|
||||
signal import_success(file_settings)
|
||||
|
||||
var result_code = preload("../../config/result_codes.gd")
|
||||
var _sf_creator = preload("../sprite_frames_creator.gd").new()
|
||||
|
||||
var _config
|
||||
var _file_system: EditorFileSystem
|
||||
|
||||
var _file_dialog_aseprite: FileDialog
|
||||
var _output_folder_dialog: FileDialog
|
||||
var _warning_dialog: AcceptDialog
|
||||
|
||||
func _exit_tree():
|
||||
_file_dialog_aseprite.queue_free()
|
||||
_output_folder_dialog.queue_free()
|
||||
_warning_dialog.queue_free()
|
||||
|
||||
|
||||
func init(config, editor_file_system: EditorFileSystem):
|
||||
_config = config
|
||||
_file_system = editor_file_system
|
||||
_file_dialog_aseprite = _create_aseprite_file_selection()
|
||||
_output_folder_dialog = _create_outuput_folder_selection()
|
||||
_warning_dialog = AcceptDialog.new()
|
||||
_warning_dialog.exclusive = false
|
||||
|
||||
_sf_creator.init(_config, _file_system)
|
||||
|
||||
get_parent().get_parent().add_child(_file_dialog_aseprite)
|
||||
get_parent().get_parent().add_child(_output_folder_dialog)
|
||||
get_parent().get_parent().add_child(_warning_dialog)
|
||||
|
||||
_load_persisted_config()
|
||||
|
||||
|
||||
func _load_persisted_config():
|
||||
_split_mode_field().button_pressed = _config.should_split_layers()
|
||||
_only_visible_layers_field().button_pressed = _config.should_include_only_visible_layers()
|
||||
_exception_pattern_field().text = _config.get_exception_pattern()
|
||||
_custom_name_field().text = _config.get_last_custom_name()
|
||||
_file_location_field().text = _config.get_last_source_path()
|
||||
_do_not_create_res_field().button_pressed = _config.should_not_create_resource()
|
||||
|
||||
var output_folder = _config.get_last_output_path()
|
||||
_output_folder_field().text = output_folder if output_folder != "" else "res://"
|
||||
|
||||
|
||||
func load_import_config(import_config: Dictionary):
|
||||
_split_mode_field().button_pressed = import_config.options.export_mode == _sf_creator.LAYERS_EXPORT_MODE
|
||||
_only_visible_layers_field().button_pressed = import_config.options.only_visible_layers
|
||||
_exception_pattern_field().text = import_config.options.exception_pattern
|
||||
_custom_name_field().text = import_config.options.output_filename
|
||||
_file_location_field().text = import_config.source_file
|
||||
_do_not_create_res_field().button_pressed = import_config.options.do_not_create_resource
|
||||
_output_folder_field().text = import_config.output_location if import_config.output_location != "" else "res://"
|
||||
|
||||
|
||||
func _open_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():
|
||||
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 = FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
file_dialog.access = FileDialog.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 = FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||
file_dialog.access = FileDialog.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
|
||||
_config.set_last_source_path(localized_path)
|
||||
|
||||
|
||||
func _on_output_folder_selected(path):
|
||||
_output_folder_field().text = path
|
||||
_config.set_last_output_path(path)
|
||||
|
||||
|
||||
func _on_next_btn_up():
|
||||
var aseprite_file = _file_location_field().text
|
||||
var output_location = _output_folder_field().text
|
||||
var split_layers = _split_mode_field().button_pressed
|
||||
|
||||
var export_mode = _sf_creator.LAYERS_EXPORT_MODE if split_layers else _sf_creator.FILE_EXPORT_MODE
|
||||
var options = {
|
||||
"export_mode": export_mode,
|
||||
"exception_pattern": _exception_pattern_field().text,
|
||||
"only_visible_layers": _only_visible_layers_field().button_pressed,
|
||||
"output_filename": _custom_name_field().text,
|
||||
"do_not_create_resource": _do_not_create_res_field().button_pressed,
|
||||
"remove_source_files_allowed": true,
|
||||
"output_folder": output_location,
|
||||
}
|
||||
var exit_code = await _sf_creator.create_and_save_resources(
|
||||
ProjectSettings.globalize_path(aseprite_file),
|
||||
options
|
||||
)
|
||||
|
||||
if exit_code != OK:
|
||||
_show_error(exit_code)
|
||||
return
|
||||
emit_signal("import_success", {
|
||||
"source_file": aseprite_file,
|
||||
"output_location": output_location,
|
||||
"options": options,
|
||||
})
|
||||
_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 _save_config():
|
||||
_config.set_split_layers(_split_mode_field().button_pressed)
|
||||
_config.set_exception_pattern(_exception_pattern_field().text)
|
||||
_config.set_custom_name(_custom_name_field().text)
|
||||
_config.set_include_only_visible_layers(_only_visible_layers_field().button_pressed)
|
||||
_config.set_do_not_create_resource(_do_not_create_res_field().button_pressed)
|
||||
|
||||
|
||||
func _show_error(code: int):
|
||||
_show_error_message(result_code.get_error_message(code))
|
||||
|
||||
|
||||
func _show_error_message(message: String):
|
||||
_warning_dialog.dialog_text = "Error: %s" % message
|
||||
_warning_dialog.popup_centered()
|
||||
|
||||
|
||||
func _show_import_success_message():
|
||||
_warning_dialog.dialog_text = "Aseprite import succeeded"
|
||||
_warning_dialog.popup_centered()
|
||||
_save_config()
|
||||
|
||||
|
||||
func _file_location_field() -> LineEdit:
|
||||
return $container/options/file_location/HBoxContainer/file_location_path as LineEdit
|
||||
|
||||
|
||||
func _output_folder_field() -> LineEdit:
|
||||
return $container/options/output_folder/HBoxContainer/file_location_path as LineEdit
|
||||
|
||||
|
||||
func _exception_pattern_field() -> LineEdit:
|
||||
return $container/options/exclude_pattern/pattern as LineEdit
|
||||
|
||||
|
||||
func _split_mode_field() -> CheckBox:
|
||||
return $container/options/layer_importing_mode/split_layers/field as CheckBox
|
||||
|
||||
|
||||
func _only_visible_layers_field() -> CheckBox:
|
||||
return $container/options/layer_importing_mode/visible_layers/field as CheckBox
|
||||
|
||||
|
||||
func _custom_name_field() -> LineEdit:
|
||||
return $container/options/custom_filename/pattern as LineEdit
|
||||
|
||||
|
||||
func _do_not_create_res_field() -> CheckBox:
|
||||
return $container/options/layer_importing_mode/disable_resource_creation/field as CheckBox
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://c5dwobjd34w3p"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/AsepriteWizard/animated_sprite/docks/as_wizard_window.gd" id="1"]
|
||||
|
||||
[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
|
||||
|
||||
[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
|
||||
caret_blink = true
|
||||
|
||||
[node name="file_location_btn" type="Button" parent="container/options/file_location/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Select"
|
||||
|
||||
[node name="output_folder" type="VBoxContainer" parent="container/options"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="label" type="Label" parent="container/options/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_folder"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="file_location_path" type="LineEdit" parent="container/options/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_folder/HBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Select"
|
||||
|
||||
[node name="exclude_pattern" type="VBoxContainer" parent="container/options"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="label" type="Label" parent="container/options/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/exclude_pattern"]
|
||||
layout_mode = 2
|
||||
caret_blink = true
|
||||
|
||||
[node name="custom_filename" type="VBoxContainer" parent="container/options"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="label" type="Label" parent="container/options/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/custom_filename"]
|
||||
layout_mode = 2
|
||||
caret_blink = true
|
||||
|
||||
[node name="layer_importing_mode" type="VBoxContainer" parent="container/options"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="label" type="Label" parent="container/options/layer_importing_mode"]
|
||||
layout_mode = 2
|
||||
mouse_filter = 1
|
||||
text = "Options:"
|
||||
|
||||
[node name="split_layers" type="HBoxContainer" parent="container/options/layer_importing_mode"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/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_importing_mode"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/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="disable_resource_creation" type="HBoxContainer" parent="container/options/layer_importing_mode"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="field" type="CheckBox" parent="container/options/layer_importing_mode/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="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_up" from="container/options/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/next" to="." method="_on_next_btn_up"]
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
@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 SORT_BY_DATE := 0
|
||||
const SORT_BY_PATH := 1
|
||||
const INITIAL_GRID_INDEX := 3
|
||||
|
||||
var _config
|
||||
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 init(config):
|
||||
_config = config
|
||||
|
||||
|
||||
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 source_path = SourcePathField.instantiate()
|
||||
source_path.set_entry(entry)
|
||||
|
||||
grid.get_child(INITIAL_GRID_INDEX).add_sibling(source_path)
|
||||
|
||||
var output_path = OutputPathField.instantiate()
|
||||
output_path.text = entry.output_location
|
||||
source_path.add_sibling(output_path)
|
||||
var import_date = ImportDateField.instantiate()
|
||||
import_date.set_date(entry.import_date)
|
||||
output_path.add_sibling(import_date)
|
||||
var actions = ItemActions.instantiate()
|
||||
actions.history_index = index
|
||||
import_date.add_sibling(actions)
|
||||
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"))
|
||||
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,
|
||||
}
|
||||
|
||||
|
||||
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 _config.is_single_file_history() and _history_nodes.has(file_settings.source_file):
|
||||
_remove_entries(file_settings.source_file)
|
||||
|
||||
_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()
|
||||
|
||||
|
||||
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.source_path_node, INITIAL_GRID_INDEX + 1)
|
||||
grid.move_child(entry.output_path_node, INITIAL_GRID_INDEX + 2)
|
||||
grid.move_child(entry.import_date_node, INITIAL_GRID_INDEX + 3)
|
||||
grid.move_child(entry.actions_node, INITIAL_GRID_INDEX + 4)
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://cyoin5ncul0fm"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/AsepriteWizard/animated_sprite/docks/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 = "Name"
|
||||
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 = 4
|
||||
|
||||
[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="date_label" type="Label" parent="MarginContainer/VBoxContainer/ScrollContainer/GridContainer"]
|
||||
layout_mode = 2
|
||||
text = "Date"
|
||||
|
||||
[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"]
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
@tool
|
||||
extends Label
|
||||
|
||||
|
||||
func set_date(timestamp: String):
|
||||
self.text = timestamp
|
||||
# self.text = _format_date(timestamp)
|
||||
|
||||
|
||||
#func _format_date(timestamp: int) -> String:
|
||||
# var dt = Time.get_datetime_dict_from_system_from_unix_time(timestamp)
|
||||
# return "%04d-%02d-%02d %02d:%02d:%02d" % [dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second]
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/wizard_nodes/import_date.gd" type="Script" id=1]
|
||||
|
||||
[node name="import_date" type="Label"]
|
||||
offset_left = 660.0
|
||||
offset_top = 39.0
|
||||
offset_right = 774.0
|
||||
offset_bottom = 53.0
|
||||
text = "2022-07-18 18:01"
|
||||
script = ExtResource( 1 )
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
@tool
|
||||
extends HBoxContainer
|
||||
|
||||
signal import_clicked(index)
|
||||
signal edit_clicked(index)
|
||||
signal removed_clicked(index)
|
||||
|
||||
var history_index = -1
|
||||
|
||||
|
||||
func _on_edit_pressed():
|
||||
emit_signal("edit_clicked", history_index)
|
||||
|
||||
|
||||
func _on_reimport_pressed():
|
||||
emit_signal("import_clicked", history_index)
|
||||
|
||||
|
||||
func _on_remove_pressed():
|
||||
emit_signal("removed_clicked", history_index)
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/wizard_nodes/list_actions.gd" type="Script" id=1]
|
||||
|
||||
[node name="actions" type="HBoxContainer"]
|
||||
offset_left = 784.0
|
||||
offset_top = 34.0
|
||||
offset_right = 950.0
|
||||
offset_bottom = 58.0
|
||||
custom_constants/separation = 5
|
||||
script = ExtResource( 1 )
|
||||
|
||||
[node name="edit" type="Button" parent="."]
|
||||
offset_right = 36.0
|
||||
offset_bottom = 20.0
|
||||
size_flags_vertical = 0
|
||||
text = "Edit"
|
||||
|
||||
[node name="reimport" type="Button" parent="."]
|
||||
offset_left = 41.0
|
||||
offset_right = 97.0
|
||||
offset_bottom = 20.0
|
||||
size_flags_vertical = 0
|
||||
text = "Import"
|
||||
|
||||
[node name="remove_at" type="Button" parent="."]
|
||||
offset_left = 102.0
|
||||
offset_right = 166.0
|
||||
offset_bottom = 20.0
|
||||
size_flags_vertical = 0
|
||||
text = "Remove"
|
||||
|
||||
[connection signal="pressed" from="edit" to="." method="_on_edit_pressed"]
|
||||
[connection signal="pressed" from="reimport" to="." method="_on_reimport_pressed"]
|
||||
[connection signal="pressed" from="remove_at" to="." method="_on_remove_pressed"]
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
[gd_scene format=2]
|
||||
|
||||
[node name="output_path" type="LineEdit"]
|
||||
offset_left = 450.0
|
||||
offset_top = 34.0
|
||||
offset_right = 650.0
|
||||
offset_bottom = 58.0
|
||||
minimum_size = Vector2( 200, 0 )
|
||||
size_flags_vertical = 0
|
||||
text = "/some/output"
|
||||
align = 3
|
||||
editable = false
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
@tool
|
||||
extends LineEdit
|
||||
|
||||
|
||||
func set_entry(entry: Dictionary):
|
||||
self.text = entry.source_file
|
||||
self.tooltip_text = _format_hint(entry)
|
||||
|
||||
|
||||
func _format_hint(entry: Dictionary) -> String:
|
||||
return """Output filename/prefix: %s
|
||||
Ex. pattern: %s
|
||||
Split: %s
|
||||
Only visible: %s
|
||||
No Resource: %s
|
||||
""" % [
|
||||
entry.options.output_filename,
|
||||
entry.options.exception_pattern,
|
||||
"yes" if entry.options.export_mode else "no",
|
||||
"yes" if entry.options.only_visible_layers else "no",
|
||||
"yes" if entry.options.do_not_create_resource else "no",
|
||||
]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
[gd_scene load_steps=2 format=2]
|
||||
|
||||
[ext_resource path="res://addons/AsepriteWizard/animated_sprite/docks/wizard_nodes/source_path.gd" type="Script" id=1]
|
||||
|
||||
[node name="source_path" type="LineEdit"]
|
||||
offset_top = 34.0
|
||||
offset_right = 440.0
|
||||
offset_bottom = 58.0
|
||||
minimum_size = Vector2( 200, 0 )
|
||||
tooltip_text = "Output name / prefix: some_name
|
||||
Ex. Pattern: $_
|
||||
Split: Yes
|
||||
Only Visible: No
|
||||
No Resource: No"
|
||||
size_flags_vertical = 0
|
||||
text = "/some/very/long/source/path/to/test/field"
|
||||
editable = false
|
||||
script = ExtResource( 1 )
|
||||
129
Sprout/addons/AsepriteWizard/animated_sprite/import_plugin.gd
Normal file
129
Sprout/addons/AsepriteWizard/animated_sprite/import_plugin.gd
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
@tool
|
||||
extends EditorImportPlugin
|
||||
|
||||
const result_codes = preload("../config/result_codes.gd")
|
||||
|
||||
var config
|
||||
var _sf_creator = preload("sprite_frames_creator.gd").new()
|
||||
var file_system: EditorFileSystem
|
||||
|
||||
func _get_importer_name():
|
||||
return "aseprite_wizard.plugin"
|
||||
|
||||
|
||||
func _get_visible_name():
|
||||
return "Aseprite SpriteFrames Importer"
|
||||
|
||||
|
||||
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 1.0
|
||||
|
||||
|
||||
func _get_import_order():
|
||||
return 1
|
||||
|
||||
|
||||
func _get_import_options(_path, _i):
|
||||
return [
|
||||
{"name": "split_layers", "default_value": false},
|
||||
{"name": "exclude_layers_pattern", "default_value": ''},
|
||||
{"name": "only_visible_layers", "default_value": false},
|
||||
{
|
||||
"name": "sheet_type",
|
||||
"default_value": "Packed",
|
||||
"property_hint": PROPERTY_HINT_ENUM,
|
||||
"hint_string": get_sheet_type_hint_string()
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
func _get_option_visibility(path, option, options):
|
||||
return true
|
||||
|
||||
|
||||
static func replace_vars(pattern : String, vars : Dictionary):
|
||||
var result = pattern;
|
||||
for k in vars:
|
||||
var v = vars[k]
|
||||
result = result.replace("{%s}" % k, v)
|
||||
return result
|
||||
|
||||
|
||||
static func get_sheet_type_hint_string() -> String:
|
||||
var hint_string := "Packed"
|
||||
for number in [2, 4, 8, 16, 32]:
|
||||
hint_string += ",%s columns" % number
|
||||
hint_string += ",Strip"
|
||||
return hint_string
|
||||
|
||||
|
||||
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.substr(0, source_file.rfind('/'))
|
||||
var source_basename = source_file.substr(source_path.length()+1, -1)
|
||||
source_basename = source_basename.substr(0, source_basename.rfind('.'))
|
||||
|
||||
_sf_creator.init(config, file_system)
|
||||
|
||||
var export_mode = _sf_creator.LAYERS_EXPORT_MODE if options['split_layers'] else _sf_creator.FILE_EXPORT_MODE
|
||||
|
||||
var aseprite_opts = {
|
||||
"export_mode": export_mode,
|
||||
"exception_pattern": options['exclude_layers_pattern'],
|
||||
"only_visible_layers": options['only_visible_layers'],
|
||||
"output_filename": '' if export_mode == _sf_creator.FILE_EXPORT_MODE else '%s_' % source_basename,
|
||||
"column_count" : int(options['sheet_type']) if options['sheet_type'] != "Strip" else 128,
|
||||
"output_folder": source_path,
|
||||
}
|
||||
|
||||
var resources = await _sf_creator.create_resources(absolute_source_file, aseprite_opts)
|
||||
|
||||
if not resources.is_ok:
|
||||
printerr("ERROR - Could not import aseprite file: %s" % result_codes.get_error_message(resources.code))
|
||||
return FAILED
|
||||
|
||||
if export_mode == _sf_creator.LAYERS_EXPORT_MODE:
|
||||
# each layer is saved as one resource using base file name to prevent
|
||||
# collisions
|
||||
# the first layer will be saved in the default resource path to prevent
|
||||
# godot from keeping re-importing it
|
||||
for resource in resources.content:
|
||||
var resource_path = "%s.res" % resource.data_file.get_basename();
|
||||
var exit_code = ResourceSaver.save(resource.resource, resource_path)
|
||||
resource.resource.take_over_path(resource_path)
|
||||
|
||||
if exit_code != OK:
|
||||
printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code))
|
||||
return FAILED
|
||||
|
||||
var resource = resources.content[0]
|
||||
var resource_path = "%s.res" % save_path
|
||||
var exit_code = ResourceSaver.save(resource.resource, resource_path)
|
||||
resource.resource.take_over_path(resource_path)
|
||||
|
||||
if exit_code != OK:
|
||||
printerr("ERROR - Could not persist aseprite file: %s" % result_codes.get_error_message(exit_code))
|
||||
return FAILED
|
||||
return OK
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
@tool
|
||||
extends EditorInspectorPlugin
|
||||
|
||||
const ASInspectorDock = preload("./docks/animated_sprite_inspector_dock.tscn")
|
||||
|
||||
var config
|
||||
var file_system: EditorFileSystem
|
||||
|
||||
func _can_handle(object):
|
||||
return object is AnimatedSprite2D || object is AnimatedSprite3D
|
||||
|
||||
|
||||
func _parse_end(object):
|
||||
var dock = ASInspectorDock.instantiate()
|
||||
dock.sprite = object
|
||||
dock.config = config
|
||||
dock.file_system = file_system
|
||||
add_custom_control(dock)
|
||||
|
|
@ -0,0 +1,352 @@
|
|||
@tool
|
||||
extends RefCounted
|
||||
|
||||
var result_code = preload("../config/result_codes.gd")
|
||||
var _aseprite = preload("../aseprite/aseprite.gd").new()
|
||||
|
||||
enum {
|
||||
FILE_EXPORT_MODE,
|
||||
LAYERS_EXPORT_MODE
|
||||
}
|
||||
|
||||
var _config
|
||||
var _file_system: EditorFileSystem
|
||||
var _should_check_file_system := false
|
||||
|
||||
|
||||
func init(config, editor_file_system: EditorFileSystem = null):
|
||||
_config = config
|
||||
_file_system = editor_file_system
|
||||
_should_check_file_system = _file_system != null
|
||||
_aseprite.init(config)
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
func create_animations(sprite: Node, options: Dictionary) -> void:
|
||||
var input_check = _initial_checks(options.source, options)
|
||||
|
||||
if input_check != result_code.SUCCESS:
|
||||
printerr(result_code.get_error_message(input_check))
|
||||
return
|
||||
|
||||
var result = await _create_animations_from_file(sprite, options)
|
||||
|
||||
if result != result_code.SUCCESS:
|
||||
printerr(result_code.get_error_message(result))
|
||||
|
||||
|
||||
func _create_animations_from_file(animated_sprite: Node, options: Dictionary) -> int:
|
||||
var output = _export_aseprite_file(options)
|
||||
|
||||
if not output.is_ok:
|
||||
return output.code
|
||||
|
||||
if _config.is_import_preset_enabled():
|
||||
_config.create_import_file(output.content)
|
||||
|
||||
await _scan_filesystem()
|
||||
|
||||
var sprite_frames_result = _create_sprite_frames(output.content)
|
||||
if not sprite_frames_result.is_ok:
|
||||
return sprite_frames_result.code
|
||||
|
||||
animated_sprite.frames = sprite_frames_result.content
|
||||
animated_sprite.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
|
||||
if _config.should_remove_source_files():
|
||||
DirAccess.remove_absolute(output.content.data_file)
|
||||
|
||||
return result_code.SUCCESS
|
||||
|
||||
|
||||
func _export_aseprite_file(options: Dictionary) -> Dictionary:
|
||||
var output
|
||||
|
||||
if options.get("layer", "") == "":
|
||||
output = _aseprite.export_file(options.source, options.output_folder, options)
|
||||
else:
|
||||
output = _aseprite.export_layer(options.source, options.layer, options.output_folder, options)
|
||||
|
||||
if output.is_empty():
|
||||
return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED)
|
||||
|
||||
return result_code.result(output)
|
||||
|
||||
|
||||
func create_and_save_resources(source_file: String, options: Dictionary) -> int:
|
||||
var resources = await create_resources(source_file, options)
|
||||
if resources.is_ok:
|
||||
return _save_resources(resources.content)
|
||||
|
||||
return resources.code
|
||||
|
||||
|
||||
func create_resources(source_file: String, options = {}) -> Dictionary:
|
||||
var input_check = _initial_checks(source_file, options)
|
||||
|
||||
if input_check != result_code.SUCCESS:
|
||||
return result_code.error(input_check)
|
||||
|
||||
var output = _create_aseprite_output_files(source_file, options)
|
||||
|
||||
if not output.is_ok:
|
||||
return output
|
||||
|
||||
await _scan_filesystem()
|
||||
|
||||
var result = await _create_sprite_frames_from_source(output.content, options)
|
||||
|
||||
if not result.is_ok:
|
||||
return result
|
||||
|
||||
var should_remove_source = options.get("remove_source_files_allowed", false) and _config.should_remove_source_files()
|
||||
|
||||
if should_remove_source:
|
||||
_remove_source_files(result.content)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
func _remove_source_files(source_files: Array):
|
||||
for s in source_files:
|
||||
DirAccess.remove_absolute(s.data_file)
|
||||
|
||||
await _scan_filesystem()
|
||||
|
||||
|
||||
func _create_aseprite_output_files(source_file: String, options: Dictionary):
|
||||
match options.get('export_mode', FILE_EXPORT_MODE):
|
||||
FILE_EXPORT_MODE:
|
||||
return result_code.result(
|
||||
[_aseprite.export_file(source_file, options.output_folder, options)]
|
||||
)
|
||||
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)
|
||||
|
||||
|
||||
func _create_sprite_frames_from_source(source_files: Array, options: Dictionary) -> Dictionary:
|
||||
var should_remove_source = options.get("remove_source_files_allowed", false) and _config.should_remove_source_files()
|
||||
|
||||
var resources = []
|
||||
|
||||
for o in source_files:
|
||||
if o.is_empty():
|
||||
return result_code.error(result_code.ERR_ASEPRITE_EXPORT_FAILED)
|
||||
|
||||
var resource = _create_sprite_frames(o)
|
||||
|
||||
if not resource.is_ok:
|
||||
return resource
|
||||
|
||||
resources.push_back({
|
||||
"data_file": o.data_file,
|
||||
"resource": resource.content,
|
||||
})
|
||||
|
||||
return result_code.result(resources)
|
||||
|
||||
|
||||
func _create_sprite_frames(data) -> Dictionary:
|
||||
var aseprite_resources = _load_aseprite_resources(data)
|
||||
if not aseprite_resources.is_ok:
|
||||
return aseprite_resources
|
||||
|
||||
return result_code.result(
|
||||
_create_sprite_frames_with_animations(
|
||||
aseprite_resources.content.metadata,
|
||||
aseprite_resources.content.texture
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _load_aseprite_resources(aseprite_data: Dictionary):
|
||||
var content_result = _load_json_content(aseprite_data.data_file)
|
||||
|
||||
if not content_result.is_ok:
|
||||
return content_result
|
||||
|
||||
var texture = _parse_texture_path(aseprite_data.sprite_sheet)
|
||||
|
||||
return result_code.result({
|
||||
"metadata": content_result.content,
|
||||
"texture": texture
|
||||
})
|
||||
|
||||
|
||||
func _load_json_content(source_file: String) -> Dictionary:
|
||||
var file = FileAccess.open(source_file, FileAccess.READ)
|
||||
if file == null:
|
||||
return result_code.error(file.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)
|
||||
|
||||
|
||||
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, texture) -> SpriteFrames:
|
||||
var frame_cache = {}
|
||||
var frames = _aseprite.get_content_frames(content)
|
||||
var sprite_frames := SpriteFrames.new()
|
||||
sprite_frames.remove_animation("default")
|
||||
|
||||
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, tag.direction, frame_cache)
|
||||
else:
|
||||
_add_animation_frames(sprite_frames, "default", frames, texture)
|
||||
|
||||
return sprite_frames
|
||||
|
||||
|
||||
func _add_animation_frames(
|
||||
sprite_frames: SpriteFrames,
|
||||
anim_name: String,
|
||||
frames : Array,
|
||||
texture,
|
||||
direction = 'forward',
|
||||
frame_cache = {}
|
||||
):
|
||||
var animation_name := anim_name
|
||||
var is_loopable = _is_loop_config_enabled()
|
||||
|
||||
var loop_prefix := _loop_config_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)
|
||||
|
||||
if direction == 'reverse':
|
||||
frames.reverse()
|
||||
|
||||
for frame in frames:
|
||||
_add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache)
|
||||
|
||||
if direction == 'pingpong':
|
||||
frames.remove_at(frames.size() - 1)
|
||||
if is_loopable:
|
||||
frames.remove_at(0)
|
||||
frames.reverse()
|
||||
|
||||
for frame in frames:
|
||||
_add_to_sprite_frames(sprite_frames, animation_name, texture, frame, min_duration, frame_cache)
|
||||
|
||||
sprite_frames.set_animation_loop(animation_name, is_loopable)
|
||||
sprite_frames.set_animation_speed(animation_name, fps)
|
||||
|
||||
|
||||
func _calculate_fps(min_duration: int) -> float:
|
||||
return ceil(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 _parse_texture_path(path):
|
||||
return ResourceLoader.load(path, 'Image', ResourceLoader.CACHE_MODE_REPLACE)
|
||||
|
||||
|
||||
func _add_to_sprite_frames(
|
||||
sprite_frames,
|
||||
animation_name: String,
|
||||
texture,
|
||||
frame: Dictionary,
|
||||
min_duration: int,
|
||||
frame_cache: Dictionary
|
||||
):
|
||||
var atlas : AtlasTexture = _create_atlastexture_from_frame(texture, frame, sprite_frames, frame_cache)
|
||||
|
||||
var number_of_sprites = ceil(frame.duration / min_duration)
|
||||
for _i in range(number_of_sprites):
|
||||
sprite_frames.add_frame(animation_name, atlas)
|
||||
|
||||
|
||||
func _create_atlastexture_from_frame(
|
||||
image,
|
||||
frame_data,
|
||||
sprite_frames: SpriteFrames,
|
||||
frame_cache: Dictionary
|
||||
) -> AtlasTexture:
|
||||
var frame = frame_data.frame
|
||||
var region := Rect2(frame.x, frame.y, frame.w, frame.h)
|
||||
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 _scan_filesystem():
|
||||
_file_system.scan()
|
||||
await _file_system.filesystem_changed
|
||||
|
||||
|
||||
func list_layers(file: String, only_visibles = false) -> Array:
|
||||
return _aseprite.list_layers(file, only_visibles)
|
||||
|
||||
|
||||
func _get_file_basename(file_path: String) -> String:
|
||||
return file_path.get_file().trim_suffix('.%s' % file_path.get_extension())
|
||||
|
||||
|
||||
func _loop_config_prefix() -> String:
|
||||
return _config.get_animation_loop_exception_prefix()
|
||||
|
||||
|
||||
func _is_loop_config_enabled() -> bool:
|
||||
return _config.is_default_animation_loop_enabled()
|
||||
|
|
@ -0,0 +1,286 @@
|
|||
extends RefCounted
|
||||
|
||||
var result_code = preload("../config/result_codes.gd")
|
||||
var _aseprite = preload("../aseprite/aseprite.gd").new()
|
||||
|
||||
var _config
|
||||
var _file_system
|
||||
|
||||
var _DEFAULT_ANIMATION_LIBRARY = "" # GLOBAL
|
||||
|
||||
|
||||
func init(config, editor_file_system: EditorFileSystem = null):
|
||||
_config = config
|
||||
_file_system = editor_file_system
|
||||
_aseprite.init(config)
|
||||
|
||||
|
||||
func create_animations(target_node: Node, player: AnimationPlayer, options: Dictionary):
|
||||
if not _aseprite.test_command():
|
||||
return result_code.ERR_ASEPRITE_CMD_NOT_FOUND
|
||||
|
||||
if not FileAccess.file_exists(options.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
|
||||
|
||||
var result = await _create_animations_from_file(target_node, player, options)
|
||||
|
||||
if result != result_code.SUCCESS:
|
||||
printerr(result_code.get_error_message(result))
|
||||
|
||||
|
||||
func _create_animations_from_file(target_node: Node, player: AnimationPlayer, options: Dictionary):
|
||||
var output
|
||||
|
||||
if options.get("layer", "") == "":
|
||||
output = _aseprite.export_file(options.source, options.output_folder, options)
|
||||
else:
|
||||
output = _aseprite.export_layer(options.source, options.layer, options.output_folder, options)
|
||||
|
||||
if output.is_empty():
|
||||
return result_code.ERR_ASEPRITE_EXPORT_FAILED
|
||||
|
||||
if _config.is_import_preset_enabled():
|
||||
_config.create_import_file(output)
|
||||
|
||||
await _scan_filesystem()
|
||||
|
||||
var result = _import(target_node, player, output, options)
|
||||
|
||||
if _config.should_remove_source_files():
|
||||
DirAccess.remove_absolute(output.data_file)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
func _import(target_node: Node, player: AnimationPlayer, data: Dictionary, options: Dictionary):
|
||||
var source_file = data.data_file
|
||||
var sprite_sheet = data.sprite_sheet
|
||||
|
||||
var file = FileAccess.open(source_file, FileAccess.READ)
|
||||
if file == null:
|
||||
return file.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.ERR_INVALID_ASEPRITE_SPRITESHEET
|
||||
|
||||
var context = {}
|
||||
|
||||
target_node.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
_setup_texture(target_node, sprite_sheet, content, context)
|
||||
var result = _configure_animations(target_node, player, content, context)
|
||||
if result != result_code.SUCCESS:
|
||||
return result
|
||||
|
||||
return _cleanup_animations(target_node, player, content, options)
|
||||
|
||||
|
||||
func _load_texture(sprite_sheet: String) -> Texture2D:
|
||||
var texture = ResourceLoader.load(sprite_sheet, 'Image', true)
|
||||
texture.take_over_path(sprite_sheet)
|
||||
return texture
|
||||
|
||||
|
||||
func _configure_animations(target_node: Node, player: AnimationPlayer, content: Dictionary, context: Dictionary):
|
||||
var frames = _aseprite.get_content_frames(content)
|
||||
|
||||
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, tag.direction)
|
||||
if result != result_code.SUCCESS:
|
||||
break
|
||||
return result
|
||||
else:
|
||||
return _add_animation_frames(target_node, player, "default", frames, context)
|
||||
|
||||
|
||||
func _add_animation_frames(target_node: Node, player: AnimationPlayer, anim_name: String, frames: Array, context: Dictionary, direction = 'forward'):
|
||||
var animation_name = anim_name
|
||||
var is_loopable = _config.is_default_animation_loop_enabled()
|
||||
|
||||
if animation_name.begins_with(_config.get_animation_loop_exception_prefix()):
|
||||
animation_name = anim_name.substr(_config.get_animation_loop_exception_prefix().length())
|
||||
is_loopable = not is_loopable
|
||||
|
||||
if not player.has_animation(animation_name):
|
||||
player.get_animation_library(_DEFAULT_ANIMATION_LIBRARY).add_animation(animation_name, Animation.new())
|
||||
|
||||
var animation = player.get_animation(animation_name)
|
||||
_create_meta_tracks(target_node, player, animation)
|
||||
var frame_track = _get_property_track_path(player, target_node, _get_frame_property())
|
||||
var frame_track_index = _create_track(target_node, animation, frame_track)
|
||||
|
||||
if direction == 'reverse':
|
||||
frames.reverse()
|
||||
|
||||
var animation_length = 0
|
||||
|
||||
for frame in frames:
|
||||
var frame_key = _get_frame_key(target_node, frame, context)
|
||||
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 == 'pingpong':
|
||||
frames.remove_at(frames.size() - 1)
|
||||
if is_loopable:
|
||||
frames.remove_at(0)
|
||||
frames.reverse()
|
||||
|
||||
for frame in frames:
|
||||
var frame_key = _get_frame_key(target_node, frame, context)
|
||||
animation.track_insert_key(frame_track_index, animation_length, frame_key)
|
||||
animation_length += frame.duration / 1000
|
||||
|
||||
animation.length = animation_length
|
||||
animation.loop_mode = Animation.LOOP_LINEAR if is_loopable else Animation.LOOP_NONE
|
||||
|
||||
return result_code.SUCCESS
|
||||
|
||||
|
||||
func _create_track(target_node: Node, animation: Animation, track: String):
|
||||
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(content, player)
|
||||
|
||||
if options.get("cleanup_hide_unused_nodes", false):
|
||||
_hide_unused_nodes(target_node, player, content)
|
||||
|
||||
return result_code.SUCCESS
|
||||
|
||||
func _remove_unused_animations(content: Dictionary, player: AnimationPlayer):
|
||||
pass # FIXME it's not removing unused animations anymore. Sample impl bellow
|
||||
# var tags = ["RESET"]
|
||||
# for t in content.meta.frameTags:
|
||||
# var a = t.name
|
||||
# if a.begins_with(_config.get_animation_loop_exception_prefix()):
|
||||
# a = a.substr(_config.get_animation_loop_exception_prefix().length())
|
||||
# tags.push_back(a)
|
||||
|
||||
# var track = _get_frame_track_path(player, sprite)
|
||||
# for a in player.get_animation_list():
|
||||
# if tags.has(a):
|
||||
# continue
|
||||
#
|
||||
# var animation = player.get_animation(a)
|
||||
# if animation.get_track_count() != 1:
|
||||
# var t = animation.find_track(track)
|
||||
# if t != -1:
|
||||
# animation.remove_track(t)
|
||||
# continue
|
||||
#
|
||||
# if animation.find_track(track) != -1:
|
||||
# player.remove_animation(a)
|
||||
|
||||
|
||||
func _hide_unused_nodes(target_node: Node, player: AnimationPlayer, content: Dictionary):
|
||||
var root_node := player.get_node(player.root_node)
|
||||
var all_animations := player.get_animation_list()
|
||||
var all_sprite_nodes := []
|
||||
var animation_sprites := {}
|
||||
|
||||
for a in all_animations:
|
||||
var animation := player.get_animation(a)
|
||||
var sprite_nodes := []
|
||||
|
||||
for track_idx in animation.get_track_count():
|
||||
var raw_path := animation.track_get_path(track_idx)
|
||||
|
||||
if raw_path.get_subname(0) == "visible":
|
||||
continue
|
||||
|
||||
var path := _remove_properties_from_path(raw_path)
|
||||
var sprite_node := root_node.get_node(path)
|
||||
|
||||
if !(sprite_node is Sprite2D || sprite_node is Sprite3D):
|
||||
continue
|
||||
|
||||
if sprite_nodes.has(sprite_node):
|
||||
continue
|
||||
sprite_nodes.append(sprite_node)
|
||||
|
||||
animation_sprites[animation] = sprite_nodes
|
||||
for sn in sprite_nodes:
|
||||
if all_sprite_nodes.has(sn):
|
||||
continue
|
||||
all_sprite_nodes.append(sn)
|
||||
|
||||
for animation in animation_sprites:
|
||||
var sprite_nodes : Array = animation_sprites[animation]
|
||||
for node in all_sprite_nodes:
|
||||
if sprite_nodes.has(node):
|
||||
continue
|
||||
var visible_track = _get_property_track_path(player, node, "visible")
|
||||
if animation.find_track(visible_track) != -1:
|
||||
continue
|
||||
var visible_track_index = _create_track(node, animation, visible_track)
|
||||
animation.track_insert_key(visible_track_index, 0, false)
|
||||
|
||||
|
||||
func _scan_filesystem():
|
||||
_file_system.scan()
|
||||
await _file_system.filesystem_changed
|
||||
|
||||
|
||||
func list_layers(file: String, only_visibles = false) -> Array:
|
||||
return _aseprite.list_layers(file, only_visibles)
|
||||
|
||||
|
||||
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 _setup_texture(target_node: Node, sprite_sheet: String, content: Dictionary, context: Dictionary):
|
||||
push_error("_setup_texture not implemented!")
|
||||
|
||||
|
||||
func _get_frame_property() -> String:
|
||||
push_error("_get_frame_property not implemented!")
|
||||
return ""
|
||||
|
||||
|
||||
func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary):
|
||||
push_error("_get_frame_key not implemented!")
|
||||
|
||||
|
||||
func _create_meta_tracks(target_node: Node, player: AnimationPlayer, animation: Animation):
|
||||
push_error("_create_meta_tracks not implemented!")
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
@tool
|
||||
extends PanelContainer
|
||||
|
||||
const wizard_config = preload("../../config/wizard_config.gd")
|
||||
const result_code = preload("../../config/result_codes.gd")
|
||||
|
||||
const AnimationCreator = preload("../animation_creator.gd")
|
||||
const SpriteAnimationCreator = preload("../sprite_animation_creator.gd")
|
||||
const TextureRectAnimationCreator = preload("../texture_rect_animation_creator.gd")
|
||||
|
||||
var animation_creator: AnimationCreator
|
||||
|
||||
var scene: Node
|
||||
var target_node: Node
|
||||
|
||||
var config
|
||||
var file_system: EditorFileSystem
|
||||
|
||||
var _layer: String = ""
|
||||
var _source: String = ""
|
||||
var _animation_player_path: String
|
||||
var _file_dialog_aseprite: FileDialog
|
||||
var _output_folder_dialog: FileDialog
|
||||
var _importing := false
|
||||
|
||||
var _output_folder := ""
|
||||
var _out_folder_default := "[Same as scene]"
|
||||
var _layer_default := "[all]"
|
||||
|
||||
@onready var _options_field = $margin/VBoxContainer/animation_player/options
|
||||
@onready var _source_field = $margin/VBoxContainer/source/button
|
||||
@onready var _layer_field = $margin/VBoxContainer/layer/options
|
||||
@onready var _options_title = $margin/VBoxContainer/options_title/options_title
|
||||
@onready var _options_container = $margin/VBoxContainer/options
|
||||
@onready var _out_folder_field = $margin/VBoxContainer/options/out_folder/button
|
||||
@onready var _out_filename_field = $margin/VBoxContainer/options/out_filename/LineEdit
|
||||
@onready var _visible_layers_field = $margin/VBoxContainer/options/visible_layers/CheckButton
|
||||
@onready var _ex_pattern_field = $margin/VBoxContainer/options/ex_pattern/LineEdit
|
||||
@onready var _cleanup_hide_unused_nodes = $margin/VBoxContainer/options/auto_visible_track/CheckButton
|
||||
|
||||
|
||||
func _ready():
|
||||
var cfg = wizard_config.load_config(target_node)
|
||||
if cfg == null:
|
||||
_load_default_config()
|
||||
else:
|
||||
_load_config(cfg)
|
||||
|
||||
if target_node is Sprite2D || target_node is Sprite3D:
|
||||
animation_creator = SpriteAnimationCreator.new()
|
||||
if target_node is TextureRect:
|
||||
animation_creator = TextureRectAnimationCreator.new()
|
||||
|
||||
animation_creator.init(config, file_system)
|
||||
|
||||
|
||||
func _load_config(cfg):
|
||||
if cfg.has("source"):
|
||||
_set_source(cfg.source)
|
||||
|
||||
if cfg.has("player"):
|
||||
_options_field.clear()
|
||||
_set_animation_player(cfg.player)
|
||||
|
||||
if cfg.get("layer", "") != "":
|
||||
_layer_field.clear()
|
||||
_set_layer(cfg.layer)
|
||||
|
||||
_output_folder = cfg.get("o_folder", "")
|
||||
_out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default
|
||||
_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", "")
|
||||
_cleanup_hide_unused_nodes.button_pressed = cfg.get("set_vis_track", config.is_set_visible_track_automatically_enabled())
|
||||
|
||||
_set_options_visible(cfg.get("op_exp", false))
|
||||
|
||||
|
||||
func _load_default_config():
|
||||
_ex_pattern_field.text = config.get_default_exclusion_pattern()
|
||||
_cleanup_hide_unused_nodes.button_pressed = config.is_set_visible_track_automatically_enabled()
|
||||
_set_options_visible(false)
|
||||
|
||||
|
||||
func _set_source(source):
|
||||
_source = source
|
||||
_source_field.text = _source
|
||||
_source_field.tooltip_text = _source
|
||||
|
||||
|
||||
func _set_animation_player(player):
|
||||
_animation_player_path = player
|
||||
_options_field.add_item(_animation_player_path)
|
||||
|
||||
|
||||
func _set_layer(layer):
|
||||
_layer = layer
|
||||
_layer_field.add_item(_layer)
|
||||
|
||||
|
||||
func _on_options_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
|
||||
_options_field.clear()
|
||||
_options_field.add_item("[empty]")
|
||||
|
||||
for ap in animation_players:
|
||||
_options_field.add_item(ap)
|
||||
if ap.get_concatenated_names() == _animation_player_path:
|
||||
current = _options_field.get_item_count() - 1
|
||||
|
||||
_options_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_options_item_selected(index):
|
||||
if index == 0:
|
||||
_animation_player_path = ""
|
||||
return
|
||||
_animation_player_path = _options_field.get_item_text(index)
|
||||
_save_config()
|
||||
|
||||
|
||||
func _on_layer_button_down():
|
||||
if _source == "":
|
||||
_show_message("Please. Select source file first.")
|
||||
return
|
||||
|
||||
var layers = animation_creator.list_layers(ProjectSettings.globalize_path(_source))
|
||||
var current = 0
|
||||
_layer_field.clear()
|
||||
_layer_field.add_item("[all]")
|
||||
|
||||
for l in layers:
|
||||
if l == "":
|
||||
continue
|
||||
|
||||
_layer_field.add_item(l)
|
||||
if l == _layer:
|
||||
current = _layer_field.get_item_count() - 1
|
||||
_layer_field.select(current)
|
||||
|
||||
|
||||
func _on_layer_item_selected(index):
|
||||
if index == 0:
|
||||
_layer = ""
|
||||
return
|
||||
_layer = _layer_field.get_item_text(index)
|
||||
_save_config()
|
||||
|
||||
|
||||
func _on_source_pressed():
|
||||
_open_source_dialog()
|
||||
|
||||
|
||||
func _on_import_pressed():
|
||||
if _importing:
|
||||
return
|
||||
_importing = true
|
||||
|
||||
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
|
||||
|
||||
if _source == "":
|
||||
_show_message("Aseprite file not selected")
|
||||
_importing = false
|
||||
return
|
||||
|
||||
var options = {
|
||||
"source": ProjectSettings.globalize_path(_source),
|
||||
"output_folder": _output_folder if _output_folder != "" else root.scene_file_path.get_base_dir(),
|
||||
"exception_pattern": _ex_pattern_field.text,
|
||||
"only_visible_layers": _visible_layers_field.button_pressed,
|
||||
"output_filename": _out_filename_field.text,
|
||||
"cleanup_hide_unused_nodes": _cleanup_hide_unused_nodes.button_pressed,
|
||||
"layer": _layer
|
||||
}
|
||||
|
||||
_save_config()
|
||||
|
||||
await animation_creator.create_animations(target_node, root.get_node(_animation_player_path), options)
|
||||
_importing = false
|
||||
|
||||
|
||||
func _save_config():
|
||||
var cfg := {
|
||||
"player": _animation_player_path,
|
||||
"source": _source,
|
||||
"layer": _layer,
|
||||
"op_exp": _options_title.button_pressed,
|
||||
"o_folder": _output_folder,
|
||||
"o_name": _out_filename_field.text,
|
||||
"only_visible": _visible_layers_field.button_pressed,
|
||||
"o_ex_p": _ex_pattern_field.text,
|
||||
}
|
||||
|
||||
if _cleanup_hide_unused_nodes.button_pressed != config.is_set_visible_track_automatically_enabled():
|
||||
cfg["set_vis_track"] = _cleanup_hide_unused_nodes.button_pressed
|
||||
|
||||
wizard_config.save_config(target_node, config.is_use_metadata_enabled(), cfg)
|
||||
|
||||
|
||||
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 = _source.get_base_dir()
|
||||
_file_dialog_aseprite.popup_centered_ratio()
|
||||
|
||||
|
||||
func _create_aseprite_file_selection():
|
||||
var file_dialog = FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_FILE
|
||||
file_dialog.access = FileDialog.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))
|
||||
_save_config()
|
||||
_file_dialog_aseprite.queue_free()
|
||||
|
||||
|
||||
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.connect("popup_hide",Callable(_warning_dialog,"queue_free"))
|
||||
|
||||
|
||||
func _on_options_title_toggled(button_pressed):
|
||||
_set_options_visible(button_pressed)
|
||||
_save_config()
|
||||
|
||||
|
||||
func _set_options_visible(is_visible):
|
||||
_options_container.visible = is_visible
|
||||
_options_title.icon = config.get_icon("expanded") if is_visible else config.get_icon("collapsed")
|
||||
|
||||
|
||||
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 = FileDialog.new()
|
||||
file_dialog.file_mode = FileDialog.FILE_MODE_OPEN_DIR
|
||||
file_dialog.access = FileDialog.ACCESS_RESOURCES
|
||||
file_dialog.connect("dir_selected",Callable(self,"_on_output_folder_selected"))
|
||||
return file_dialog
|
||||
|
||||
|
||||
func _on_output_folder_selected(path):
|
||||
_output_folder = path
|
||||
_out_folder_field.text = _output_folder if _output_folder != "" else _out_folder_default
|
||||
_output_folder_dialog.queue_free()
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://biyshalfalqqw"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/AsepriteWizard/animation_player/docks/animation_player_inspector_dock.gd" id="1"]
|
||||
|
||||
[node name="sprite_inspector_dock" type="PanelContainer"]
|
||||
offset_right = 14.0
|
||||
offset_bottom = 14.0
|
||||
script = ExtResource("1")
|
||||
|
||||
[node name="margin" type="MarginContainer" parent="."]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="margin"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="section_title" type="PanelContainer" parent="margin/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="title" type="Label" parent="margin/VBoxContainer/section_title"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Aseprite"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="animation_player" type="HBoxContainer" parent="margin/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "AnimationPlayer node where animations should be added to."
|
||||
|
||||
[node name="Label" type="Label" parent="margin/VBoxContainer/animation_player"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "AnimationPlayer"
|
||||
|
||||
[node name="options" type="OptionButton" parent="margin/VBoxContainer/animation_player"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
item_count = 1
|
||||
selected = 0
|
||||
popup/item_0/text = "[empty]"
|
||||
popup/item_0/id = 0
|
||||
|
||||
[node name="source" type="HBoxContainer" parent="margin/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Location of the Aseprite (*.ase, *.aseprite) source file."
|
||||
|
||||
[node name="Label" type="Label" parent="margin/VBoxContainer/source"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Aseprite File"
|
||||
|
||||
[node name="button" type="Button" parent="margin/VBoxContainer/source"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "[empty]"
|
||||
clip_text = true
|
||||
|
||||
[node name="layer" type="HBoxContainer" parent="margin/VBoxContainer"]
|
||||
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="margin/VBoxContainer/layer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Layer"
|
||||
|
||||
[node name="options" type="OptionButton" parent="margin/VBoxContainer/layer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
item_count = 1
|
||||
selected = 0
|
||||
popup/item_0/text = "[all]"
|
||||
popup/item_0/id = 0
|
||||
|
||||
[node name="options_title" type="PanelContainer" parent="margin/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="options_title" type="Button" parent="margin/VBoxContainer/options_title"]
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Options"
|
||||
|
||||
[node name="options" type="VBoxContainer" parent="margin/VBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="out_folder" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Location where the spritesheet file should be saved."
|
||||
|
||||
[node name="Label" type="Label" parent="margin/VBoxContainer/options/out_folder"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Output folder"
|
||||
|
||||
[node name="button" type="Button" parent="margin/VBoxContainer/options/out_folder"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "[empty]"
|
||||
clip_text = true
|
||||
|
||||
[node name="out_filename" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
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="margin/VBoxContainer/options/out_filename"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Output file name"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/options/out_filename"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
|
||||
[node name="ex_pattern" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "Exclude layers with name matching this pattern (regex)."
|
||||
|
||||
[node name="Label" type="Label" parent="margin/VBoxContainer/options/ex_pattern"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Exclude pattern"
|
||||
|
||||
[node name="LineEdit" type="LineEdit" parent="margin/VBoxContainer/options/ex_pattern"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
|
||||
[node name="visible_layers" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
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="margin/VBoxContainer/options/visible_layers"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Only visible layers"
|
||||
|
||||
[node name="CheckButton" type="CheckButton" parent="margin/VBoxContainer/options/visible_layers"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
|
||||
[node name="auto_visible_track" type="HBoxContainer" parent="margin/VBoxContainer/options"]
|
||||
layout_mode = 2
|
||||
tooltip_text = "If active, it will automatically determine unused Sprite2D and Sprite3D nodes in each animation and hide them."
|
||||
|
||||
[node name="Label" type="Label" parent="margin/VBoxContainer/options/auto_visible_track"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
text = "Hide unused sprites"
|
||||
|
||||
[node name="CheckButton" type="CheckButton" parent="margin/VBoxContainer/options/auto_visible_track"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 2.0
|
||||
|
||||
[node name="import" type="Button" parent="margin/VBoxContainer"]
|
||||
layout_mode = 2
|
||||
text = "Import"
|
||||
|
||||
[connection signal="button_down" from="margin/VBoxContainer/animation_player/options" to="." method="_on_options_button_down"]
|
||||
[connection signal="item_selected" from="margin/VBoxContainer/animation_player/options" to="." method="_on_options_item_selected"]
|
||||
[connection signal="pressed" from="margin/VBoxContainer/source/button" to="." method="_on_source_pressed"]
|
||||
[connection signal="button_down" from="margin/VBoxContainer/layer/options" to="." method="_on_layer_button_down"]
|
||||
[connection signal="item_selected" from="margin/VBoxContainer/layer/options" to="." method="_on_layer_item_selected"]
|
||||
[connection signal="toggled" from="margin/VBoxContainer/options_title/options_title" to="." method="_on_options_title_toggled"]
|
||||
[connection signal="pressed" from="margin/VBoxContainer/options/out_folder/button" to="." method="_on_out_folder_pressed"]
|
||||
[connection signal="pressed" from="margin/VBoxContainer/import" to="." method="_on_import_pressed"]
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
@tool
|
||||
extends EditorInspectorPlugin
|
||||
|
||||
const APInspectorDock = preload("./docks/animation_player_inspector_dock.tscn")
|
||||
|
||||
var config
|
||||
var file_system: EditorFileSystem
|
||||
|
||||
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
|
||||
dock.config = config
|
||||
dock.file_system = file_system
|
||||
add_custom_control(dock)
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
extends "animation_creator.gd"
|
||||
|
||||
|
||||
func _setup_texture(sprite: Node, sprite_sheet: String, content: Dictionary, context: Dictionary):
|
||||
var texture = _load_texture(sprite_sheet)
|
||||
sprite.texture = texture
|
||||
|
||||
if content.frames.is_empty():
|
||||
return
|
||||
|
||||
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() -> String:
|
||||
return "frame"
|
||||
|
||||
|
||||
func _create_meta_tracks(sprite: Node, player: AnimationPlayer, animation: Animation):
|
||||
var texture_track = _get_property_track_path(player, sprite, "texture")
|
||||
var texture_track_index = _create_track(sprite, animation, texture_track)
|
||||
animation.track_insert_key(texture_track_index, 0, sprite.texture)
|
||||
|
||||
var hframes_track = _get_property_track_path(player, sprite, "hframes")
|
||||
var hframes_track_index = _create_track(sprite, animation, hframes_track)
|
||||
animation.track_insert_key(hframes_track_index, 0, sprite.hframes)
|
||||
|
||||
var vframes_track = _get_property_track_path(player, sprite, "vframes")
|
||||
var vframes_track_index = _create_track(sprite, animation, vframes_track)
|
||||
animation.track_insert_key(vframes_track_index, 0, sprite.vframes)
|
||||
|
||||
var visible_track = _get_property_track_path(player, sprite, "visible")
|
||||
var visible_track_index = _create_track(sprite, animation, visible_track)
|
||||
animation.track_insert_key(visible_track_index, 0, true)
|
||||
|
||||
|
||||
func _get_frame_key(sprite: Node, frame: Dictionary, context: Dictionary):
|
||||
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
|
||||
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
extends "animation_creator.gd"
|
||||
|
||||
|
||||
func _setup_texture(target_node: Node, sprite_sheet: String, content: Dictionary, context: Dictionary):
|
||||
context["base_texture"] = _load_texture(sprite_sheet)
|
||||
|
||||
|
||||
func _get_frame_property() -> String:
|
||||
return "texture"
|
||||
|
||||
|
||||
func _get_frame_key(target_node: Node, frame: Dictionary, context: Dictionary):
|
||||
return _get_atlas_texture(context["base_texture"], frame)
|
||||
|
||||
|
||||
func _create_meta_tracks(target_node: Node, player: AnimationPlayer, animation: Animation):
|
||||
var texture_track = _get_property_track_path(player, target_node, "texture")
|
||||
var texture_track_index = _create_track(target_node, animation, texture_track)
|
||||
animation.track_insert_key(texture_track_index, 0, target_node.texture)
|
||||
|
||||
var visible_track = _get_property_track_path(player, target_node, "visible")
|
||||
var visible_track_index = _create_track(target_node, animation, visible_track)
|
||||
animation.track_insert_key(visible_track_index, 0, true)
|
||||
|
||||
|
||||
func _get_atlas_texture(base_texture: Texture2D, frame_data: Dictionary) -> 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
|
||||
return tex
|
||||
187
Sprout/addons/AsepriteWizard/aseprite/aseprite.gd
Normal file
187
Sprout/addons/AsepriteWizard/aseprite/aseprite.gd
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
@tool
|
||||
extends RefCounted
|
||||
|
||||
var _config
|
||||
|
||||
func init(config):
|
||||
_config = config
|
||||
|
||||
#
|
||||
# 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 basename = _get_file_basename(output_name)
|
||||
var output_dir = output_folder.replace("res://", "./")
|
||||
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")
|
||||
|
||||
_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': data_file.replace("./", "res://"),
|
||||
"sprite_sheet": sprite_sheet.replace("./", "res://")
|
||||
}
|
||||
|
||||
|
||||
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_layer(file_name, layer, output_folder, options))
|
||||
|
||||
return output
|
||||
|
||||
|
||||
func export_layer(file_name: String, layer_name: String, 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 data_file = "%s/%s%s.json" % [output_dir, output_prefix, layer_name]
|
||||
var sprite_sheet = "%s/%s%s.png" % [output_dir, output_prefix, layer_name]
|
||||
var output = []
|
||||
var arguments = _export_command_common_arguments(file_name, data_file, sprite_sheet)
|
||||
arguments.push_front(layer_name)
|
||||
arguments.push_front("--layer")
|
||||
|
||||
_add_sheet_type_arguments(arguments, options)
|
||||
|
||||
var exit_code = _execute(arguments, output)
|
||||
if exit_code != 0:
|
||||
print('aseprite: failed to export layer spritesheet')
|
||||
print(output)
|
||||
return {}
|
||||
|
||||
return {
|
||||
'data_file': data_file.replace("./", "res://"),
|
||||
"sprite_sheet": sprite_sheet.replace("./", "res://")
|
||||
}
|
||||
|
||||
|
||||
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 column_count : int = options.get("column_count", 0)
|
||||
if column_count > 0:
|
||||
arguments.push_back("--merge-duplicates") # Yes, this is undocumented
|
||||
arguments.push_back("--sheet-columns")
|
||||
arguments.push_back(column_count)
|
||||
else:
|
||||
arguments.push_back("--sheet-pack")
|
||||
|
||||
|
||||
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_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 _export_command_common_arguments(source_name: String, data_path: String, spritesheet_path: String) -> Array:
|
||||
return [
|
||||
"-b",
|
||||
"--list-tags",
|
||||
"--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()
|
||||
19
Sprout/addons/AsepriteWizard/config/2d_pixel_preset.cfg
Normal file
19
Sprout/addons/AsepriteWizard/config/2d_pixel_preset.cfg
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[preset]
|
||||
|
||||
compress/mode=0
|
||||
compress/lossy_quality=0.7
|
||||
compress/hdr_compression=1
|
||||
compress/bptc_ldr=0
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=1
|
||||
311
Sprout/addons/AsepriteWizard/config/config.gd
Normal file
311
Sprout/addons/AsepriteWizard/config/config.gd
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
@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_LOOP_EX_PREFIX = '_'
|
||||
const _LOOP_ENABLED = 'aseprite/animation/loop/enabled'
|
||||
const _LOOP_EXCEPTION_PREFIX = 'aseprite/animation/loop/exception_prefix'
|
||||
const _USE_METADATA = 'aseprite/animation/storage/use_metadata'
|
||||
|
||||
# custom preset
|
||||
const _IMPORT_PRESET_ENABLED = 'aseprite/import/preset/enable_custom_preset'
|
||||
const _IMPORT_PRESET_KEY = 'aseprite/import/preset/preset'
|
||||
const _PIXEL_2D_PRESET_CFG = 'res://addons/AsepriteWizard/config/2d_pixel_preset.cfg'
|
||||
|
||||
# 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'
|
||||
|
||||
# wizard history
|
||||
const _HISTORY_CONFIG_FILE_CFG_KEY = 'aseprite/wizard/history/cache_file_path'
|
||||
const _HISTORY_SINGLE_ENTRY_KEY = 'aseprite/wizard/history/keep_one_entry_per_source_file'
|
||||
const _DEFAULT_HISTORY_CONFIG_FILE_PATH = 'res://.aseprite_wizard_history'
|
||||
|
||||
# IMPORT SETTINGS
|
||||
const _I_LAST_SOURCE_PATH_KEY = 'i_source'
|
||||
const _I_LAST_OUTPUT_DIR_KEY = 'i_output'
|
||||
const _I_SHOULD_SPLIT_LAYERS_KEY = 'i_split_layers'
|
||||
const _I_EXCEPTIONS_KEY = 'i_exceptions_key'
|
||||
const _I_ONLY_VISIBLE_LAYERS_KEY = 'i_only_visible_layers'
|
||||
const _I_CUSTOM_NAME_KEY = 'i_custom_name'
|
||||
const _I_DO_NOT_CREATE_RES_KEY = 'i_disable_resource_creation'
|
||||
|
||||
# export
|
||||
const _EXPORTER_ENABLE_KEY = 'aseprite/animation/storage/enable_metadata_removal_on_export'
|
||||
|
||||
var _editor_settings: EditorSettings
|
||||
|
||||
# INTERFACE SETTINGS
|
||||
var _plugin_icons: Dictionary
|
||||
|
||||
#######################################################
|
||||
# GLOBAL CONFIGS
|
||||
######################################################
|
||||
|
||||
func default_command() -> String:
|
||||
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
|
||||
######################################################
|
||||
func is_importer_enabled() -> bool:
|
||||
return _get_project_setting(_IMPORTER_ENABLE_KEY, false)
|
||||
|
||||
|
||||
func is_exporter_enabled() -> bool:
|
||||
return _get_project_setting(_EXPORTER_ENABLE_KEY, true)
|
||||
|
||||
|
||||
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 is_use_metadata_enabled() -> bool:
|
||||
return _get_project_setting(_USE_METADATA, true)
|
||||
|
||||
|
||||
func get_default_exclusion_pattern() -> String:
|
||||
return _get_project_setting(_DEFAULT_EXCLUSION_PATTERN_KEY, "")
|
||||
|
||||
|
||||
func is_import_preset_enabled() -> bool:
|
||||
return _get_project_setting(_IMPORT_PRESET_ENABLED, false)
|
||||
|
||||
|
||||
func is_single_file_history() -> bool:
|
||||
return ProjectSettings.get(_HISTORY_SINGLE_ENTRY_KEY) == true
|
||||
|
||||
|
||||
func get_import_history() -> Array:
|
||||
var history = []
|
||||
var history_path := _get_history_file_path()
|
||||
|
||||
if not FileAccess.file_exists(history_path):
|
||||
return history
|
||||
|
||||
var file_object = FileAccess.open(history_path, FileAccess.READ)
|
||||
|
||||
while not file_object.eof_reached():
|
||||
var line = file_object.get_line()
|
||||
if line:
|
||||
var test_json_conv = JSON.new()
|
||||
test_json_conv.parse(line)
|
||||
history.push_back(test_json_conv.get_data())
|
||||
|
||||
return history
|
||||
|
||||
|
||||
func is_set_visible_track_automatically_enabled() -> bool:
|
||||
return _get_project_setting(_SET_VISIBLE_TRACK_AUTOMATICALLY, false)
|
||||
|
||||
# history is saved and retrieved line-by-line so
|
||||
# file becomes version control friendly
|
||||
func save_import_history(history: Array):
|
||||
var file = FileAccess.open(_get_history_file_path(), FileAccess.WRITE)
|
||||
for entry in history:
|
||||
file.store_line(JSON.new().stringify(entry))
|
||||
file = null
|
||||
|
||||
|
||||
func _get_history_file_path() -> String:
|
||||
return _get_project_setting(_HISTORY_CONFIG_FILE_CFG_KEY, _DEFAULT_HISTORY_CONFIG_FILE_PATH)
|
||||
|
||||
|
||||
func create_import_preset_setting() -> void:
|
||||
if ProjectSettings.has_setting(_IMPORT_PRESET_KEY) && (ProjectSettings.get_setting(_IMPORT_PRESET_KEY) as Dictionary).size() > 0:
|
||||
return
|
||||
|
||||
var preset := ConfigFile.new()
|
||||
preset.load(_PIXEL_2D_PRESET_CFG)
|
||||
|
||||
var dict = {}
|
||||
for key in preset.get_section_keys("preset"):
|
||||
dict[key] = preset.get_value("preset", key)
|
||||
|
||||
_initialize_project_cfg(_IMPORT_PRESET_KEY, dict, TYPE_DICTIONARY)
|
||||
|
||||
|
||||
#######################################################
|
||||
# IMPORT CONFIGS
|
||||
######################################################
|
||||
func get_last_source_path() -> String:
|
||||
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_SOURCE_PATH_KEY, "")
|
||||
|
||||
|
||||
func set_last_source_path(source_path: String) -> void:
|
||||
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_SOURCE_PATH_KEY, source_path)
|
||||
|
||||
|
||||
func get_last_output_path() -> String:
|
||||
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_OUTPUT_DIR_KEY, "")
|
||||
|
||||
|
||||
func set_last_output_path(output_path: String) -> void:
|
||||
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_LAST_OUTPUT_DIR_KEY, output_path)
|
||||
|
||||
|
||||
func should_split_layers() -> bool:
|
||||
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_SHOULD_SPLIT_LAYERS_KEY, false)
|
||||
|
||||
|
||||
func set_split_layers(should_split: bool) -> void:
|
||||
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_SHOULD_SPLIT_LAYERS_KEY, false)
|
||||
|
||||
|
||||
func get_exception_pattern() -> String:
|
||||
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_EXCEPTIONS_KEY, "")
|
||||
|
||||
|
||||
func set_exception_pattern(pattern: String) -> void:
|
||||
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_EXCEPTIONS_KEY, pattern)
|
||||
|
||||
|
||||
func should_include_only_visible_layers() -> bool:
|
||||
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_ONLY_VISIBLE_LAYERS_KEY, false)
|
||||
|
||||
|
||||
func set_include_only_visible_layers(include_only_visible: bool) -> void:
|
||||
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_ONLY_VISIBLE_LAYERS_KEY, include_only_visible)
|
||||
|
||||
|
||||
func get_last_custom_name() -> String:
|
||||
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_CUSTOM_NAME_KEY, "")
|
||||
|
||||
|
||||
func set_custom_name(custom_name: String) -> void:
|
||||
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_CUSTOM_NAME_KEY, custom_name)
|
||||
|
||||
|
||||
func should_not_create_resource() -> bool:
|
||||
return _editor_settings.get_project_metadata(_CONFIG_SECTION_KEY, _I_DO_NOT_CREATE_RES_KEY, false)
|
||||
|
||||
|
||||
func set_do_not_create_resource(do_no_create: bool) -> void:
|
||||
_editor_settings.set_project_metadata(_CONFIG_SECTION_KEY, _I_DO_NOT_CREATE_RES_KEY, do_no_create)
|
||||
|
||||
#######################################################
|
||||
# INTERFACE SETTINGS
|
||||
######################################################
|
||||
|
||||
func set_icons(plugin_icons: Dictionary) -> void:
|
||||
_plugin_icons = plugin_icons
|
||||
|
||||
|
||||
func get_icon(icon_name: String) -> Texture2D:
|
||||
return _plugin_icons[icon_name]
|
||||
|
||||
|
||||
#######################################################
|
||||
# INITIALIZATION
|
||||
######################################################
|
||||
func initialize_project_settings():
|
||||
_initialize_project_cfg(_DEFAULT_EXCLUSION_PATTERN_KEY, "", TYPE_STRING)
|
||||
_initialize_project_cfg(_LOOP_ENABLED, true, TYPE_BOOL)
|
||||
_initialize_project_cfg(_LOOP_EXCEPTION_PREFIX, _DEFAULT_LOOP_EX_PREFIX, TYPE_STRING)
|
||||
_initialize_project_cfg(_USE_METADATA, true, TYPE_BOOL)
|
||||
|
||||
_initialize_project_cfg(_IMPORT_PRESET_ENABLED, false, TYPE_BOOL)
|
||||
|
||||
_initialize_project_cfg(_REMOVE_SOURCE_FILES_KEY, true, TYPE_BOOL)
|
||||
_initialize_project_cfg(_IMPORTER_ENABLE_KEY, false, TYPE_BOOL)
|
||||
|
||||
_initialize_project_cfg(_EXPORTER_ENABLE_KEY, true, TYPE_BOOL)
|
||||
|
||||
_initialize_project_cfg(_HISTORY_CONFIG_FILE_CFG_KEY, _DEFAULT_HISTORY_CONFIG_FILE_PATH, TYPE_STRING, PROPERTY_HINT_GLOBAL_FILE)
|
||||
_initialize_project_cfg(_HISTORY_SINGLE_ENTRY_KEY, false, TYPE_BOOL)
|
||||
|
||||
_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,
|
||||
_USE_METADATA,
|
||||
_IMPORT_PRESET_ENABLED,
|
||||
_IMPORT_PRESET_KEY,
|
||||
_REMOVE_SOURCE_FILES_KEY,
|
||||
_IMPORTER_ENABLE_KEY,
|
||||
_EXPORTER_ENABLE_KEY,
|
||||
_HISTORY_CONFIG_FILE_CFG_KEY,
|
||||
_HISTORY_SINGLE_ENTRY_KEY,
|
||||
_SET_VISIBLE_TRACK_AUTOMATICALLY
|
||||
]
|
||||
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):
|
||||
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,
|
||||
})
|
||||
|
||||
|
||||
func _get_project_setting(key: String, default_value):
|
||||
var p = ProjectSettings.get(key)
|
||||
return p if p else default_value
|
||||
|
||||
|
||||
func create_import_file(data: Dictionary) -> void:
|
||||
if !ProjectSettings.has_setting(_IMPORT_PRESET_KEY):
|
||||
push_warning("no import settings found for 'aseprite_texture' in Project Settings")
|
||||
return
|
||||
|
||||
var file_path := "%s.import" % [data.sprite_sheet]
|
||||
var import_file := ConfigFile.new()
|
||||
if import_file.load(file_path) == OK:
|
||||
return
|
||||
|
||||
import_file.set_value("remap", "importer", "texture")
|
||||
import_file.set_value("remap", "type", "CompressedTexture2D")
|
||||
import_file.set_value("deps", "source_file", data.sprite_sheet)
|
||||
var preset: Dictionary = ProjectSettings.get_setting(_IMPORT_PRESET_KEY)
|
||||
for key in preset:
|
||||
import_file.set_value("params", key, preset[key])
|
||||
import_file.save(file_path)
|
||||
|
||||
|
||||
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,
|
||||
})
|
||||
33
Sprout/addons/AsepriteWizard/config/config_dialog.gd
Normal file
33
Sprout/addons/AsepriteWizard/config/config_dialog.gd
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
@tool
|
||||
extends PopupPanel
|
||||
|
||||
var _config
|
||||
|
||||
@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 init(config):
|
||||
_config = config
|
||||
|
||||
|
||||
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
|
||||
80
Sprout/addons/AsepriteWizard/config/config_dialog.tscn
Normal file
80
Sprout/addons/AsepriteWizard/config/config_dialog.tscn
Normal 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 Path3D"
|
||||
|
||||
[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"]
|
||||
37
Sprout/addons/AsepriteWizard/config/result_codes.gd
Normal file
37
Sprout/addons/AsepriteWizard/config/result_codes.gd
Normal 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 \"Project > Tools > Aseprite Wizard Config\"."
|
||||
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 }
|
||||
71
Sprout/addons/AsepriteWizard/config/wizard_config.gd
Normal file
71
Sprout/addons/AsepriteWizard/config/wizard_config.gd
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
@tool
|
||||
extends RefCounted
|
||||
|
||||
const WIZARD_CONFIG_META_NAME = "_aseprite_wizard_config_"
|
||||
const WIZARD_CONFIG_MARKER = "aseprite_wizard_config"
|
||||
const SEPARATOR = "|="
|
||||
|
||||
static func encode(object: Dictionary):
|
||||
var text = "%s\n" % WIZARD_CONFIG_MARKER
|
||||
|
||||
for prop in object:
|
||||
text += "%s%s%s\n" % [prop, SEPARATOR, object[prop]]
|
||||
|
||||
return Marshalls.utf8_to_base64(text)
|
||||
|
||||
|
||||
static func decode(string: String):
|
||||
var decoded = _decode_base64(string)
|
||||
if not _is_wizard_config(decoded):
|
||||
return null
|
||||
|
||||
var cfg = decoded.split("\n")
|
||||
var config = {}
|
||||
for c in cfg:
|
||||
var parts = c.split(SEPARATOR, 1)
|
||||
if parts.size() == 2:
|
||||
var key = parts[0].strip_edges()
|
||||
var value = parts[1].strip_edges()
|
||||
|
||||
#Convert bool properties
|
||||
if key == "only_visible" or key == "op_exp":
|
||||
match value:
|
||||
"True":
|
||||
config[key] = true
|
||||
"False":
|
||||
config[key] = false
|
||||
_:
|
||||
config[key] = false
|
||||
else:
|
||||
config[key] = value
|
||||
|
||||
return config
|
||||
|
||||
|
||||
static func _decode_base64(string: String):
|
||||
if string != "":
|
||||
return Marshalls.base64_to_utf8(string)
|
||||
return null
|
||||
|
||||
|
||||
static func _is_wizard_config(cfg) -> bool:
|
||||
return cfg != null and cfg.begins_with(WIZARD_CONFIG_MARKER)
|
||||
|
||||
|
||||
static func load_config(node:Node):
|
||||
if node.has_meta(WIZARD_CONFIG_META_NAME):
|
||||
return node.get_meta(WIZARD_CONFIG_META_NAME)
|
||||
|
||||
return decode(node.editor_description)
|
||||
|
||||
|
||||
static func save_config(node:Node, use_metadata:bool, cfg:Dictionary):
|
||||
if use_metadata:
|
||||
node.set_meta(WIZARD_CONFIG_META_NAME, cfg)
|
||||
|
||||
#Delete config from editor_description
|
||||
var decoded = _decode_base64(node.editor_description)
|
||||
if _is_wizard_config(decoded):
|
||||
node.editor_description = ""
|
||||
else:
|
||||
node.editor_description = encode(cfg)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
extends EditorExportPlugin
|
||||
|
||||
const wizard_config = preload("../config/wizard_config.gd")
|
||||
|
||||
func _export_file(path: String, type: String, features: PackedStringArray) -> void:
|
||||
if type != "PackedScene": return
|
||||
|
||||
var scene : PackedScene = ResourceLoader.load(path, type, true)
|
||||
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, path):
|
||||
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:Node, path: String) -> bool:
|
||||
if node.has_meta(wizard_config.WIZARD_CONFIG_META_NAME):
|
||||
node.remove_meta(wizard_config.WIZARD_CONFIG_META_NAME)
|
||||
print("Aseprite Wizard: Removed metadata from scene %s" % path)
|
||||
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
|
||||
7
Sprout/addons/AsepriteWizard/plugin.cfg
Normal file
7
Sprout/addons/AsepriteWizard/plugin.cfg
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="Aseprite Wizard (Godot 4)"
|
||||
description="Import Aseprite files to AnimationPlayers, AnimatedSprites and SpriteFrames."
|
||||
author="Vinicius Gerevini"
|
||||
version="6.0.0"
|
||||
script="plugin.gd"
|
||||
150
Sprout/addons/AsepriteWizard/plugin.gd
Normal file
150
Sprout/addons/AsepriteWizard/plugin.gd
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const ConfigDialog = preload('config/config_dialog.tscn')
|
||||
const WizardWindow = preload("animated_sprite/docks/as_wizard_dock_container.tscn")
|
||||
const ImportPlugin = preload("animated_sprite/import_plugin.gd")
|
||||
const ExportPlugin = preload("export/metadata_export_plugin.gd")
|
||||
const AnimatedSpriteInspectorPlugin = preload("animated_sprite/inspector_plugin.gd")
|
||||
const SpriteInspectorPlugin = preload("animation_player/inspector_plugin.gd")
|
||||
const menu_item_name = "Aseprite Spritesheet Wizard"
|
||||
const config_menu_item_name = "Aseprite Wizard Config"
|
||||
|
||||
var config = preload("config/config.gd").new()
|
||||
var window: TabContainer
|
||||
var config_window: PopupPanel
|
||||
var import_plugin : EditorImportPlugin
|
||||
var export_plugin : EditorExportPlugin
|
||||
var sprite_inspector_plugin: EditorInspectorPlugin
|
||||
var animated_sprite_inspector_plugin: EditorInspectorPlugin
|
||||
|
||||
var _importer_enabled = false
|
||||
var _exporter_enabled = false
|
||||
|
||||
|
||||
func _enter_tree():
|
||||
_load_config()
|
||||
_setup_menu_entries()
|
||||
_setup_importer()
|
||||
_setup_exporter()
|
||||
_configure_preset()
|
||||
_setup_animated_sprite_inspector_plugin()
|
||||
_setup_sprite_inspector_plugin()
|
||||
|
||||
|
||||
func _disable_plugin():
|
||||
_remove_menu_entries()
|
||||
_remove_importer()
|
||||
_remove_exporter()
|
||||
_remove_wizard_dock()
|
||||
_remove_inspector_plugins()
|
||||
config.clear_project_settings()
|
||||
config.set_icons({})
|
||||
|
||||
|
||||
func _load_config():
|
||||
var editor_gui = get_editor_interface().get_base_control()
|
||||
config._editor_settings = get_editor_interface().get_editor_settings()
|
||||
config.set_icons({
|
||||
"collapsed": editor_gui.get_theme_icon("GuiTreeArrowRight", "EditorIcons"),
|
||||
"expanded": editor_gui.get_theme_icon("GuiTreeArrowDown", "EditorIcons"),
|
||||
})
|
||||
config.initialize_project_settings()
|
||||
|
||||
|
||||
func _setup_menu_entries():
|
||||
add_tool_menu_item(menu_item_name, _open_window)
|
||||
add_tool_menu_item(config_menu_item_name, _open_config_dialog)
|
||||
|
||||
|
||||
func _remove_menu_entries():
|
||||
remove_tool_menu_item(menu_item_name)
|
||||
remove_tool_menu_item(config_menu_item_name)
|
||||
|
||||
|
||||
func _setup_importer():
|
||||
if config.is_importer_enabled():
|
||||
import_plugin = ImportPlugin.new()
|
||||
import_plugin.file_system = get_editor_interface().get_resource_filesystem()
|
||||
import_plugin.config = config
|
||||
add_import_plugin(import_plugin)
|
||||
_importer_enabled = true
|
||||
|
||||
|
||||
func _configure_preset():
|
||||
if config.is_import_preset_enabled():
|
||||
config.create_import_preset_setting()
|
||||
|
||||
|
||||
func _remove_importer():
|
||||
if _importer_enabled:
|
||||
remove_import_plugin(import_plugin)
|
||||
_importer_enabled = false
|
||||
|
||||
|
||||
func _setup_exporter():
|
||||
if config.is_exporter_enabled():
|
||||
export_plugin = ExportPlugin.new()
|
||||
add_export_plugin(export_plugin)
|
||||
_exporter_enabled = true
|
||||
|
||||
|
||||
func _remove_exporter():
|
||||
if _exporter_enabled:
|
||||
remove_export_plugin(export_plugin)
|
||||
_exporter_enabled = false
|
||||
|
||||
|
||||
func _setup_sprite_inspector_plugin():
|
||||
sprite_inspector_plugin = SpriteInspectorPlugin.new()
|
||||
sprite_inspector_plugin.file_system = get_editor_interface().get_resource_filesystem()
|
||||
sprite_inspector_plugin.config = config
|
||||
add_inspector_plugin(sprite_inspector_plugin)
|
||||
|
||||
|
||||
func _setup_animated_sprite_inspector_plugin():
|
||||
animated_sprite_inspector_plugin = AnimatedSpriteInspectorPlugin.new()
|
||||
animated_sprite_inspector_plugin.file_system = get_editor_interface().get_resource_filesystem()
|
||||
animated_sprite_inspector_plugin.config = config
|
||||
add_inspector_plugin(animated_sprite_inspector_plugin)
|
||||
|
||||
|
||||
func _remove_inspector_plugins():
|
||||
remove_inspector_plugin(sprite_inspector_plugin)
|
||||
remove_inspector_plugin(animated_sprite_inspector_plugin)
|
||||
|
||||
|
||||
func _remove_wizard_dock():
|
||||
if window:
|
||||
remove_control_from_bottom_panel(window)
|
||||
window.queue_free()
|
||||
window = null
|
||||
|
||||
|
||||
func _open_window():
|
||||
if window:
|
||||
make_bottom_panel_item_visible(window)
|
||||
return
|
||||
|
||||
window = WizardWindow.instantiate()
|
||||
window.init(config, get_editor_interface().get_resource_filesystem())
|
||||
window.connect("close_requested",Callable(self,"_on_window_closed"))
|
||||
add_control_to_bottom_panel(window, "Aseprite Wizard")
|
||||
make_bottom_panel_item_visible(window)
|
||||
|
||||
|
||||
func _open_config_dialog():
|
||||
if is_instance_valid(config_window):
|
||||
config_window.queue_free()
|
||||
|
||||
config_window = ConfigDialog.instantiate()
|
||||
config_window.init(config)
|
||||
get_editor_interface().get_base_control().add_child(config_window)
|
||||
config_window.popup_centered()
|
||||
|
||||
|
||||
func _on_window_closed():
|
||||
if window:
|
||||
remove_control_from_bottom_panel(window)
|
||||
window.queue_free()
|
||||
window = null
|
||||
Loading…
Add table
Add a link
Reference in a new issue