init commit
This commit is contained in:
212
addons/nklbdev.importality/export/_.gd
Normal file
212
addons/nklbdev.importality/export/_.gd
Normal file
@@ -0,0 +1,212 @@
|
||||
@tool
|
||||
extends RefCounted
|
||||
|
||||
const _Result = preload("../result.gd").Class
|
||||
const _Common = preload("../common.gd")
|
||||
const _Options = preload("../options.gd")
|
||||
const _Setting = preload("../setting.gd")
|
||||
const _DirAccessExtensions = preload("../dir_access_ext.gd")
|
||||
|
||||
const _SpriteSheetBuilderBase = preload("../sprite_sheet_builder/_.gd")
|
||||
const _GridBasedSpriteSheetBuilder = preload("../sprite_sheet_builder/grid_based.gd")
|
||||
const _PackedSpriteSheetBuilder = preload("../sprite_sheet_builder/packed.gd")
|
||||
|
||||
const ATLAS_TEXTURE_RESOURCE_TYPE_NAMES: PackedStringArray = [
|
||||
"Embedded PortableCompressedTexture2D (compact)",
|
||||
"Embedded ImageTexture (large)",
|
||||
"Separated image (custom)",
|
||||
]
|
||||
enum AtlasResourceType {
|
||||
EMBEDDED_PORTABLE_COMPRESSED_TEXTURE_2D = 0,
|
||||
EMBEDDED_IMAGE_TEXTURE = 1,
|
||||
SEPARATED_IMAGE = 2,
|
||||
}
|
||||
|
||||
class ExportResult:
|
||||
extends _Result
|
||||
var atlas_image: Image
|
||||
var sprite_sheet: _Common.SpriteSheetInfo
|
||||
var animation_library: _Common.AnimationLibraryInfo
|
||||
func _get_result_type_description() -> String:
|
||||
return "Export"
|
||||
func success(
|
||||
atlas_image: Image,
|
||||
sprite_sheet: _Common.SpriteSheetInfo,
|
||||
animation_library: _Common.AnimationLibraryInfo
|
||||
) -> void:
|
||||
_success()
|
||||
self.atlas_image = atlas_image
|
||||
self.sprite_sheet = sprite_sheet
|
||||
self.animation_library = animation_library
|
||||
|
||||
var __name: String
|
||||
var __recognized_extensions: PackedStringArray
|
||||
var __settings: Array[_Setting] = [_Common.common_temporary_files_directory_path_setting]
|
||||
var __options: Array[Dictionary] = [
|
||||
_Options.create_option(_Options.EDGES_ARTIFACTS_AVOIDANCE_METHOD, _Common.EdgesArtifactsAvoidanceMethod.NONE,
|
||||
PROPERTY_HINT_ENUM, ",".join(_Common.EDGES_ARTIFACTS_AVOIDANCE_METHODS_NAMES),
|
||||
PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED),
|
||||
_Options.create_option(_Options.SPRITES_SURROUNDING_COLOR, Color.TRANSPARENT,
|
||||
PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT,
|
||||
func(o): return o[_Options.EDGES_ARTIFACTS_AVOIDANCE_METHOD] == \
|
||||
_Common.EdgesArtifactsAvoidanceMethod.SOLID_COLOR_SURROUNDING),
|
||||
_Options.create_option(_Options.SPRITE_SHEET_LAYOUT, _Common.SpriteSheetLayout.PACKED,
|
||||
PROPERTY_HINT_ENUM, ",".join(_Common.SPRITE_SHEET_LAYOUTS_NAMES),
|
||||
PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED),
|
||||
_Options.create_option(_Options.MAX_CELLS_IN_STRIP, 0,
|
||||
PROPERTY_HINT_RANGE, "0,,1,or_greater", PROPERTY_USAGE_DEFAULT,
|
||||
func(o): return o[_Options.SPRITE_SHEET_LAYOUT] != \
|
||||
_Common.SpriteSheetLayout.PACKED),
|
||||
_Options.create_option(_Options.TRIM_SPRITES_TO_OVERALL_MIN_SIZE, true,
|
||||
PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT,
|
||||
func(o): return o[_Options.SPRITE_SHEET_LAYOUT] != \
|
||||
_Common.SpriteSheetLayout.PACKED),
|
||||
_Options.create_option(_Options.COLLAPSE_TRANSPARENT_SPRITES, true,
|
||||
PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT,
|
||||
func(o): return o[_Options.SPRITE_SHEET_LAYOUT] != \
|
||||
_Common.SpriteSheetLayout.PACKED),
|
||||
_Options.create_option(_Options.MERGE_DUPLICATED_SPRITES, true,
|
||||
PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT,
|
||||
func(o): return o[_Options.SPRITE_SHEET_LAYOUT] != \
|
||||
_Common.SpriteSheetLayout.PACKED),
|
||||
]
|
||||
var __image_format_loader_extension: ImageFormatLoaderExtension
|
||||
|
||||
func _init(
|
||||
name: String,
|
||||
recognized_extensions: PackedStringArray,
|
||||
options: Array[Dictionary],
|
||||
settings: Array[_Setting],
|
||||
image_format_loader_extension: ImageFormatLoaderExtension = null
|
||||
) -> void:
|
||||
__name = name
|
||||
__recognized_extensions = recognized_extensions
|
||||
__options.append_array(options)
|
||||
__settings.append_array(settings)
|
||||
__image_format_loader_extension = image_format_loader_extension
|
||||
|
||||
func get_recognized_extensions() -> PackedStringArray:
|
||||
return __recognized_extensions
|
||||
|
||||
func get_options() -> Array[Dictionary]:
|
||||
return __options
|
||||
|
||||
func get_name() -> String:
|
||||
return __name
|
||||
|
||||
func get_settings() -> Array[_Setting]:
|
||||
return __settings
|
||||
|
||||
func get_image_format_loader_extension() -> ImageFormatLoaderExtension:
|
||||
return __image_format_loader_extension
|
||||
|
||||
func export(
|
||||
res_source_file_path: String,
|
||||
options: Dictionary,
|
||||
editor_import_plugin: EditorImportPlugin
|
||||
) -> ExportResult:
|
||||
return _export(
|
||||
res_source_file_path,
|
||||
options)
|
||||
|
||||
func _export(source_file: String, options: Dictionary) -> ExportResult:
|
||||
assert(false, "This method is abstract and must be overriden.")
|
||||
var result: ExportResult = ExportResult.new()
|
||||
result.fail(ERR_UNCONFIGURED)
|
||||
return result
|
||||
|
||||
enum AnimationOptions {
|
||||
FramesCount = 1,
|
||||
Direction = 2,
|
||||
RepeatCount = 4,
|
||||
}
|
||||
|
||||
static var __option_regex: RegEx = RegEx.create_from_string("\\s-\\p{L}:\\s*\\S+")
|
||||
static var __natural_number_regex: RegEx = RegEx.create_from_string("\\A\\d+\\z")
|
||||
|
||||
class AnimationParamsParsingResult:
|
||||
extends _Result
|
||||
var name: String
|
||||
var first_frame_index: int
|
||||
var frames_count: int
|
||||
var direction: _Common.AnimationDirection
|
||||
var repeat_count: int
|
||||
func _get_result_type_description() -> String:
|
||||
return "Animation parameters parsing"
|
||||
|
||||
static func _parse_animation_params(
|
||||
raw_animation_params: String,
|
||||
animation_options: AnimationOptions,
|
||||
first_frame_index: int,
|
||||
frames_count: int = 0
|
||||
) -> AnimationParamsParsingResult:
|
||||
var result = AnimationParamsParsingResult.new()
|
||||
if first_frame_index < 0:
|
||||
result.fail(ERR_INVALID_DATA, "Wrong value for animation first frame index. Expected natural number, got: %s" % [first_frame_index])
|
||||
return result
|
||||
result.first_frame_index = first_frame_index
|
||||
result.frames_count = frames_count
|
||||
result.direction = -1
|
||||
result.repeat_count = -1
|
||||
raw_animation_params = raw_animation_params.strip_edges()
|
||||
var options_matches: Array[RegExMatch] = __option_regex.search_all(raw_animation_params)
|
||||
var first_match_position: int = raw_animation_params.length()
|
||||
for option_match in options_matches:
|
||||
var match_position: int = option_match.get_start()
|
||||
assert(match_position >= 0)
|
||||
if match_position < first_match_position:
|
||||
first_match_position = match_position
|
||||
var raw_option: String = option_match.get_string().strip_edges()
|
||||
var raw_value = raw_option.substr(3).strip_edges()
|
||||
match raw_option.substr(0, 3):
|
||||
"-f:":
|
||||
if animation_options & AnimationOptions.FramesCount:
|
||||
if result.frames_count == 0:
|
||||
if __natural_number_regex.search(raw_value):
|
||||
result.frames_count = raw_value.to_int()
|
||||
if result.frames_count <= 0:
|
||||
result.fail(ERR_INVALID_DATA, "Wrong value format for frames count. Expected positive integer number, got: \"%s\"" % [raw_value])
|
||||
return result
|
||||
"-d:":
|
||||
if animation_options & AnimationOptions.Direction:
|
||||
if result.direction < 0:
|
||||
match raw_value:
|
||||
"f": result.direction = _Common.AnimationDirection.FORWARD
|
||||
"r": result.direction = _Common.AnimationDirection.REVERSE
|
||||
"pp": result.direction = _Common.AnimationDirection.PING_PONG
|
||||
"ppr": result.direction = _Common.AnimationDirection.PING_PONG_REVERSE
|
||||
_:
|
||||
result.fail(ERR_INVALID_DATA, "Wrong value format for animation direction. Expected one of: [\"f\", \"r\", \"pp\", \"ppr\"], got: \"%s\"" % [raw_value])
|
||||
return result
|
||||
"-r:":
|
||||
if animation_options & AnimationOptions.RepeatCount:
|
||||
if result.repeat_count < 0:
|
||||
if __natural_number_regex.search(raw_value):
|
||||
result.repeat_count = raw_value.to_int()
|
||||
else:
|
||||
result.fail(ERR_INVALID_DATA, "Wrong value format for repeat count. Expected positive integer number or zero, got: \"%s\"" % [raw_value])
|
||||
return result
|
||||
_: pass # Ignore unknown parameter
|
||||
result.name = raw_animation_params.left(first_match_position).strip_edges()
|
||||
if result.frames_count <= 0:
|
||||
result.fail(ERR_UNCONFIGURED, "Animation frames count is required but not specified")
|
||||
return result
|
||||
return result
|
||||
|
||||
func _create_sprite_sheet_builder(options: Dictionary) -> _SpriteSheetBuilderBase:
|
||||
var sprite_sheet_layout: _Common.SpriteSheetLayout = options[_Options.SPRITE_SHEET_LAYOUT]
|
||||
return \
|
||||
_PackedSpriteSheetBuilder.new(
|
||||
options[_Options.EDGES_ARTIFACTS_AVOIDANCE_METHOD],
|
||||
options[_Options.SPRITES_SURROUNDING_COLOR]) \
|
||||
if sprite_sheet_layout == _Common.SpriteSheetLayout.PACKED else \
|
||||
_GridBasedSpriteSheetBuilder.new(
|
||||
options[_Options.EDGES_ARTIFACTS_AVOIDANCE_METHOD],
|
||||
_GridBasedSpriteSheetBuilder.StripDirection.HORIZONTAL
|
||||
if sprite_sheet_layout == _Common.SpriteSheetLayout.HORIZONTAL_STRIPS else
|
||||
_GridBasedSpriteSheetBuilder.StripDirection.HORIZONTAL,
|
||||
options[_Options.MAX_CELLS_IN_STRIP],
|
||||
options[_Options.TRIM_SPRITES_TO_OVERALL_MIN_SIZE],
|
||||
options[_Options.COLLAPSE_TRANSPARENT_SPRITES],
|
||||
options[_Options.MERGE_DUPLICATED_SPRITES],
|
||||
options[_Options.SPRITES_SURROUNDING_COLOR])
|
||||
1
addons/nklbdev.importality/export/_.gd.uid
Normal file
1
addons/nklbdev.importality/export/_.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c3omv6s66bvkh
|
||||
288
addons/nklbdev.importality/export/aseprite.gd
Normal file
288
addons/nklbdev.importality/export/aseprite.gd
Normal file
@@ -0,0 +1,288 @@
|
||||
@tool
|
||||
extends "_.gd"
|
||||
|
||||
const __aseprite_sheet_types_by_sprite_sheet_layout: PackedStringArray = \
|
||||
[ "packed", "rows", "columns" ]
|
||||
const __aseprite_animation_directions: PackedStringArray = \
|
||||
[ "forward", "reverse", "pingpong", "pingpong_reverse" ]
|
||||
|
||||
var __os_command_setting: _Setting = _Setting.new(
|
||||
"aseprite_or_libre_sprite_command", "", TYPE_STRING, PROPERTY_HINT_NONE,
|
||||
"", true, func(v: String): return v.is_empty())
|
||||
|
||||
var __os_command_arguments_setting: _Setting = _Setting.new(
|
||||
"aseprite_or_libre_sprite_command_arguments", PackedStringArray(), TYPE_PACKED_STRING_ARRAY, PROPERTY_HINT_NONE,
|
||||
"", true, func(v: PackedStringArray): return false)
|
||||
|
||||
func _init(editor_file_system: EditorFileSystem) -> void:
|
||||
var recognized_extensions: PackedStringArray = ["ase", "aseprite"]
|
||||
super("Aseprite", recognized_extensions, [],
|
||||
[__os_command_setting, __os_command_arguments_setting],
|
||||
CustomImageFormatLoaderExtension.new(
|
||||
recognized_extensions,
|
||||
__os_command_setting,
|
||||
__os_command_arguments_setting,
|
||||
_Common.common_temporary_files_directory_path_setting))
|
||||
|
||||
func _export(res_source_file_path: String, options: Dictionary) -> ExportResult:
|
||||
var result: ExportResult = ExportResult.new()
|
||||
var err: Error
|
||||
|
||||
var os_command_result: _Setting.GettingValueResult = __os_command_setting.get_value()
|
||||
if os_command_result.error:
|
||||
result.fail(ERR_UNCONFIGURED, "Failed to get Aseprite Command to export spritesheet", os_command_result)
|
||||
return result
|
||||
|
||||
var os_command_arguments_result: _Setting.GettingValueResult = __os_command_arguments_setting.get_value()
|
||||
if os_command_arguments_result.error:
|
||||
result.fail(ERR_UNCONFIGURED, "Failed to get Aseprite Command Arguments to export spritesheet", os_command_arguments_result)
|
||||
return result
|
||||
|
||||
var temp_dir_path_result: _Setting.GettingValueResult = _Common.common_temporary_files_directory_path_setting.get_value()
|
||||
if temp_dir_path_result.error:
|
||||
result.fail(ERR_UNCONFIGURED, "Failed to get Temporary Files Directory Path to export spritesheet", temp_dir_path_result)
|
||||
return result
|
||||
var global_temp_dir_path: String = ProjectSettings.globalize_path(
|
||||
temp_dir_path_result.value.strip_edges())
|
||||
var unique_temp_dir_creation_result: _DirAccessExtensions.CreationResult = \
|
||||
_DirAccessExtensions.create_directory_with_unique_name(global_temp_dir_path)
|
||||
if unique_temp_dir_creation_result.error:
|
||||
result.fail(ERR_QUERY_FAILED, "Failed to create unique temporary directory to export spritesheet", unique_temp_dir_creation_result)
|
||||
return result
|
||||
var unique_temp_dir_path: String = unique_temp_dir_creation_result.path
|
||||
|
||||
var global_source_file_path: String = ProjectSettings.globalize_path(res_source_file_path)
|
||||
|
||||
var global_png_path: String = unique_temp_dir_path.path_join("temp.png")
|
||||
var global_json_path: String = unique_temp_dir_path.path_join("temp.json")
|
||||
|
||||
var command: String = os_command_result.value.strip_edges()
|
||||
var arguments: PackedStringArray = \
|
||||
os_command_arguments_result.value + \
|
||||
PackedStringArray([
|
||||
"--batch",
|
||||
"--format", "json-array",
|
||||
"--list-tags",
|
||||
"--sheet", global_png_path,
|
||||
"--data", global_json_path,
|
||||
global_source_file_path])
|
||||
|
||||
var output: Array = []
|
||||
var exit_code: int = OS.execute(command, arguments, output, true, false)
|
||||
if exit_code:
|
||||
for arg_index in arguments.size():
|
||||
arguments[arg_index] = "\nArgument: " + arguments[arg_index]
|
||||
result.fail(ERR_QUERY_FAILED, " ".join([
|
||||
"An error occurred while executing the Aseprite command.",
|
||||
"Process exited with code %s:\nCommand: %s%s"
|
||||
]) % [exit_code, command, "".join(arguments)])
|
||||
return result
|
||||
var raw_atlas_image: Image = Image.load_from_file(global_png_path)
|
||||
var json = JSON.new()
|
||||
err = json.parse(FileAccess.get_file_as_string(global_json_path))
|
||||
if err:
|
||||
result.fail(ERR_INVALID_DATA, "Failed to parse sprite sheet json data with error %s \"%s\"" % [err, error_string(err)])
|
||||
return result
|
||||
var raw_sprite_sheet_data: Dictionary = json.data
|
||||
|
||||
var sprite_sheet_layout: _Common.SpriteSheetLayout = options[_Options.SPRITE_SHEET_LAYOUT]
|
||||
var source_image_size: Vector2i = _Common.get_vector2i(
|
||||
raw_sprite_sheet_data.frames[0].sourceSize, "w", "h")
|
||||
|
||||
var frames_images_by_indices: Dictionary
|
||||
var tags_data: Array = raw_sprite_sheet_data.meta.frameTags
|
||||
var frames_data: Array = raw_sprite_sheet_data.frames
|
||||
var frames_count: int = frames_data.size()
|
||||
if tags_data.is_empty():
|
||||
var default_animation_name: String = options[_Options.DEFAULT_ANIMATION_NAME].strip_edges()
|
||||
if default_animation_name.is_empty():
|
||||
default_animation_name = "default"
|
||||
tags_data.push_back({
|
||||
name = default_animation_name,
|
||||
from = 0,
|
||||
to = frames_count - 1,
|
||||
direction = __aseprite_animation_directions[options[_Options.DEFAULT_ANIMATION_DIRECTION]],
|
||||
repeat = options[_Options.DEFAULT_ANIMATION_REPEAT_COUNT]
|
||||
})
|
||||
var animations_count: int = tags_data.size()
|
||||
for tag_data in tags_data:
|
||||
for frame_index in range(tag_data.from, tag_data.to + 1):
|
||||
if frames_images_by_indices.has(frame_index):
|
||||
continue
|
||||
var frame_data: Dictionary = frames_data[frame_index]
|
||||
frames_images_by_indices[frame_index] = raw_atlas_image.get_region(Rect2i(
|
||||
_Common.get_vector2i(frame_data.frame, "x", "y"),
|
||||
source_image_size))
|
||||
var used_frames_indices: PackedInt32Array = PackedInt32Array(frames_images_by_indices.keys())
|
||||
used_frames_indices.sort()
|
||||
var used_frames_count: int = used_frames_indices.size()
|
||||
var sprite_sheet_frames_indices_by_global_frame_indices: Dictionary
|
||||
for sprite_sheet_frame_index in used_frames_indices.size():
|
||||
sprite_sheet_frames_indices_by_global_frame_indices[
|
||||
used_frames_indices[sprite_sheet_frame_index]] = \
|
||||
sprite_sheet_frame_index
|
||||
var used_frames_images: Array[Image]
|
||||
used_frames_images.resize(used_frames_count)
|
||||
for i in used_frames_count:
|
||||
used_frames_images[i] = frames_images_by_indices[used_frames_indices[i]]
|
||||
|
||||
var sprite_sheet_builder: _SpriteSheetBuilderBase = _create_sprite_sheet_builder(options)
|
||||
|
||||
var sprite_sheet_building_result: _SpriteSheetBuilderBase.SpriteSheetBuildingResult = sprite_sheet_builder.build_sprite_sheet(used_frames_images)
|
||||
if sprite_sheet_building_result.error:
|
||||
result.fail(ERR_BUG, "Sprite sheet building failed", sprite_sheet_building_result)
|
||||
return result
|
||||
var sprite_sheet: _Common.SpriteSheetInfo = sprite_sheet_building_result.sprite_sheet
|
||||
|
||||
var animation_library: _Common.AnimationLibraryInfo = _Common.AnimationLibraryInfo.new()
|
||||
var autoplay_animation_name: String = options[_Options.AUTOPLAY_ANIMATION_NAME].strip_edges()
|
||||
|
||||
var all_frames: Array[_Common.FrameInfo]
|
||||
all_frames.resize(used_frames_count)
|
||||
var unique_animations_names: PackedStringArray
|
||||
for animation_index in animations_count:
|
||||
var tag_data: Dictionary = tags_data[animation_index]
|
||||
|
||||
var animation_params_parsing_result: AnimationParamsParsingResult = _parse_animation_params(
|
||||
tag_data.name.strip_edges(),
|
||||
AnimationOptions.Direction | AnimationOptions.RepeatCount,
|
||||
tag_data.from,
|
||||
tag_data.to - tag_data.from + 1)
|
||||
if animation_params_parsing_result.error:
|
||||
result.fail(ERR_CANT_RESOLVE, "Failed to parse animation parameters",
|
||||
animation_params_parsing_result)
|
||||
return result
|
||||
if unique_animations_names.has(animation_params_parsing_result.name):
|
||||
result.fail(ERR_INVALID_DATA, "Duplicated animation name \"%s\" at index: %s" %
|
||||
[animation_params_parsing_result.name, animation_index])
|
||||
return result
|
||||
unique_animations_names.push_back(animation_params_parsing_result.name)
|
||||
var animation = _Common.AnimationInfo.new()
|
||||
animation.name = animation_params_parsing_result.name
|
||||
if animation.name.is_empty():
|
||||
result.fail(ERR_INVALID_DATA, "A tag with empty name found")
|
||||
return result
|
||||
if animation.name == autoplay_animation_name:
|
||||
animation_library.autoplay_index = animation_index
|
||||
animation.direction = __aseprite_animation_directions.find(tag_data.direction)
|
||||
if animation_params_parsing_result.direction >= 0:
|
||||
animation.direction = animation_params_parsing_result.direction
|
||||
animation.repeat_count = int(tag_data.get("repeat", "0"))
|
||||
if animation_params_parsing_result.repeat_count >= 0:
|
||||
animation.repeat_count = animation_params_parsing_result.repeat_count
|
||||
for global_frame_index in range(tag_data.from, tag_data.to + 1):
|
||||
var sprite_sheet_frame_index: int = \
|
||||
sprite_sheet_frames_indices_by_global_frame_indices[global_frame_index]
|
||||
var frame: _Common.FrameInfo = all_frames[sprite_sheet_frame_index]
|
||||
if frame == null:
|
||||
frame = _Common.FrameInfo.new()
|
||||
frame.sprite = sprite_sheet.sprites[sprite_sheet_frame_index]
|
||||
frame.duration = frames_data[global_frame_index].duration * 0.001
|
||||
all_frames[sprite_sheet_frame_index] = frame
|
||||
animation.frames.push_back(frame)
|
||||
animation_library.animations.push_back(animation)
|
||||
|
||||
if not autoplay_animation_name.is_empty() and animation_library.autoplay_index < 0:
|
||||
push_warning("Autoplay animation name not found: \"%s\". Continuing..." % [autoplay_animation_name])
|
||||
|
||||
if _DirAccessExtensions.remove_dir_recursive(unique_temp_dir_path).error:
|
||||
push_warning(
|
||||
"Failed to remove unique temporary directory: \"%s\"" %
|
||||
[unique_temp_dir_path])
|
||||
|
||||
result.success(sprite_sheet_building_result.atlas_image, sprite_sheet, animation_library)
|
||||
return result
|
||||
|
||||
class CustomImageFormatLoaderExtension:
|
||||
extends ImageFormatLoaderExtension
|
||||
|
||||
var __recognized_extensions: PackedStringArray
|
||||
var __os_command_setting: _Setting
|
||||
var __os_command_arguments_setting: _Setting
|
||||
var __common_temporary_files_directory_path_setting: _Setting
|
||||
|
||||
func _init(recognized_extensions: PackedStringArray,
|
||||
os_command_setting: _Setting,
|
||||
os_command_arguments_setting: _Setting,
|
||||
common_temporary_files_directory_path_setting: _Setting
|
||||
) -> void:
|
||||
__recognized_extensions = recognized_extensions
|
||||
__os_command_setting = os_command_setting
|
||||
__os_command_arguments_setting = os_command_arguments_setting
|
||||
__common_temporary_files_directory_path_setting = \
|
||||
common_temporary_files_directory_path_setting
|
||||
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return __recognized_extensions
|
||||
|
||||
func _load_image(image: Image, file_access: FileAccess, flags: int, scale: float) -> Error:
|
||||
var global_source_file_path: String = file_access.get_path_absolute()
|
||||
var err: Error
|
||||
|
||||
var os_command_result: _Setting.GettingValueResult = __os_command_setting.get_value()
|
||||
if os_command_result.error:
|
||||
push_error(os_command_result.error_description)
|
||||
return os_command_result.error
|
||||
|
||||
var os_command_arguments_result: _Setting.GettingValueResult = __os_command_arguments_setting.get_value()
|
||||
if os_command_arguments_result.error:
|
||||
push_error(os_command_arguments_result.error_description)
|
||||
return os_command_arguments_result.error
|
||||
|
||||
var temp_dir_path_result: _Setting.GettingValueResult = _Common.common_temporary_files_directory_path_setting.get_value()
|
||||
if temp_dir_path_result.error:
|
||||
push_error("Failed to get Temporary Files Directory Path to export spritesheet")
|
||||
return temp_dir_path_result.error
|
||||
var global_temp_dir_path: String = ProjectSettings.globalize_path(
|
||||
temp_dir_path_result.value.strip_edges())
|
||||
var unique_temp_dir_creation_result: _DirAccessExtensions.CreationResult = \
|
||||
_DirAccessExtensions.create_directory_with_unique_name(global_temp_dir_path)
|
||||
if unique_temp_dir_creation_result.error:
|
||||
push_error("Failed to create unique temporary directory to export spritesheet")
|
||||
return unique_temp_dir_creation_result.error
|
||||
var unique_temp_dir_path: String = unique_temp_dir_creation_result.path
|
||||
|
||||
var global_png_path: String = unique_temp_dir_path.path_join("temp.png")
|
||||
var global_json_path: String = unique_temp_dir_path.path_join("temp.json")
|
||||
|
||||
var command: String = os_command_result.value.strip_edges()
|
||||
var arguments: PackedStringArray = \
|
||||
os_command_arguments_result.value + \
|
||||
PackedStringArray([
|
||||
"--batch",
|
||||
"--format", "json-array",
|
||||
"--list-tags",
|
||||
"--sheet", global_png_path,
|
||||
"--data", global_json_path,
|
||||
global_source_file_path,
|
||||
])
|
||||
|
||||
var output: Array = []
|
||||
var exit_code: int = OS.execute(command, arguments, output, true, false)
|
||||
if exit_code:
|
||||
for arg_index in arguments.size():
|
||||
arguments[arg_index] = "\nArgument: " + arguments[arg_index]
|
||||
push_error(" ".join([
|
||||
"An error occurred while executing the Aseprite command.",
|
||||
"Process exited with code %s:\nCommand: %s%s"
|
||||
]) % [exit_code, command, "".join(arguments)])
|
||||
return ERR_QUERY_FAILED
|
||||
|
||||
var raw_atlas_image: Image = Image.load_from_file(global_png_path)
|
||||
var json = JSON.new()
|
||||
err = json.parse(FileAccess.get_file_as_string(global_json_path))
|
||||
if err:
|
||||
push_error("Failed to parse sprite sheet json data with error %s \"%s\"" % [err, error_string(err)])
|
||||
return ERR_INVALID_DATA
|
||||
var raw_sprite_sheet_data: Dictionary = json.data
|
||||
|
||||
var source_image_size: Vector2i = _Common.get_vector2i(
|
||||
raw_sprite_sheet_data.frames[0].sourceSize, "w", "h")
|
||||
|
||||
if _DirAccessExtensions.remove_dir_recursive(unique_temp_dir_path).error:
|
||||
push_warning(
|
||||
"Failed to remove unique temporary directory: \"%s\"" %
|
||||
[unique_temp_dir_path])
|
||||
|
||||
image.copy_from(raw_atlas_image.get_region(Rect2i(Vector2i.ZERO, source_image_size)))
|
||||
return OK
|
||||
1
addons/nklbdev.importality/export/aseprite.gd.uid
Normal file
1
addons/nklbdev.importality/export/aseprite.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://c3qof0o2rjew
|
||||
286
addons/nklbdev.importality/export/krita.gd
Normal file
286
addons/nklbdev.importality/export/krita.gd
Normal file
@@ -0,0 +1,286 @@
|
||||
@tool
|
||||
extends "_.gd"
|
||||
|
||||
const _XML = preload("../xml.gd")
|
||||
|
||||
var __os_command_setting: _Setting = _Setting.new(
|
||||
"krita_command", "", TYPE_STRING, PROPERTY_HINT_NONE,
|
||||
"", true, func(v: String): return v.is_empty())
|
||||
|
||||
var __os_command_arguments_setting: _Setting = _Setting.new(
|
||||
"krita_command_arguments", PackedStringArray(), TYPE_PACKED_STRING_ARRAY, PROPERTY_HINT_NONE,
|
||||
"", true, func(v: PackedStringArray): return false)
|
||||
|
||||
func _init(editor_file_system: EditorFileSystem) -> void:
|
||||
var recognized_extensions: PackedStringArray = ["kra", "krita"]
|
||||
super("Krita", recognized_extensions, [], [
|
||||
__os_command_setting,
|
||||
__os_command_arguments_setting,
|
||||
], CustomImageFormatLoaderExtension.new(recognized_extensions))
|
||||
|
||||
func __validate_image_name(image_name: String) -> _Result:
|
||||
var result: _Result = _Result.new()
|
||||
var image_name_with_underscored_invalid_characters: String = image_name.validate_filename()
|
||||
var unsupported_characters: PackedStringArray
|
||||
for character_index in image_name.length():
|
||||
var validated_character = image_name_with_underscored_invalid_characters[character_index]
|
||||
if validated_character == "_":
|
||||
var original_character = image_name[character_index]
|
||||
if original_character != "_":
|
||||
if not unsupported_characters.has(original_character):
|
||||
unsupported_characters.push_back(original_character)
|
||||
if not unsupported_characters.is_empty():
|
||||
result.fail(ERR_FILE_BAD_PATH, "There are unsupported characters in Krita Document Title: \"%s\"" % ["".join(unsupported_characters)])
|
||||
return result
|
||||
|
||||
func _export(res_source_file_path: String, options: Dictionary) -> ExportResult:
|
||||
var result: ExportResult = ExportResult.new()
|
||||
var err: Error
|
||||
|
||||
var os_command_result: _Setting.GettingValueResult = __os_command_setting.get_value()
|
||||
if os_command_result.error:
|
||||
result.fail(ERR_UNCONFIGURED, "Failed to get Krita Command to export spritesheet", os_command_result)
|
||||
return result
|
||||
|
||||
var os_command_arguments_result: _Setting.GettingValueResult = __os_command_arguments_setting.get_value()
|
||||
if os_command_arguments_result.error:
|
||||
result.fail(ERR_UNCONFIGURED, "Failed to get Krita Command Arguments to export spritesheet", os_command_arguments_result)
|
||||
return result
|
||||
|
||||
var temp_dir_path_result: _Setting.GettingValueResult = _Common.common_temporary_files_directory_path_setting.get_value()
|
||||
if temp_dir_path_result.error:
|
||||
result.fail(ERR_UNCONFIGURED, "Failed to get Temporary Files Directory Path to export spritesheet", temp_dir_path_result)
|
||||
return result
|
||||
var global_temp_dir_path: String = ProjectSettings.globalize_path(
|
||||
temp_dir_path_result.value.strip_edges())
|
||||
var unique_temp_dir_creation_result: _DirAccessExtensions.CreationResult = \
|
||||
_DirAccessExtensions.create_directory_with_unique_name(global_temp_dir_path)
|
||||
if unique_temp_dir_creation_result.error:
|
||||
result.fail(ERR_QUERY_FAILED, "Failed to create unique temporary directory to export spritesheet", unique_temp_dir_creation_result)
|
||||
return result
|
||||
var unique_temp_dir_path: String = unique_temp_dir_creation_result.path
|
||||
|
||||
var global_source_file_path: String = ProjectSettings.globalize_path(res_source_file_path)
|
||||
|
||||
var zip_reader: ZIPReader = ZIPReader.new()
|
||||
var zip_error: Error = zip_reader.open(global_source_file_path)
|
||||
if zip_error:
|
||||
result.fail(zip_error, "Failed to open Krita file \"%s\" as ZIP archive with error: %s (%s)" % [res_source_file_path, zip_error, error_string(zip_error)])
|
||||
return result
|
||||
|
||||
var files_names_in_zip: PackedStringArray = zip_reader.get_files()
|
||||
|
||||
var maindoc_filename: String = "maindoc.xml"
|
||||
var maindoc_buffer: PackedByteArray = zip_reader.read_file(maindoc_filename)
|
||||
var maindoc_xml_root: _XML.XMLNodeRoot = _XML.parse_buffer(maindoc_buffer)
|
||||
var maindoc_doc_xml_element: _XML.XMLNodeElement = maindoc_xml_root.get_elements("DOC").front()
|
||||
|
||||
var image_xml_element: _XML.XMLNodeElement = maindoc_doc_xml_element.get_elements("IMAGE").front()
|
||||
var image_name: String = image_xml_element.get_string("name")
|
||||
var image_size: Vector2i = image_xml_element.get_vector2i("width", "height")
|
||||
var image_name_validation_result: _Result = __validate_image_name(image_name)
|
||||
if image_name_validation_result.error:
|
||||
result.fail(ERR_INVALID_DATA,
|
||||
"Krita Document Title have unsupported format",
|
||||
image_name_validation_result)
|
||||
return result
|
||||
|
||||
var has_keyframes: bool
|
||||
for layer_xml_element in image_xml_element.get_elements("layers").front().get_elements("layer"):
|
||||
if layer_xml_element.attributes.has("keyframes"):
|
||||
has_keyframes = true
|
||||
break
|
||||
if not has_keyframes:
|
||||
result.fail(ERR_INVALID_DATA, "Source file has no keyframes")
|
||||
return result
|
||||
|
||||
var animation_xml_element: _XML.XMLNodeElement = image_xml_element.get_elements("animation").front()
|
||||
var animation_framerate: int = max(1, animation_xml_element.get_elements("framerate").front().get_int("value"))
|
||||
var animation_range_xml_element: _XML.XMLNodeElement = animation_xml_element.get_elements("range").front()
|
||||
|
||||
var animation_index_filename: String = "%s/animation/index.xml" % image_name
|
||||
var animation_index_buffer: PackedByteArray = zip_reader.read_file(animation_index_filename)
|
||||
var animation_index_xml_root: _XML.XMLNodeRoot = _XML.parse_buffer(animation_index_buffer)
|
||||
var animation_index_animation_metadata_xml_element: _XML.XMLNodeElement = animation_index_xml_root.get_elements("animation-metadata").front()
|
||||
var animation_index_animation_metadata_range_xml_element: _XML.XMLNodeElement = animation_index_animation_metadata_xml_element.get_elements("range").front()
|
||||
var export_settings_xml_element: _XML.XMLNodeElement = animation_index_animation_metadata_xml_element.get_elements("export-settings").front()
|
||||
var sequence_file_path_xml_element: _XML.XMLNodeElement = export_settings_xml_element.get_elements("sequenceFilePath").front()
|
||||
var sequence_base_name_xml_element: _XML.XMLNodeElement = export_settings_xml_element.get_elements("sequenceBaseName").front()
|
||||
|
||||
var animations_parameters_parsing_results: Array[AnimationParamsParsingResult]
|
||||
var total_animations_frames_count: int
|
||||
var first_animations_frame_index: int = -1
|
||||
var last_animations_frame_index: int = -1
|
||||
var global_temp_kra_path: String
|
||||
var temp_file_base_name: String = "img"
|
||||
var temp_kra_file_name: String = temp_file_base_name + ".kra"
|
||||
var temp_png_file_name_pattern: String = temp_file_base_name + ".png"
|
||||
|
||||
var storyboard_index_file_name: String = "%s/storyboard/index.xml" % image_name
|
||||
if storyboard_index_file_name in files_names_in_zip:
|
||||
var storyboard_index_xml_root: _XML.XMLNodeRoot = _XML.parse_buffer(zip_reader.read_file("%s/storyboard/index.xml" % image_name))
|
||||
var storyboard_info_xml_element: _XML.XMLNodeElement = storyboard_index_xml_root.get_elements("storyboard-info").front()
|
||||
var storyboard_item_list_xml_element: _XML.XMLNodeElement = storyboard_info_xml_element.get_elements("StoryboardItemList").front()
|
||||
var storyboard_item_xml_elements: Array[_XML.XMLNodeElement] = storyboard_item_list_xml_element.get_elements("storyboarditem")
|
||||
var unique_animations_names: PackedStringArray
|
||||
|
||||
for animation_index in storyboard_item_xml_elements.size():
|
||||
var story_xml_element: _XML.XMLNodeElement = storyboard_item_xml_elements[animation_index]
|
||||
var animation_first_frame: int = story_xml_element.get_int("frame")
|
||||
var animation_params_parsing_result: AnimationParamsParsingResult = _parse_animation_params(
|
||||
story_xml_element.get_string("item-name").strip_edges(),
|
||||
AnimationOptions.Direction | AnimationOptions.RepeatCount,
|
||||
animation_first_frame,
|
||||
story_xml_element.get_int("duration-frame") + \
|
||||
animation_framerate * story_xml_element.get_int("duration-second"))
|
||||
if animation_params_parsing_result.error:
|
||||
result.fail(ERR_CANT_RESOLVE, "Failed to parse animation parameters",
|
||||
animation_params_parsing_result)
|
||||
return result
|
||||
if unique_animations_names.has(animation_params_parsing_result.name):
|
||||
result.fail(ERR_INVALID_DATA, "Duplicated animation name \"%s\" at index: %s" %
|
||||
[animation_params_parsing_result.name, animation_index])
|
||||
return result
|
||||
unique_animations_names.push_back(animation_params_parsing_result.name)
|
||||
animations_parameters_parsing_results.push_back(animation_params_parsing_result)
|
||||
total_animations_frames_count += animation_params_parsing_result.frames_count
|
||||
if first_animations_frame_index < 0 or animation_params_parsing_result.first_frame_index < first_animations_frame_index:
|
||||
first_animations_frame_index = animation_params_parsing_result.first_frame_index
|
||||
var animation_last_frame_index: int = animation_params_parsing_result.first_frame_index + animation_params_parsing_result.frames_count - 1
|
||||
if last_animations_frame_index < 0 or animation_last_frame_index > last_animations_frame_index:
|
||||
last_animations_frame_index = animation_last_frame_index
|
||||
|
||||
animation_range_xml_element.attributes["from"] = str(first_animations_frame_index)
|
||||
animation_range_xml_element.attributes["to"] = str(last_animations_frame_index)
|
||||
|
||||
global_temp_kra_path = unique_temp_dir_path.path_join(temp_kra_file_name)
|
||||
|
||||
animation_index_animation_metadata_range_xml_element.attributes["from"] = str(first_animations_frame_index)
|
||||
animation_index_animation_metadata_range_xml_element.attributes["to"] = str(last_animations_frame_index)
|
||||
|
||||
var zip_writer = ZIPPacker.new()
|
||||
zip_writer.open(global_temp_kra_path, ZIPPacker.APPEND_CREATE)
|
||||
for filename in zip_reader.get_files():
|
||||
zip_writer.start_file(filename)
|
||||
match filename:
|
||||
maindoc_filename:
|
||||
zip_writer.write_file(maindoc_xml_root.dump_to_buffer())
|
||||
animation_index_filename:
|
||||
zip_writer.write_file(animation_index_xml_root.dump_to_buffer())
|
||||
_: zip_writer.write_file(zip_reader.read_file(filename))
|
||||
zip_writer.close_file()
|
||||
zip_writer.close()
|
||||
else:
|
||||
first_animations_frame_index = animation_range_xml_element.get_int("from")
|
||||
last_animations_frame_index = animation_range_xml_element.get_int("to")
|
||||
total_animations_frames_count = last_animations_frame_index - first_animations_frame_index + 1
|
||||
var default_animation_params_parsing_result: AnimationParamsParsingResult = AnimationParamsParsingResult.new()
|
||||
default_animation_params_parsing_result.name = options[_Options.DEFAULT_ANIMATION_NAME].strip_edges()
|
||||
if not default_animation_params_parsing_result.name:
|
||||
default_animation_params_parsing_result.name = "default"
|
||||
default_animation_params_parsing_result.first_frame_index = first_animations_frame_index
|
||||
default_animation_params_parsing_result.frames_count = last_animations_frame_index - first_animations_frame_index + 1
|
||||
default_animation_params_parsing_result.direction = options[_Options.DEFAULT_ANIMATION_DIRECTION]
|
||||
default_animation_params_parsing_result.repeat_count = options[_Options.DEFAULT_ANIMATION_REPEAT_COUNT]
|
||||
animations_parameters_parsing_results.push_back(default_animation_params_parsing_result)
|
||||
global_temp_kra_path = global_source_file_path
|
||||
|
||||
zip_reader.close()
|
||||
|
||||
var global_temp_png_path_pattern: String = unique_temp_dir_path.path_join(temp_png_file_name_pattern)
|
||||
|
||||
var command: String = os_command_result.value.strip_edges()
|
||||
var arguments: PackedStringArray = \
|
||||
os_command_arguments_result.value + \
|
||||
PackedStringArray([
|
||||
"--export-sequence",
|
||||
"--export-filename", global_temp_png_path_pattern,
|
||||
global_temp_kra_path])
|
||||
|
||||
var output: Array
|
||||
var exit_code: int = OS.execute(command, arguments, output, true, false)
|
||||
if exit_code:
|
||||
for arg_index in arguments.size():
|
||||
arguments[arg_index] = "\nArgument: " + arguments[arg_index]
|
||||
result.fail(ERR_QUERY_FAILED, " ".join([
|
||||
"An error occurred while executing the Krita command.",
|
||||
"Process exited with code %s:\nCommand: %s%s"
|
||||
]) % [exit_code, command, "".join(arguments)])
|
||||
return result
|
||||
|
||||
var unique_frames_count: int = last_animations_frame_index + 1 # - first_stories_frame
|
||||
var frames_images: Array[Image]
|
||||
for image_idx in unique_frames_count:
|
||||
var global_frame_png_path: String = unique_temp_dir_path \
|
||||
.path_join("%s%04d.png" % [temp_file_base_name, image_idx])
|
||||
if FileAccess.file_exists(global_frame_png_path):
|
||||
var image: Image = Image.load_from_file(global_frame_png_path)
|
||||
frames_images.push_back(image)
|
||||
else:
|
||||
frames_images.push_back(frames_images.back())
|
||||
|
||||
var sprite_sheet_builder: _SpriteSheetBuilderBase = _create_sprite_sheet_builder(options)
|
||||
|
||||
var sprite_sheet_building_result: _SpriteSheetBuilderBase.SpriteSheetBuildingResult = sprite_sheet_builder.build_sprite_sheet(frames_images)
|
||||
if sprite_sheet_building_result.error:
|
||||
result.fail(ERR_BUG, "Sprite sheet building failed", sprite_sheet_building_result)
|
||||
return result
|
||||
var sprite_sheet: _Common.SpriteSheetInfo = sprite_sheet_building_result.sprite_sheet
|
||||
|
||||
var animation_library: _Common.AnimationLibraryInfo = _Common.AnimationLibraryInfo.new()
|
||||
var autoplay_animation_name: String = options[_Options.AUTOPLAY_ANIMATION_NAME].strip_edges()
|
||||
|
||||
var frames_duration: float = 1.0 / animation_framerate
|
||||
var all_frames: Array[_Common.FrameInfo]
|
||||
all_frames.resize(unique_frames_count)
|
||||
for animation_index in animations_parameters_parsing_results.size():
|
||||
var animation_params_parsing_result: AnimationParamsParsingResult = animations_parameters_parsing_results[animation_index]
|
||||
var animation = _Common.AnimationInfo.new()
|
||||
animation.name = animation_params_parsing_result.name
|
||||
if animation.name == autoplay_animation_name:
|
||||
animation_library.autoplay_index = animation_index
|
||||
animation.direction = animation_params_parsing_result.direction
|
||||
if animation.direction < 0:
|
||||
animation.direction = _Common.AnimationDirection.FORWARD
|
||||
animation.repeat_count = animation_params_parsing_result.repeat_count
|
||||
if animation.repeat_count < 0:
|
||||
animation.repeat_count = 1
|
||||
for animation_frame_index in animation_params_parsing_result.frames_count:
|
||||
var global_frame_index: int = animation_params_parsing_result.first_frame_index + animation_frame_index
|
||||
var frame: _Common.FrameInfo = all_frames[global_frame_index]
|
||||
if frame == null:
|
||||
frame = _Common.FrameInfo.new()
|
||||
frame.sprite = sprite_sheet.sprites[global_frame_index]
|
||||
frame.duration = frames_duration
|
||||
all_frames[global_frame_index] = frame
|
||||
animation.frames.push_back(frame)
|
||||
animation_library.animations.push_back(animation)
|
||||
|
||||
if not autoplay_animation_name.is_empty() and animation_library.autoplay_index < 0:
|
||||
push_warning("Autoplay animation name not found: \"%s\". Continuing..." % [autoplay_animation_name])
|
||||
|
||||
if _DirAccessExtensions.remove_dir_recursive(unique_temp_dir_path).error:
|
||||
push_warning(
|
||||
"Failed to remove unique temporary directory: \"%s\"" %
|
||||
[unique_temp_dir_path])
|
||||
|
||||
result.success(sprite_sheet_building_result.atlas_image, sprite_sheet, animation_library)
|
||||
return result
|
||||
|
||||
class CustomImageFormatLoaderExtension:
|
||||
extends ImageFormatLoaderExtension
|
||||
|
||||
var __recognized_extensions: PackedStringArray
|
||||
|
||||
func _init(recognized_extensions: PackedStringArray) -> void:
|
||||
__recognized_extensions = recognized_extensions
|
||||
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return __recognized_extensions
|
||||
|
||||
func _load_image(image: Image, file_access: FileAccess, flags: int, scale: float) -> Error:
|
||||
var zip_reader := ZIPReader.new()
|
||||
zip_reader.open(file_access.get_path_absolute())
|
||||
image.load_png_from_buffer(zip_reader.read_file("mergedimage.png"))
|
||||
zip_reader.close()
|
||||
return OK
|
||||
1
addons/nklbdev.importality/export/krita.gd.uid
Normal file
1
addons/nklbdev.importality/export/krita.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bnpwq4kns36g6
|
||||
267
addons/nklbdev.importality/export/pencil2d.gd
Normal file
267
addons/nklbdev.importality/export/pencil2d.gd
Normal file
@@ -0,0 +1,267 @@
|
||||
@tool
|
||||
extends "_.gd"
|
||||
|
||||
const _XML = preload("../xml.gd")
|
||||
|
||||
var __os_command_setting: _Setting = _Setting.new(
|
||||
"pencil2d_command", "", TYPE_STRING, PROPERTY_HINT_NONE,
|
||||
"", true, func(v: String): return v.is_empty())
|
||||
|
||||
var __os_command_arguments_setting: _Setting = _Setting.new(
|
||||
"pencil2d_command_arguments", PackedStringArray(), TYPE_PACKED_STRING_ARRAY, PROPERTY_HINT_NONE,
|
||||
"", true, func(v: PackedStringArray): return false)
|
||||
|
||||
const __ANIMATIONS_PARAMETERS_OPTION: StringName = "pencil2d/animations_parameters"
|
||||
|
||||
func _init(editor_file_system: EditorFileSystem) -> void:
|
||||
var recognized_extensions: PackedStringArray = ["pclx"]
|
||||
super("Pencil2D", recognized_extensions, [
|
||||
_Options.create_option(__ANIMATIONS_PARAMETERS_OPTION, PackedStringArray(),
|
||||
PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)],
|
||||
[ __os_command_setting, __os_command_arguments_setting ],
|
||||
CustomImageFormatLoaderExtension.new(
|
||||
recognized_extensions,
|
||||
__os_command_setting,
|
||||
__os_command_arguments_setting,
|
||||
_Common.common_temporary_files_directory_path_setting))
|
||||
|
||||
func _export(res_source_file_path: String, options: Dictionary) -> ExportResult:
|
||||
var result: ExportResult = ExportResult.new()
|
||||
var err: Error
|
||||
|
||||
var os_command_result: _Setting.GettingValueResult = __os_command_setting.get_value()
|
||||
if os_command_result.error:
|
||||
result.fail(ERR_UNCONFIGURED, "Failed to get Pencil2D Command to export spritesheet", os_command_result)
|
||||
return result
|
||||
|
||||
var os_command_arguments_result: _Setting.GettingValueResult = __os_command_arguments_setting.get_value()
|
||||
if os_command_arguments_result.error:
|
||||
result.fail(ERR_UNCONFIGURED, "Failed to get Pencil2D Command Arguments to export spritesheet", os_command_arguments_result)
|
||||
return result
|
||||
|
||||
var temp_dir_path_result: _Setting.GettingValueResult = _Common.common_temporary_files_directory_path_setting.get_value()
|
||||
if temp_dir_path_result.error:
|
||||
result.fail(ERR_UNCONFIGURED, "Failed to get Temporary Files Directory Path to export spritesheet", temp_dir_path_result)
|
||||
return result
|
||||
var global_temp_dir_path: String = ProjectSettings.globalize_path(
|
||||
temp_dir_path_result.value.strip_edges())
|
||||
var unique_temp_dir_creation_result: _DirAccessExtensions.CreationResult = \
|
||||
_DirAccessExtensions.create_directory_with_unique_name(global_temp_dir_path)
|
||||
if unique_temp_dir_creation_result.error:
|
||||
result.fail(ERR_QUERY_FAILED, "Failed to create unique temporary directory to export spritesheet", unique_temp_dir_creation_result)
|
||||
return result
|
||||
var unique_temp_dir_path: String = unique_temp_dir_creation_result.path
|
||||
|
||||
var global_source_file_path: String = ProjectSettings.globalize_path(res_source_file_path)
|
||||
|
||||
var zip_reader: ZIPReader = ZIPReader.new()
|
||||
var zip_error: Error = zip_reader.open(global_source_file_path)
|
||||
if zip_error:
|
||||
result.fail(zip_error, "Failed to open Pencil2D file \"%s\" as ZIP archive with error: %s (%s)" % [res_source_file_path, zip_error, error_string(zip_error)])
|
||||
return result
|
||||
var buffer: PackedByteArray = zip_reader.read_file("main.xml")
|
||||
var main_xml_root: _XML.XMLNodeRoot = _XML.parse_buffer(buffer)
|
||||
zip_reader.close()
|
||||
var animation_framerate: int = main_xml_root \
|
||||
.get_elements("document").front() \
|
||||
.get_elements("projectdata").front() \
|
||||
.get_elements("fps").front() \
|
||||
.get_int("value")
|
||||
|
||||
var raw_animations_params_list: PackedStringArray = options[__ANIMATIONS_PARAMETERS_OPTION]
|
||||
var animations_params_parsing_results: Array[AnimationParamsParsingResult]
|
||||
animations_params_parsing_results.resize(raw_animations_params_list.size())
|
||||
var unique_animations_names: PackedStringArray
|
||||
var frame_indices_to_export
|
||||
var unique_frames_count: int = 0
|
||||
var animation_first_frame_index: int = 0
|
||||
for animation_index in raw_animations_params_list.size():
|
||||
var raw_animation_params: String = raw_animations_params_list[animation_index]
|
||||
var animation_params_parsing_result: AnimationParamsParsingResult = _parse_animation_params(
|
||||
raw_animation_params,
|
||||
AnimationOptions.FramesCount | AnimationOptions.Direction | AnimationOptions.RepeatCount,
|
||||
animation_first_frame_index)
|
||||
if animation_params_parsing_result.error:
|
||||
result.fail(ERR_CANT_RESOLVE, "Failed to parse animation parameters", animation_params_parsing_result)
|
||||
return result
|
||||
if unique_animations_names.has(animation_params_parsing_result.name):
|
||||
result.fail(ERR_INVALID_DATA, "Duplicated animation name \"%s\" at index: %s" %
|
||||
[animation_params_parsing_result.name, animation_index])
|
||||
return result
|
||||
unique_animations_names.push_back(animation_params_parsing_result.name)
|
||||
unique_frames_count += animation_params_parsing_result.frames_count
|
||||
animation_first_frame_index += animation_params_parsing_result.frames_count
|
||||
animations_params_parsing_results[animation_index] = animation_params_parsing_result
|
||||
|
||||
# -o --export <output_path> Render the file to <output_path>
|
||||
# --camera <layer_name> Name of the camera layer to use
|
||||
# --width <integer> Width of the output frames
|
||||
# --height <integer> Height of the output frames
|
||||
# --start <frame> The first frame you want to include in the exported movie
|
||||
# --end <frame> The last frame you want to include in the exported movie. Can also be last or last-sound to automatically use the last frame containing animation or sound respectively
|
||||
# --transparency Render transparency when possible
|
||||
# input Path to input pencil file
|
||||
var png_base_name: String = "temp"
|
||||
var global_temp_png_path: String = unique_temp_dir_path.path_join("%s.png" % png_base_name)
|
||||
|
||||
var command: String = os_command_result.value.strip_edges()
|
||||
var arguments: PackedStringArray = \
|
||||
os_command_arguments_result.value + \
|
||||
PackedStringArray([
|
||||
"--export", global_temp_png_path,
|
||||
"--start", 1,
|
||||
"--end", unique_frames_count,
|
||||
"--transparency",
|
||||
global_source_file_path])
|
||||
|
||||
var output: Array
|
||||
var exit_code: int = OS.execute(command, arguments, output, true, false)
|
||||
if exit_code:
|
||||
for arg_index in arguments.size():
|
||||
arguments[arg_index] = "\nArgument: " + arguments[arg_index]
|
||||
result.fail(ERR_QUERY_FAILED, " ".join([
|
||||
"An error occurred while executing the Pencil2D command.",
|
||||
"Process exited with code %s:\nCommand: %s%s"
|
||||
]) % [exit_code, command, "".join(arguments)])
|
||||
return result
|
||||
|
||||
var frames_images: Array[Image]
|
||||
for image_idx in unique_frames_count:
|
||||
var global_frame_png_path: String = unique_temp_dir_path \
|
||||
.path_join("%s%04d.png" % [png_base_name, image_idx + 1])
|
||||
frames_images.push_back(Image.load_from_file(global_frame_png_path))
|
||||
|
||||
var sprite_sheet_builder: _SpriteSheetBuilderBase = _create_sprite_sheet_builder(options)
|
||||
|
||||
var sprite_sheet_building_result: _SpriteSheetBuilderBase.SpriteSheetBuildingResult = sprite_sheet_builder.build_sprite_sheet(frames_images)
|
||||
if sprite_sheet_building_result.error:
|
||||
result.fail(ERR_BUG, "Sprite sheet building failed", sprite_sheet_building_result)
|
||||
return result
|
||||
var sprite_sheet: _Common.SpriteSheetInfo = sprite_sheet_building_result.sprite_sheet
|
||||
|
||||
var animation_library: _Common.AnimationLibraryInfo = _Common.AnimationLibraryInfo.new()
|
||||
var autoplay_animation_name: String = options[_Options.AUTOPLAY_ANIMATION_NAME].strip_edges()
|
||||
|
||||
var frames_duration: float = 1.0 / animation_framerate
|
||||
var all_frames: Array[_Common.FrameInfo]
|
||||
all_frames.resize(unique_frames_count)
|
||||
for animation_index in animations_params_parsing_results.size():
|
||||
var animation_params_parsing_result: AnimationParamsParsingResult = animations_params_parsing_results[animation_index]
|
||||
var animation = _Common.AnimationInfo.new()
|
||||
animation.name = animation_params_parsing_result.name
|
||||
if animation.name == autoplay_animation_name:
|
||||
animation_library.autoplay_index = animation_index
|
||||
animation.direction = animation_params_parsing_result.direction
|
||||
if animation.direction < 0:
|
||||
animation.direction = _Common.AnimationDirection.FORWARD
|
||||
animation.repeat_count = animation_params_parsing_result.repeat_count
|
||||
if animation.repeat_count < 0:
|
||||
animation.repeat_count = 1
|
||||
for animation_frame_index in animation_params_parsing_result.frames_count:
|
||||
var global_frame_index: int = animation_params_parsing_result.first_frame_index + animation_frame_index
|
||||
var frame: _Common.FrameInfo = all_frames[global_frame_index]
|
||||
if frame == null:
|
||||
frame = _Common.FrameInfo.new()
|
||||
frame.sprite = sprite_sheet.sprites[global_frame_index]
|
||||
frame.duration = frames_duration
|
||||
all_frames[global_frame_index] = frame
|
||||
animation.frames.push_back(frame)
|
||||
animation_library.animations.push_back(animation)
|
||||
if not autoplay_animation_name.is_empty() and animation_library.autoplay_index < 0:
|
||||
push_warning("Autoplay animation name not found: \"%s\". Continuing..." % [autoplay_animation_name])
|
||||
|
||||
if _DirAccessExtensions.remove_dir_recursive(unique_temp_dir_path).error:
|
||||
push_warning(
|
||||
"Failed to remove unique temporary directory: \"%s\"" %
|
||||
[unique_temp_dir_path])
|
||||
|
||||
result.success(sprite_sheet_building_result.atlas_image, sprite_sheet, animation_library)
|
||||
return result
|
||||
|
||||
class CustomImageFormatLoaderExtension:
|
||||
extends ImageFormatLoaderExtension
|
||||
|
||||
var __recognized_extensions: PackedStringArray
|
||||
var __os_command_setting: _Setting
|
||||
var __os_command_arguments_setting: _Setting
|
||||
var __common_temporary_files_directory_path_setting: _Setting
|
||||
|
||||
func _init(recognized_extensions: PackedStringArray,
|
||||
os_command_setting: _Setting,
|
||||
os_command_arguments_setting: _Setting,
|
||||
common_temporary_files_directory_path: _Setting,
|
||||
) -> void:
|
||||
__recognized_extensions = recognized_extensions
|
||||
__os_command_setting = os_command_setting
|
||||
__os_command_arguments_setting = os_command_arguments_setting
|
||||
__common_temporary_files_directory_path_setting = common_temporary_files_directory_path
|
||||
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return __recognized_extensions
|
||||
|
||||
func _load_image(image: Image, file_access: FileAccess, flags: int, scale: float) -> Error:
|
||||
var err: Error
|
||||
|
||||
var os_command_result: _Setting.GettingValueResult = __os_command_setting.get_value()
|
||||
if os_command_result.error:
|
||||
push_error(os_command_result.error_description)
|
||||
return os_command_result.error
|
||||
|
||||
var os_command_arguments_result: _Setting.GettingValueResult = __os_command_arguments_setting.get_value()
|
||||
if os_command_arguments_result.error:
|
||||
push_error(os_command_arguments_result.error_description)
|
||||
return os_command_arguments_result.error
|
||||
|
||||
var temp_dir_path_result: _Setting.GettingValueResult = _Common.common_temporary_files_directory_path_setting.get_value()
|
||||
if temp_dir_path_result.error:
|
||||
push_error("Failed to get Temporary Files Directory Path to export spritesheet")
|
||||
return temp_dir_path_result.error
|
||||
var global_temp_dir_path: String = ProjectSettings.globalize_path(
|
||||
temp_dir_path_result.value.strip_edges())
|
||||
var unique_temp_dir_creation_result: _DirAccessExtensions.CreationResult = \
|
||||
_DirAccessExtensions.create_directory_with_unique_name(global_temp_dir_path)
|
||||
if unique_temp_dir_creation_result.error:
|
||||
push_error("Failed to create unique temporary directory to export spritesheet")
|
||||
return unique_temp_dir_creation_result.error
|
||||
var unique_temp_dir_path: String = unique_temp_dir_creation_result.path
|
||||
|
||||
var global_source_file_path: String = ProjectSettings.globalize_path(file_access.get_path())
|
||||
|
||||
const png_base_name: String = "img"
|
||||
var global_temp_png_path: String = unique_temp_dir_path.path_join("%s.png" % png_base_name)
|
||||
|
||||
var command: String = os_command_result.value.strip_edges()
|
||||
var arguments: PackedStringArray = \
|
||||
os_command_arguments_result.value + \
|
||||
PackedStringArray([
|
||||
"--export", global_temp_png_path,
|
||||
"--start", 1,
|
||||
"--end", 1,
|
||||
"--transparency",
|
||||
global_source_file_path])
|
||||
|
||||
var output: Array
|
||||
var exit_code: int = OS.execute(command, arguments, output, true, false)
|
||||
if exit_code:
|
||||
for arg_index in arguments.size():
|
||||
arguments[arg_index] = "\nArgument: " + arguments[arg_index]
|
||||
push_error(" ".join([
|
||||
"An error occurred while executing the Pencil2D command.",
|
||||
"Process exited with code %s:\nCommand: %s%s"
|
||||
]) % [exit_code, command, "".join(arguments)])
|
||||
return ERR_QUERY_FAILED
|
||||
|
||||
var global_frame_png_path: String = unique_temp_dir_path \
|
||||
.path_join("%s0001.png" % [png_base_name])
|
||||
err = image.load_png_from_buffer(FileAccess.get_file_as_bytes(global_frame_png_path))
|
||||
if err:
|
||||
push_error("An error occurred while image loading")
|
||||
return err
|
||||
|
||||
if _DirAccessExtensions.remove_dir_recursive(unique_temp_dir_path).error:
|
||||
push_warning(
|
||||
"Failed to remove unique temporary directory: \"%s\"" %
|
||||
[unique_temp_dir_path])
|
||||
|
||||
return OK
|
||||
|
||||
1
addons/nklbdev.importality/export/pencil2d.gd.uid
Normal file
1
addons/nklbdev.importality/export/pencil2d.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cgsbagkpxk5y7
|
||||
149
addons/nklbdev.importality/export/piskel.gd
Normal file
149
addons/nklbdev.importality/export/piskel.gd
Normal file
@@ -0,0 +1,149 @@
|
||||
@tool
|
||||
extends "_.gd"
|
||||
|
||||
const __ANIMATIONS_PARAMETERS_OPTION: StringName = "piskel/animations_parameters"
|
||||
|
||||
func _init(editor_file_system: EditorFileSystem) -> void:
|
||||
var recognized_extensions: PackedStringArray = ["piskel"]
|
||||
super("Piskel", recognized_extensions, [
|
||||
_Options.create_option(__ANIMATIONS_PARAMETERS_OPTION, PackedStringArray(),
|
||||
PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT)],
|
||||
[],
|
||||
CustomImageFormatLoaderExtension.new(recognized_extensions))
|
||||
|
||||
func _export(res_source_file_path: String, options: Dictionary) -> ExportResult:
|
||||
var result: ExportResult = ExportResult.new()
|
||||
|
||||
var raw_animations_params_list: PackedStringArray = options[__ANIMATIONS_PARAMETERS_OPTION]
|
||||
var animations_params_parsing_results: Array[AnimationParamsParsingResult]
|
||||
animations_params_parsing_results.resize(raw_animations_params_list.size())
|
||||
var unique_animations_names: PackedStringArray
|
||||
var frame_indices_to_export
|
||||
var unique_frames_count: int = 0
|
||||
var animation_first_frame_index: int = 0
|
||||
for animation_index in raw_animations_params_list.size():
|
||||
var raw_animation_params: String = raw_animations_params_list[animation_index]
|
||||
var animation_params_parsing_result: AnimationParamsParsingResult = _parse_animation_params(
|
||||
raw_animation_params,
|
||||
AnimationOptions.FramesCount | AnimationOptions.Direction | AnimationOptions.RepeatCount,
|
||||
animation_first_frame_index)
|
||||
if animation_params_parsing_result.error:
|
||||
result.fail(ERR_CANT_RESOLVE, "Failed to parse animation parameters", animation_params_parsing_result)
|
||||
return result
|
||||
if unique_animations_names.has(animation_params_parsing_result.name):
|
||||
result.fail(ERR_INVALID_DATA, "Duplicated animation name \"%s\" at index: %s" %
|
||||
[animation_params_parsing_result.name, animation_index])
|
||||
return result
|
||||
unique_animations_names.push_back(animation_params_parsing_result.name)
|
||||
unique_frames_count += animation_params_parsing_result.frames_count
|
||||
animation_first_frame_index += animation_params_parsing_result.frames_count
|
||||
animations_params_parsing_results[animation_index] = animation_params_parsing_result
|
||||
|
||||
var document: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(res_source_file_path))
|
||||
document.modelVersion #int 2
|
||||
var piskel: Dictionary = document.piskel
|
||||
piskel.name #string New Piskel
|
||||
piskel.description #string asdfasdfasdf
|
||||
piskel.fps #int 12,
|
||||
var image_size: Vector2i = Vector2i(piskel.width, piskel.height)
|
||||
# piskel.hiddenFrames#Array may absend
|
||||
var blended_layers: Image
|
||||
var layer_image: Image = Image.new()
|
||||
var frames_count: int
|
||||
var layer_image_size: Vector2i = image_size
|
||||
for layer_string in piskel.layers: #Array
|
||||
var layer: Dictionary = JSON.parse_string(layer_string)
|
||||
layer.name #string layer 1
|
||||
layer.opacity #float 1
|
||||
if frames_count == 0:
|
||||
frames_count = layer.frameCount
|
||||
layer_image_size.x = image_size.x * frames_count
|
||||
else:
|
||||
assert(frames_count == layer.frameCount)
|
||||
for chunk in layer.chunks:
|
||||
# chunk.layout # array [ [ 0 ], [ 1 ], [ 2 ] ]
|
||||
layer_image.load_png_from_buffer(Marshalls.base64_to_raw(chunk.base64PNG.trim_prefix("data:image/png;base64,")))
|
||||
assert(layer_image.get_size() == layer_image_size)
|
||||
if blended_layers == null:
|
||||
blended_layers = layer_image
|
||||
layer_image = Image.new()
|
||||
else:
|
||||
blended_layers.blend_rect(layer_image, Rect2i(Vector2i.ZERO, layer_image.get_size()), Vector2i.ZERO)
|
||||
|
||||
var frames_images: Array[Image]
|
||||
frames_images.resize(frames_count)
|
||||
for frame_index in frames_count:
|
||||
frames_images[frame_index] = blended_layers.get_region(
|
||||
Rect2i(Vector2i(frame_index * image_size.x, 0), image_size))
|
||||
|
||||
var sprite_sheet_builder: _SpriteSheetBuilderBase = _create_sprite_sheet_builder(options)
|
||||
|
||||
var sprite_sheet_building_result: _SpriteSheetBuilderBase.SpriteSheetBuildingResult = \
|
||||
sprite_sheet_builder.build_sprite_sheet(frames_images)
|
||||
if sprite_sheet_building_result.error:
|
||||
result.fail(ERR_BUG, "Sprite sheet building failed", sprite_sheet_building_result)
|
||||
return result
|
||||
var sprite_sheet: _Common.SpriteSheetInfo = sprite_sheet_building_result.sprite_sheet
|
||||
|
||||
var animation_library: _Common.AnimationLibraryInfo = _Common.AnimationLibraryInfo.new()
|
||||
var autoplay_animation_name: String = options[_Options.AUTOPLAY_ANIMATION_NAME].strip_edges()
|
||||
|
||||
var frames_duration: float = 1.0 / piskel.fps
|
||||
var all_frames: Array[_Common.FrameInfo]
|
||||
all_frames.resize(unique_frames_count)
|
||||
for animation_index in animations_params_parsing_results.size():
|
||||
var animation_params_parsing_result: AnimationParamsParsingResult = animations_params_parsing_results[animation_index]
|
||||
var animation = _Common.AnimationInfo.new()
|
||||
animation.name = animation_params_parsing_result.name
|
||||
if animation.name == autoplay_animation_name:
|
||||
animation_library.autoplay_index = animation_index
|
||||
animation.direction = animation_params_parsing_result.direction
|
||||
if animation.direction < 0:
|
||||
animation.direction = _Common.AnimationDirection.FORWARD
|
||||
animation.repeat_count = animation_params_parsing_result.repeat_count
|
||||
if animation.repeat_count < 0:
|
||||
animation.repeat_count = 1
|
||||
for animation_frame_index in animation_params_parsing_result.frames_count:
|
||||
var global_frame_index: int = animation_params_parsing_result.first_frame_index + animation_frame_index
|
||||
var frame: _Common.FrameInfo = all_frames[global_frame_index]
|
||||
if frame == null:
|
||||
frame = _Common.FrameInfo.new()
|
||||
frame.sprite = sprite_sheet.sprites[global_frame_index]
|
||||
frame.duration = frames_duration
|
||||
all_frames[global_frame_index] = frame
|
||||
animation.frames.push_back(frame)
|
||||
animation_library.animations.push_back(animation)
|
||||
if not autoplay_animation_name.is_empty() and animation_library.autoplay_index < 0:
|
||||
push_warning("Autoplay animation name not found: \"%s\". Continuing..." % [autoplay_animation_name])
|
||||
|
||||
result.success(sprite_sheet_building_result.atlas_image, sprite_sheet, animation_library)
|
||||
return result
|
||||
|
||||
class CustomImageFormatLoaderExtension:
|
||||
extends ImageFormatLoaderExtension
|
||||
|
||||
var __recognized_extensions: PackedStringArray
|
||||
|
||||
func _init(recognized_extensions: PackedStringArray) -> void:
|
||||
__recognized_extensions = recognized_extensions
|
||||
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return __recognized_extensions
|
||||
|
||||
func _load_image(image: Image, file_access: FileAccess, flags: int, scale: float) -> Error:
|
||||
|
||||
var document: Dictionary = JSON.parse_string(file_access.get_as_text())
|
||||
var piskel: Dictionary = document.piskel
|
||||
var image_size: Vector2i = Vector2i(piskel.width, piskel.height)
|
||||
var image_rect: Rect2i = Rect2i(Vector2i.ZERO, image_size)
|
||||
image.set_data(1, 1, false, Image.FORMAT_RGBA8, [0, 0, 0, 0])
|
||||
image.resize(image_size.x, image_size.y)
|
||||
var layer_image: Image = Image.new()
|
||||
for layer_string in piskel.layers: #Array
|
||||
var layer: Dictionary = JSON.parse_string(layer_string)
|
||||
layer.opacity #float 1
|
||||
for chunk in layer.chunks:
|
||||
# chunk.layout # array [ [ 0 ], [ 1 ], [ 2 ] ]
|
||||
layer_image.load_png_from_buffer(Marshalls.base64_to_raw(chunk.base64PNG.trim_prefix("data:image/png;base64,")))
|
||||
image.blend_rect(layer_image, image_rect, Vector2i.ZERO)
|
||||
return OK
|
||||
1
addons/nklbdev.importality/export/piskel.gd.uid
Normal file
1
addons/nklbdev.importality/export/piskel.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://q3joovito34w
|
||||
225
addons/nklbdev.importality/export/pixelorama.gd
Normal file
225
addons/nklbdev.importality/export/pixelorama.gd
Normal file
@@ -0,0 +1,225 @@
|
||||
@tool
|
||||
extends "_.gd"
|
||||
|
||||
enum PxoLayerType {
|
||||
PIXEL_LAYER = 0,
|
||||
GROUP_LAYER = 1,
|
||||
LAYER_3D = 2,
|
||||
}
|
||||
|
||||
func _init(editor_file_system: EditorFileSystem) -> void:
|
||||
var recognized_extensions: PackedStringArray = ["pxo"]
|
||||
super("Pixelorama", recognized_extensions, [
|
||||
], [
|
||||
# settings
|
||||
], CustomImageFormatLoaderExtension.new(recognized_extensions))
|
||||
|
||||
func _export(res_source_file_path: String, options: Dictionary) -> ExportResult:
|
||||
var result: ExportResult = ExportResult.new()
|
||||
|
||||
var file: FileAccess = FileAccess.open_compressed(res_source_file_path, FileAccess.READ, FileAccess.COMPRESSION_ZSTD)
|
||||
if file == null or file.get_open_error() == ERR_FILE_UNRECOGNIZED:
|
||||
file = FileAccess.open(res_source_file_path, FileAccess.READ)
|
||||
if file == null:
|
||||
result.fail(ERR_FILE_CANT_OPEN, "Failed to open file with unknown error")
|
||||
return result
|
||||
var open_error: Error = file.get_open_error()
|
||||
if open_error:
|
||||
result.fail(ERR_FILE_CANT_OPEN, "Failed to open file with error: %s \"%s\"" % [open_error, error_string(open_error)])
|
||||
return result
|
||||
|
||||
var first_line: String = file.get_line()
|
||||
var images_data: PackedByteArray = file.get_buffer(file.get_length() - file.get_position())
|
||||
file.close()
|
||||
|
||||
var pxo_project: Dictionary = JSON.parse_string(first_line)
|
||||
var image_size: Vector2i = Vector2i(pxo_project.size_x, pxo_project.size_y)
|
||||
var pxo_cel_image_buffer_size: int = image_size.x * image_size.y * 4
|
||||
var pxo_cel_image_buffer_offset: int
|
||||
var pxo_cel_image: Image = Image.create(image_size.x, image_size.y, false, Image.FORMAT_RGBA8)
|
||||
var pixel_layers_count: int
|
||||
for pxo_layer in pxo_project.layers:
|
||||
if pxo_layer.type == PxoLayerType.PIXEL_LAYER:
|
||||
pixel_layers_count += 1
|
||||
|
||||
var autoplay_animation_name: String = options[_Options.AUTOPLAY_ANIMATION_NAME].strip_edges()
|
||||
var unique_frames_indices_by_frame_index: Dictionary
|
||||
var unique_frames: Array[_Common.FrameInfo]
|
||||
var unique_frames_images: Array[Image]
|
||||
var unique_frames_count: int
|
||||
var pixel_layer_index: int
|
||||
var image_rect: Rect2i = Rect2i(Vector2i.ZERO, image_size)
|
||||
var frame: _Common.FrameInfo
|
||||
|
||||
|
||||
var is_animation_default: bool = pxo_project.tags.is_empty()
|
||||
if is_animation_default:
|
||||
var default_animation_name: String = options[_Options.DEFAULT_ANIMATION_NAME].strip_edges()
|
||||
if default_animation_name.is_empty():
|
||||
default_animation_name = "default"
|
||||
pxo_project.tags.push_back({
|
||||
name = default_animation_name,
|
||||
from = 1,
|
||||
to = pxo_project.frames.size()})
|
||||
var animations_count: int = pxo_project.tags.size()
|
||||
|
||||
var animation_library: _Common.AnimationLibraryInfo = _Common.AnimationLibraryInfo.new()
|
||||
animation_library.animations.resize(animations_count)
|
||||
var pxo_cel_opacity: float
|
||||
var unique_animations_names: PackedStringArray
|
||||
for animation_index in animations_count:
|
||||
var pxo_tag: Dictionary = pxo_project.tags[animation_index]
|
||||
var animation: _Common.AnimationInfo = _Common.AnimationInfo.new()
|
||||
animation_library.animations[animation_index] = animation
|
||||
var animation_frames_count: int = pxo_tag.to + 1 - pxo_tag.from
|
||||
if is_animation_default:
|
||||
animation.name = pxo_tag.name
|
||||
if animation.name == autoplay_animation_name:
|
||||
animation_library.autoplay_index = animation_index
|
||||
animation.direction = options[_Options.DEFAULT_ANIMATION_DIRECTION]
|
||||
animation.repeat_count = options[_Options.DEFAULT_ANIMATION_REPEAT_COUNT]
|
||||
else:
|
||||
var animation_params_parsing_result: AnimationParamsParsingResult = _parse_animation_params(
|
||||
pxo_tag.name, AnimationOptions.Direction | AnimationOptions.RepeatCount,
|
||||
pxo_tag.from, animation_frames_count)
|
||||
if animation_params_parsing_result.error:
|
||||
result.fail(ERR_CANT_RESOLVE, "Failed to parse animation parameters",
|
||||
animation_params_parsing_result)
|
||||
return result
|
||||
if unique_animations_names.has(animation_params_parsing_result.name):
|
||||
result.fail(ERR_INVALID_DATA, "Duplicated animation name \"%s\" at index: %s" %
|
||||
[animation_params_parsing_result.name, animation_index])
|
||||
return result
|
||||
unique_animations_names.push_back(animation_params_parsing_result.name)
|
||||
animation.name = animation_params_parsing_result.name
|
||||
if animation.name == autoplay_animation_name:
|
||||
animation_library.autoplay_index = animation_index
|
||||
animation.direction = animation_params_parsing_result.direction
|
||||
if animation.direction < 0:
|
||||
animation.direction = _Common.AnimationDirection.FORWARD
|
||||
animation.repeat_count = animation_params_parsing_result.repeat_count
|
||||
if animation.repeat_count < 0:
|
||||
animation.repeat_count = 1
|
||||
|
||||
animation.frames.resize(animation_frames_count)
|
||||
|
||||
var frame_image: Image
|
||||
for animation_frame_index in animation_frames_count:
|
||||
var frame_index: int = pxo_tag.from - 1 + animation_frame_index
|
||||
var unique_frame_index: int = unique_frames_indices_by_frame_index.get(frame_index, -1)
|
||||
if unique_frame_index >= 0:
|
||||
frame = unique_frames[unique_frame_index]
|
||||
else:
|
||||
frame = _Common.FrameInfo.new()
|
||||
unique_frames.push_back(frame)
|
||||
frame_image = Image.create(image_size.x, image_size.y, false, Image.FORMAT_RGBA8)
|
||||
unique_frames_images.push_back(frame_image)
|
||||
unique_frame_index = unique_frames_count
|
||||
unique_frames_count += 1
|
||||
pixel_layer_index = -1
|
||||
var pxo_frame: Dictionary = pxo_project.frames[frame_index]
|
||||
frame.duration = pxo_frame.duration / pxo_project.fps
|
||||
for cel_index in pxo_frame.cels.size():
|
||||
var pxo_cel = pxo_frame.cels[cel_index]
|
||||
var pxo_layer = pxo_project.layers[cel_index]
|
||||
if pxo_layer.type == PxoLayerType.PIXEL_LAYER:
|
||||
pixel_layer_index += 1
|
||||
var l: Dictionary = pxo_layer
|
||||
while l.parent >= 0 and pxo_layer.visible:
|
||||
if not l.visible:
|
||||
pxo_layer.visible = false
|
||||
break
|
||||
l = pxo_project.layers[l.parent]
|
||||
pxo_cel_opacity = pxo_cel.opacity
|
||||
if not pxo_layer.visible or pxo_cel_opacity == 0:
|
||||
continue
|
||||
pxo_cel_image_buffer_offset = pxo_cel_image_buffer_size * \
|
||||
(pixel_layers_count * frame_index + pixel_layer_index)
|
||||
var pxo_cel_image_buffer: PackedByteArray = images_data.slice(
|
||||
pxo_cel_image_buffer_offset,
|
||||
pxo_cel_image_buffer_offset + pxo_cel_image_buffer_size)
|
||||
for alpha_index in range(3, pxo_cel_image_buffer_size, 4):
|
||||
pxo_cel_image_buffer[alpha_index] = roundi(pxo_cel_image_buffer[alpha_index] * pxo_cel_opacity)
|
||||
pxo_cel_image.set_data(image_size.x, image_size.y, false, Image.FORMAT_RGBA8, pxo_cel_image_buffer)
|
||||
frame_image.blend_rect(pxo_cel_image, image_rect, Vector2i.ZERO)
|
||||
unique_frames_indices_by_frame_index[frame_index] = unique_frame_index
|
||||
animation.frames[animation_frame_index] = frame
|
||||
if not autoplay_animation_name.is_empty() and animation_library.autoplay_index < 0:
|
||||
push_warning("Autoplay animation name not found: \"%s\". Continuing..." % [autoplay_animation_name])
|
||||
|
||||
var sprite_sheet_builder: _SpriteSheetBuilderBase = _create_sprite_sheet_builder(options)
|
||||
|
||||
var sprite_sheet_building_result: _SpriteSheetBuilderBase.SpriteSheetBuildingResult = \
|
||||
sprite_sheet_builder.build_sprite_sheet(unique_frames_images)
|
||||
if sprite_sheet_building_result.error:
|
||||
result.fail(ERR_BUG, "Sprite sheet building failed", sprite_sheet_building_result)
|
||||
return result
|
||||
var sprite_sheet: _Common.SpriteSheetInfo = sprite_sheet_building_result.sprite_sheet
|
||||
|
||||
for unique_frame_index in unique_frames_count:
|
||||
var unique_frame: _Common.FrameInfo = unique_frames[unique_frame_index]
|
||||
unique_frame.sprite = sprite_sheet.sprites[unique_frame_index]
|
||||
|
||||
result.success(sprite_sheet_building_result.atlas_image, sprite_sheet, animation_library)
|
||||
return result
|
||||
|
||||
class CustomImageFormatLoaderExtension:
|
||||
extends ImageFormatLoaderExtension
|
||||
|
||||
var __recognized_extensions: PackedStringArray
|
||||
|
||||
func _init(recognized_extensions: PackedStringArray) -> void:
|
||||
__recognized_extensions = recognized_extensions
|
||||
|
||||
func _get_recognized_extensions() -> PackedStringArray:
|
||||
return __recognized_extensions
|
||||
|
||||
func _load_image(image: Image, file_access: FileAccess, flags: int, scale: float) -> Error:
|
||||
var file: FileAccess = FileAccess.open_compressed(file_access.get_path(), FileAccess.READ, FileAccess.COMPRESSION_ZSTD)
|
||||
if file == null or file.get_open_error() == ERR_FILE_UNRECOGNIZED:
|
||||
file = FileAccess.open(file_access.get_path(), FileAccess.READ)
|
||||
if file == null:
|
||||
push_error("Failed to open file with unknown error")
|
||||
return ERR_FILE_CANT_OPEN
|
||||
var open_error: Error = file.get_open_error()
|
||||
if open_error:
|
||||
push_error("Failed to open file with error: %s \"%s\"" % [open_error, error_string(open_error)])
|
||||
return ERR_FILE_CANT_OPEN
|
||||
|
||||
var first_line: String = file.get_line()
|
||||
|
||||
var pxo_project: Dictionary = JSON.parse_string(first_line)
|
||||
var image_size: Vector2i = Vector2i(pxo_project.size_x, pxo_project.size_y)
|
||||
var pxo_cel_image_buffer_size: int = image_size.x * image_size.y * 4
|
||||
var pxo_cel_image_buffer_offset: int
|
||||
var pxo_cel_image: Image = Image.create(image_size.x, image_size.y, false, Image.FORMAT_RGBA8)
|
||||
var pixel_layer_index: int = -1
|
||||
image.set_data(1, 1, false, Image.FORMAT_RGBA8, [0, 0, 0, 0])
|
||||
image.resize(image_size.x, image_size.y)
|
||||
var image_rect: Rect2i = Rect2i(Vector2i.ZERO, image_size)
|
||||
for layer_index in pxo_project.layers.size():
|
||||
var pxo_layer: Dictionary = pxo_project.layers[layer_index]
|
||||
if pxo_layer.type != PxoLayerType.PIXEL_LAYER:
|
||||
continue
|
||||
pixel_layer_index += 1
|
||||
var l: Dictionary = pxo_layer
|
||||
while l.parent >= 0 and pxo_layer.visible:
|
||||
if not l.visible:
|
||||
pxo_layer.visible = false
|
||||
break
|
||||
l = pxo_project.layers[l.parent]
|
||||
if not pxo_layer.visible:
|
||||
continue
|
||||
var pxo_cel: Dictionary = pxo_project.frames[0].cels[layer_index]
|
||||
var pxo_cel_opacity = pxo_cel.opacity
|
||||
if pxo_cel_opacity == 0:
|
||||
continue
|
||||
pxo_cel_image_buffer_offset = pxo_cel_image_buffer_size * layer_index
|
||||
var pxo_cel_image_buffer: PackedByteArray = file.get_buffer(pxo_cel_image_buffer_size)
|
||||
for alpha_index in range(3, pxo_cel_image_buffer_size, 4):
|
||||
pxo_cel_image_buffer[alpha_index] = roundi(pxo_cel_image_buffer[alpha_index] * pxo_cel_opacity)
|
||||
pxo_cel_image.set_data(image_size.x, image_size.y, false, Image.FORMAT_RGBA8, pxo_cel_image_buffer)
|
||||
image.blend_rect(pxo_cel_image, image_rect, Vector2i.ZERO)
|
||||
file.close()
|
||||
return OK
|
||||
|
||||
1
addons/nklbdev.importality/export/pixelorama.gd.uid
Normal file
1
addons/nklbdev.importality/export/pixelorama.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ccbaalk42lyab
|
||||
Reference in New Issue
Block a user