From d2f2d821ee60f2d2ab3c9d2d9112eecc11db631b Mon Sep 17 00:00:00 2001 From: Joachim Arting Date: Sun, 3 May 2026 21:09:17 +0200 Subject: [PATCH 1/4] Fix command line export argument errors --- parse_args.gd | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/parse_args.gd b/parse_args.gd index 6ba1acfa2..3d8509d6e 100644 --- a/parse_args.gd +++ b/parse_args.gd @@ -1,7 +1,9 @@ extends Node -func show_error(message : String): +func show_error(message : String, exit_on_error : bool = false): print(message) + if exit_on_error: + get_tree().quit(1) static func name_to_lower(s : String) -> String: s = s.strip_edges() @@ -105,25 +107,38 @@ func _ready(): match OS.get_cmdline_args()[i]: "-t", "--target": i += 1 + if i >= OS.get_cmdline_args().size(): + show_error("ERROR: missing target for " + OS.get_cmdline_args()[i - 1], true) + return target = OS.get_cmdline_args()[i] "-o", "--output-dir": i += 1 + if i >= OS.get_cmdline_args().size(): + show_error("ERROR: missing output dir for " + OS.get_cmdline_args()[i - 1], true) + return output_dir = OS.get_cmdline_args()[i] "--output-file": i += 1 + if i >= OS.get_cmdline_args().size(): + show_error("ERROR: missing output file format for --output-file", true) + return output_file = OS.get_cmdline_args()[i] "--size": i += 1 + if i >= OS.get_cmdline_args().size(): + show_error("ERROR: missing size for --size", true) + return texture_size = int(OS.get_cmdline_args()[i]) - if texture_size < 0: - #show_error("ERROR: incorrect size "+OS.get_cmdline_args()[i]) + if texture_size <= 0: + show_error("ERROR: incorrect size "+OS.get_cmdline_args()[i], true) return + image_size = texture_size _: files.push_back(OS.get_cmdline_args()[i]) i += 1 print("Output dir: ", output_dir) if ! dir.dir_exists(output_dir): - show_error("ERROR: Output directory '%s' does not exist" % output_dir) + show_error("ERROR: Output directory '%s' does not exist" % output_dir, true) return var expanded_files = [] for f : String in files: From 366d2d1b015a6ec71dfc08377699485c59c52518 Mon Sep 17 00:00:00 2001 From: Joachim Arting Date: Sun, 3 May 2026 21:10:15 +0200 Subject: [PATCH 2/4] Add command line export profile listing --- parse_args.gd | 145 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 49 deletions(-) diff --git a/parse_args.gd b/parse_args.gd index 3d8509d6e..9af96f6b1 100644 --- a/parse_args.gd +++ b/parse_args.gd @@ -12,6 +12,66 @@ static func name_to_lower(s : String) -> String: s = s.remove_chars("()/\"") return s +func _expand_input_files(files : Array[String]) -> Array[String]: + var expanded_files : Array[String] = [] + for f : String in files: + var basedir : String = f.get_base_dir() + if basedir == "": + basedir = "." + var basename : String = f.get_file() + if basename.find("*") != -1: + basename = basename.replace("*", ".*") + var dir : DirAccess = DirAccess.open(basedir) + if dir.is_open(): + var regex : RegEx = RegEx.new() + regex.compile("^"+basename+"$") + dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 + var file_name = dir.get_next() + while file_name != "": + if regex.search(file_name) and file_name.get_extension() == "ptex": + expanded_files.push_back(basedir+"/"+file_name) + file_name = dir.get_next() + elif f.begins_with("website:"): + for m : String in f.right(-8).split(","): + var range : PackedStringArray = m.split("-") + match range.size(): + 1: + if m.is_valid_int(): + expanded_files.push_back("website:"+m) + 2: + if range[0].is_valid_int() and range[1].is_valid_int(): + for mi in range(range[0].to_int(), range[1].to_int()+1): + expanded_files.push_back("website:"+str(mi)) + else: + expanded_files.push_back(f) + return expanded_files + +func _list_export_profiles(files : Array[String]) -> bool: + var results : Array = [] + var has_loaded_input : bool = false + var has_failure : bool = false + for f : String in files: + var gen = await mm_loader.load_gen(f) + if gen == null: + results.append({ "input": f, "material": null, "profiles": [] }) + has_failure = true + continue + has_loaded_input = true + add_child(gen) + var has_material : bool = false + for c in gen.get_children(): + if c.has_method("get_export_profiles") and c.has_method("export_material"): + has_material = true + var profiles : Array = c.get_export_profiles() + profiles.sort() + results.append({ "input": f, "material": c.name, "profiles": profiles }) + if !has_material: + results.append({ "input": f, "material": null, "profiles": [] }) + has_failure = true + gen.queue_free() + print(JSON.stringify(results)) + return has_loaded_input and !has_failure + func export_files(files, output_dir, target, target_file, image_size) -> void: var website_materials : Array = [] var export_list : PackedStringArray = PackedStringArray() @@ -90,87 +150,74 @@ func export_files(files, output_dir, target, target_file, image_size) -> void: func _ready(): RenderingServer.set_default_clear_color(Color.BLACK) var args : PackedStringArray = OS.get_cmdline_args() - if ("--export" in args) or ("--export-material" in args): - print("Exporting...") + if ("--export" in args) or ("--export-material" in args) or ("--list-export-profiles" in args): var image_size : int = 2048 var dir : DirAccess = DirAccess.open(".") var output = [] - print("Current dir: ", dir.get_current_dir()) var target : String = "Godot/Godot 4 Standard" #TODO: fix this var output_dir : String = dir.get_current_dir() var output_file : String = "%f" var texture_size : int = 0 var files : Array[String] = [] - var i = 1 - while i < OS.get_cmdline_args().size(): - match OS.get_cmdline_args()[i]: + var list_export_profiles : bool = false + var i = 0 + while i < args.size(): + var arg : String = args[i] + match arg: + "--export", "--export-material": + pass "-t", "--target": i += 1 - if i >= OS.get_cmdline_args().size(): - show_error("ERROR: missing target for " + OS.get_cmdline_args()[i - 1], true) + if i >= args.size(): + show_error("ERROR: missing target for " + arg, true) return - target = OS.get_cmdline_args()[i] + target = args[i] "-o", "--output-dir": i += 1 - if i >= OS.get_cmdline_args().size(): - show_error("ERROR: missing output dir for " + OS.get_cmdline_args()[i - 1], true) + if i >= args.size(): + show_error("ERROR: missing output dir for " + arg, true) return - output_dir = OS.get_cmdline_args()[i] + output_dir = args[i] "--output-file": i += 1 - if i >= OS.get_cmdline_args().size(): + if i >= args.size(): show_error("ERROR: missing output file format for --output-file", true) return - output_file = OS.get_cmdline_args()[i] + output_file = args[i] "--size": i += 1 - if i >= OS.get_cmdline_args().size(): + if i >= args.size(): show_error("ERROR: missing size for --size", true) return - texture_size = int(OS.get_cmdline_args()[i]) + texture_size = int(args[i]) if texture_size <= 0: - show_error("ERROR: incorrect size "+OS.get_cmdline_args()[i], true) + show_error("ERROR: incorrect size "+args[i], true) return image_size = texture_size + "--list-export-profiles": + list_export_profiles = true _: - files.push_back(OS.get_cmdline_args()[i]) + files.push_back(arg) i += 1 + + var expanded_files : Array[String] = _expand_input_files(files) + if expanded_files.is_empty(): + show_error("ERROR: No input files provided", true) + return + + if list_export_profiles: + var list_success = await _list_export_profiles(expanded_files) + get_tree().quit(0 if list_success else 1) + return + + print("Exporting...") + print("Current dir: ", dir.get_current_dir()) print("Output dir: ", output_dir) if ! dir.dir_exists(output_dir): show_error("ERROR: Output directory '%s' does not exist" % output_dir, true) return - var expanded_files = [] - for f : String in files: - var basedir : String = f.get_base_dir() - if basedir == "": - basedir = "." - var basename : String = f.get_file() - if basename.find("*") != -1: - basename = basename.replace("*", ".*") - dir = DirAccess.open(basedir) - if dir.is_open(): - var regex : RegEx = RegEx.new() - regex.compile("^"+basename+"$") - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - var file_name = dir.get_next() - while file_name != "": - if regex.search(file_name) and file_name.get_extension() == "ptex": - expanded_files.push_back(basedir+"/"+file_name) - file_name = dir.get_next() - elif f.begins_with("website:"): - for m : String in f.right(-8).split(","): - var range : PackedStringArray = m.split("-") - match range.size(): - 1: - if m.is_valid_int(): - expanded_files.push_back("website:"+m) - 2: - if range[0].is_valid_int() and range[1].is_valid_int(): - for mi in range(range[0].to_int(), range[1].to_int()+1): - expanded_files.push_back("website:"+str(mi)) - else: - expanded_files.push_back(f) + await export_files(expanded_files, output_dir, target, output_file, image_size) else: var no_logo : bool = ( args.find("--no-splash") != -1 ) From b1fcb8ad99bd177479d2d0ca6f050cfbd08e4485 Mon Sep 17 00:00:00 2001 From: Joachim Arting Date: Sun, 3 May 2026 21:12:16 +0200 Subject: [PATCH 3/4] Emit JSON summaries for command line exports --- parse_args.gd | 201 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 154 insertions(+), 47 deletions(-) diff --git a/parse_args.gd b/parse_args.gd index 9af96f6b1..d9dba77c6 100644 --- a/parse_args.gd +++ b/parse_args.gd @@ -12,6 +12,45 @@ static func name_to_lower(s : String) -> String: s = s.remove_chars("()/\"") return s +func _snapshot_output_directory(output_dir : String) -> Dictionary: + var files : Dictionary = {} + if output_dir == "": + return files + if DirAccess.open(output_dir) == null: + return files + var dirs : Array = [""] + while !dirs.is_empty(): + var relative_dir : String = dirs.pop_back() + var current_dir : String = output_dir if relative_dir == "" else output_dir + "/" + relative_dir + var dir : DirAccess = DirAccess.open(current_dir) + if dir == null: + continue + dir.list_dir_begin() + var file_name : String = dir.get_next() + while file_name != "": + if file_name != "." and file_name != "..": + var child_relative : String = file_name if relative_dir == "" else relative_dir + "/" + file_name + if dir.current_is_dir(): + dirs.push_back(child_relative) + else: + var full_path : String = current_dir + "/" + file_name + var file_bytes : PackedByteArray = FileAccess.get_file_as_bytes(full_path) + files[child_relative] = { + "size": file_bytes.size(), + "time": FileAccess.get_modified_time(full_path) + } + file_name = dir.get_next() + dir.list_dir_end() + return files + +func _list_new_output_files(before_snapshot : Dictionary, after_snapshot : Dictionary) -> Array: + var output_files : Array = [] + for file_name in after_snapshot.keys(): + if !before_snapshot.has(file_name) or before_snapshot[file_name] != after_snapshot[file_name]: + output_files.append(file_name) + output_files.sort() + return output_files + func _expand_input_files(files : Array[String]) -> Array[String]: var expanded_files : Array[String] = [] for f : String in files: @@ -72,16 +111,21 @@ func _list_export_profiles(files : Array[String]) -> bool: print(JSON.stringify(results)) return has_loaded_input and !has_failure -func export_files(files, output_dir, target, target_file, image_size) -> void: +func export_files(files : Array[String], output_dir : String, target : String, target_file : String, image_size : int) -> Dictionary: var website_materials : Array = [] var export_list : PackedStringArray = PackedStringArray() + var export_summary : Array = [] + var any_success : bool = false + var any_failure : bool = false for f : String in files: var basename : String = f.get_file().get_basename() var mat_name : String = f.get_file().get_basename() var mat_author : String = "unknown" var gen = await mm_loader.load_gen(f) var from_website : bool = false + var load_failed : bool = gen == null if gen == null and f.begins_with("website:"): + load_failed = false var asset_index = f.right(-8).to_int() basename = "website_"+str(asset_index) var http_request : HTTPRequest = HTTPRequest.new() @@ -100,52 +144,107 @@ func export_files(files, output_dir, target, target_file, image_size) -> void: break var error = http_request.request(MMPaths.WEBSITE_ADDRESS+"/api/getMaterial?id="+str(asset_index)) if error != OK: - continue - var data = ( await http_request.request_completed )[3].get_string_from_utf8() - var json : JSON = JSON.new() - if json.parse(data) != OK or ! json.data is Dictionary: - continue - var parse_result : Dictionary = json.data - if json.parse(parse_result.json) == OK and json.data is Dictionary: - gen = await mm_loader.create_gen(json.data) + load_failed = true else: - print("Failed to download asset ", asset_index) - continue - from_website = true + var data = ( await http_request.request_completed )[3].get_string_from_utf8() + var json : JSON = JSON.new() + if json.parse(data) != OK or ! json.data is Dictionary: + load_failed = true + else: + var parse_result : Dictionary = json.data + if json.parse(parse_result.json) == OK and json.data is Dictionary: + gen = await mm_loader.create_gen(json.data) + from_website = true + else: + print("Failed to download asset ", asset_index) + load_failed = true + if load_failed or gen == null: + any_failure = true + export_summary.append({ + "input": f, + "requested_target": target, + "chosen_target": "", + "prefix": "", + "output_files": [], + "success": false + }) + continue + var mat_name_lower = name_to_lower(mat_name) var mat_author_lower = name_to_lower(mat_author) - if gen != null: - add_child(gen) - for c in gen.get_children(): - if c.has_method("export_material"): - var best_target : String = target - if c.has_method("get_export_profiles"): - if c.get_export_profiles().find(target) == -1: - var best_similarity : float = 0.0 - for p : String in c.get_export_profiles(): - var similarity : float = p.similarity(target) - if similarity > best_similarity: - best_similarity = similarity - best_target = p - if best_target == "": - continue - print("Using target ", best_target, " (and not ", target, ")") - var target_file_name = target_file - target_file_name = target_file_name.replace("%f", basename) - target_file_name = target_file_name.replace("%N", mat_name) - target_file_name = target_file_name.replace("%A", mat_author) - target_file_name = target_file_name.replace("%n", mat_name_lower) - target_file_name = target_file_name.replace("%a", mat_author_lower) - var prefix : String = output_dir+"/"+target_file_name - print("Exporting %s to %s..." % [f.get_file(), prefix]) - await c.export_material(prefix, best_target, image_size, true) - print("Done") - if from_website: - export_list.append("\""+prefix.get_file()+"\": \""+mat_name+","+mat_author+"\"") - gen.queue_free() + var has_exportable : bool = false + add_child(gen) + for c in gen.get_children(): + if !c.has_method("export_material"): + continue + has_exportable = true + var best_target : String = target + if c.has_method("get_export_profiles"): + var profiles : Array = c.get_export_profiles() + if profiles.find(target) == -1: + var best_similarity : float = 0.0 + for p : String in profiles: + var similarity : float = p.similarity(target) + if similarity > best_similarity: + best_similarity = similarity + best_target = p + if best_target == "": + print("No export profile for target ", target, " in ", f.get_file()) + any_failure = true + export_summary.append({ + "input": f, + "requested_target": target, + "chosen_target": "", + "prefix": "", + "output_files": [], + "success": false + }) + continue + print("Using target ", best_target, " (and not ", target, ")") + var target_file_name = target_file + target_file_name = target_file_name.replace("%f", basename) + target_file_name = target_file_name.replace("%N", mat_name) + target_file_name = target_file_name.replace("%A", mat_author) + target_file_name = target_file_name.replace("%n", mat_name_lower) + target_file_name = target_file_name.replace("%a", mat_author_lower) + var prefix : String = output_dir+"/"+target_file_name + + var before_files : Dictionary = _snapshot_output_directory(output_dir) + print("Exporting %s to %s..." % [f.get_file(), prefix]) + await c.export_material(prefix, best_target, image_size, true) + print("Done") + if from_website: + export_list.append("\""+prefix.get_file()+"\": \""+mat_name+","+mat_author+"\"") + + var output_files : Array = _list_new_output_files(before_files, _snapshot_output_directory(output_dir)) + var success : bool = output_files.size() > 0 + if !success: + any_failure = true + else: + any_success = true + export_summary.append({ + "input": f, + "requested_target": target, + "chosen_target": best_target, + "prefix": prefix, + "output_files": output_files, + "success": success + }) + if !has_exportable: + any_failure = true + export_summary.append({ + "input": f, + "requested_target": target, + "chosen_target": "", + "prefix": "", + "output_files": [], + "success": false + }) + gen.queue_free() if not export_list.is_empty(): print(",\n".join(export_list)) - get_tree().quit() + var export_success : bool = any_success and !any_failure + return { "success": export_success, "files": export_summary } func _ready(): RenderingServer.set_default_clear_color(Color.BLACK) @@ -161,6 +260,7 @@ func _ready(): var texture_size : int = 0 var files : Array[String] = [] var list_export_profiles : bool = false + var json_output : bool = false var i = 0 while i < args.size(): var arg : String = args[i] @@ -192,9 +292,11 @@ func _ready(): return texture_size = int(args[i]) if texture_size <= 0: - show_error("ERROR: incorrect size "+args[i], true) + show_error("ERROR: incorrect size " + args[i], true) return image_size = texture_size + "--json": + json_output = true "--list-export-profiles": list_export_profiles = true _: @@ -211,14 +313,19 @@ func _ready(): get_tree().quit(0 if list_success else 1) return - print("Exporting...") - print("Current dir: ", dir.get_current_dir()) - print("Output dir: ", output_dir) + if !json_output: + print("Exporting...") + print("Current dir: ", dir.get_current_dir()) + print("Output dir: ", output_dir) if ! dir.dir_exists(output_dir): show_error("ERROR: Output directory '%s' does not exist" % output_dir, true) return - await export_files(expanded_files, output_dir, target, output_file, image_size) + var export_result = await export_files(expanded_files, output_dir, target, output_file, image_size) + if json_output: + print(JSON.stringify(export_result)) + var export_success = export_result.has("success") and export_result["success"] + get_tree().quit(0 if export_success else 1) else: var no_logo : bool = ( args.find("--no-splash") != -1 ) var scene : PackedScene From 0593a5b93a10a580430a4c874a91a9182e9211d6 Mon Sep 17 00:00:00 2001 From: Joachim Arting Date: Sun, 3 May 2026 21:20:29 +0200 Subject: [PATCH 4/4] Refactor command line export helpers --- parse_args.gd | 409 ++++++++++++++++++++++++++++---------------------- 1 file changed, 231 insertions(+), 178 deletions(-) diff --git a/parse_args.gd b/parse_args.gd index d9dba77c6..0222df2dd 100644 --- a/parse_args.gd +++ b/parse_args.gd @@ -12,7 +12,65 @@ static func name_to_lower(s : String) -> String: s = s.remove_chars("()/\"") return s -func _snapshot_output_directory(output_dir : String) -> Dictionary: +func parse_cli_arguments(args : PackedStringArray) -> Dictionary: + var defaults = { + "enabled": false, + "success": true, + "error": "", + "image_size": 2048, + "target": "Godot/Godot 4 Standard", + "output_dir": DirAccess.open(".").get_current_dir(), + "output_file": "%f", + "files": [], + "list_export_profiles": false, + "json_output": false + } + + var texture_size : int = 0 + var i : int = 0 + while i < args.size(): + var arg : String = args[i] + match arg: + "--export", "--export-material", "--list-export-profiles": + defaults["enabled"] = true + if arg == "--list-export-profiles": + defaults["list_export_profiles"] = true + "-t", "--target": + i += 1 + if i >= args.size(): + return _argument_error(defaults, "ERROR: missing target for " + arg) + defaults["target"] = args[i] + "-o", "--output-dir": + i += 1 + if i >= args.size(): + return _argument_error(defaults, "ERROR: missing output dir for " + arg) + defaults["output_dir"] = args[i] + "--output-file": + i += 1 + if i >= args.size(): + return _argument_error(defaults, "ERROR: missing output file format for --output-file") + defaults["output_file"] = args[i] + "--size": + i += 1 + if i >= args.size(): + return _argument_error(defaults, "ERROR: missing size for --size") + texture_size = int(args[i]) + if texture_size <= 0: + return _argument_error(defaults, "ERROR: incorrect size " + args[i]) + defaults["image_size"] = texture_size + "--json": + defaults["json_output"] = true + _: + defaults["files"].append(arg) + i += 1 + return defaults + +func _argument_error(parsed_args : Dictionary, message : String) -> Dictionary: + parsed_args["success"] = false + parsed_args["error"] = message + return parsed_args + +func snapshot_output_directory(output_dir : String) -> Dictionary: var files : Dictionary = {} if output_dir == "": return files @@ -43,7 +101,7 @@ func _snapshot_output_directory(output_dir : String) -> Dictionary: dir.list_dir_end() return files -func _list_new_output_files(before_snapshot : Dictionary, after_snapshot : Dictionary) -> Array: +func changed_output_files(before_snapshot : Dictionary, after_snapshot : Dictionary) -> Array: var output_files : Array = [] for file_name in after_snapshot.keys(): if !before_snapshot.has(file_name) or before_snapshot[file_name] != after_snapshot[file_name]: @@ -51,7 +109,7 @@ func _list_new_output_files(before_snapshot : Dictionary, after_snapshot : Dicti output_files.sort() return output_files -func _expand_input_files(files : Array[String]) -> Array[String]: +func expand_input_files(files : Array[String]) -> Array[String]: var expanded_files : Array[String] = [] for f : String in files: var basedir : String = f.get_base_dir() @@ -85,7 +143,7 @@ func _expand_input_files(files : Array[String]) -> Array[String]: expanded_files.push_back(f) return expanded_files -func _list_export_profiles(files : Array[String]) -> bool: +func list_export_profiles_for_files(files : Array[String]) -> bool: var results : Array = [] var has_loaded_input : bool = false var has_failure : bool = false @@ -111,112 +169,146 @@ func _list_export_profiles(files : Array[String]) -> bool: print(JSON.stringify(results)) return has_loaded_input and !has_failure -func export_files(files : Array[String], output_dir : String, target : String, target_file : String, image_size : int) -> Dictionary: +func load_material_generator(input : String, website_materials : Array) -> Dictionary: + var result = { + "input": input, + "generator": null, + "basename": input.get_file().get_basename(), + "material_name": input.get_file().get_basename(), + "material_author": "unknown", + "from_website": false, + "load_failed": false + } + + var gen = await mm_loader.load_gen(input) + result["generator"] = gen + if gen != null: + return result + + if !input.begins_with("website:"): + result["load_failed"] = true + return result + + var asset_index = input.right(-8).to_int() + result["basename"] = "website_"+str(asset_index) + var http_request : HTTPRequest = HTTPRequest.new() + add_child(http_request) + + if website_materials.is_empty(): + var get_materials_error = http_request.request(MMPaths.WEBSITE_ADDRESS+"/api/getMaterials") + if get_materials_error == OK: + var data = ( await http_request.request_completed )[3].get_string_from_utf8() + var json = JSON.new() + if json.parse(data) == OK and json.get_data() is Array: + website_materials.clear() + website_materials.append_array(json.get_data()) + + for m in website_materials: + if int(m.id) == asset_index: + result["material_name"] = m.name + result["material_author"] = m.author + break + + var get_material_error = http_request.request(MMPaths.WEBSITE_ADDRESS+"/api/getMaterial?id="+str(asset_index)) + if get_material_error != OK: + result["load_failed"] = true + return result + + var material_data = ( await http_request.request_completed )[3].get_string_from_utf8() + var material_json : JSON = JSON.new() + if material_json.parse(material_data) != OK or !material_json.data is Dictionary: + result["load_failed"] = true + return result + + var parse_result : Dictionary = material_json.data + if material_json.parse(parse_result.json) != OK or !material_json.data is Dictionary: + print("Failed to download asset ", asset_index) + result["load_failed"] = true + return result + + result["generator"] = await mm_loader.create_gen(material_json.data) + result["from_website"] = true + return result + +func choose_export_target(material_node : Node, requested_target : String, input_file : String) -> String: + var chosen_target : String = requested_target + if !material_node.has_method("get_export_profiles"): + return chosen_target + + var profiles : Array = material_node.get_export_profiles() + if profiles.find(requested_target) == -1: + var best_similarity : float = 0.0 + for p : String in profiles: + var similarity : float = p.similarity(requested_target) + if similarity > best_similarity: + best_similarity = similarity + chosen_target = p + if chosen_target == "": + print("No export profile for target ", requested_target, " in ", input_file.get_file()) + return "" + print("Using target ", chosen_target, " (and not ", requested_target, ")") + return chosen_target + +func build_output_prefix(output_dir : String, output_file : String, basename : String, material_name : String, material_author : String) -> String: + var target_file_name = output_file + target_file_name = target_file_name.replace("%f", basename) + target_file_name = target_file_name.replace("%N", material_name) + target_file_name = target_file_name.replace("%A", material_author) + target_file_name = target_file_name.replace("%n", name_to_lower(material_name)) + target_file_name = target_file_name.replace("%a", name_to_lower(material_author)) + return output_dir + "/" + target_file_name + +func _append_export_failure(export_summary : Array, input_file : String, requested_target : String, chosen_target : String = "") -> void: + export_summary.append({ + "input": input_file, + "requested_target": requested_target, + "chosen_target": chosen_target, + "prefix": "", + "output_files": [], + "success": false + }) + +func export_materials_for_files(files : Array[String], output_dir : String, target : String, output_file : String, image_size : int) -> Dictionary: var website_materials : Array = [] var export_list : PackedStringArray = PackedStringArray() var export_summary : Array = [] var any_success : bool = false var any_failure : bool = false for f : String in files: - var basename : String = f.get_file().get_basename() - var mat_name : String = f.get_file().get_basename() - var mat_author : String = "unknown" - var gen = await mm_loader.load_gen(f) - var from_website : bool = false - var load_failed : bool = gen == null - if gen == null and f.begins_with("website:"): - load_failed = false - var asset_index = f.right(-8).to_int() - basename = "website_"+str(asset_index) - var http_request : HTTPRequest = HTTPRequest.new() - add_child(http_request) - if website_materials.is_empty(): - var error = http_request.request(MMPaths.WEBSITE_ADDRESS+"/api/getMaterials") - if error == OK: - var data = ( await http_request.request_completed )[3].get_string_from_utf8() - var json = JSON.new() - if json.parse(data) == OK and json.get_data() is Array: - website_materials = json.get_data() - for m in website_materials: - if int(m.id) == asset_index: - mat_name = m.name - mat_author = m.author - break - var error = http_request.request(MMPaths.WEBSITE_ADDRESS+"/api/getMaterial?id="+str(asset_index)) - if error != OK: - load_failed = true - else: - var data = ( await http_request.request_completed )[3].get_string_from_utf8() - var json : JSON = JSON.new() - if json.parse(data) != OK or ! json.data is Dictionary: - load_failed = true - else: - var parse_result : Dictionary = json.data - if json.parse(parse_result.json) == OK and json.data is Dictionary: - gen = await mm_loader.create_gen(json.data) - from_website = true - else: - print("Failed to download asset ", asset_index) - load_failed = true - if load_failed or gen == null: + var load_result = await load_material_generator(f, website_materials) + var gen = load_result["generator"] + var from_website = load_result["from_website"] + var basename = load_result["basename"] + var mat_name = load_result["material_name"] + var mat_author = load_result["material_author"] + + if load_result["load_failed"] or gen == null: any_failure = true - export_summary.append({ - "input": f, - "requested_target": target, - "chosen_target": "", - "prefix": "", - "output_files": [], - "success": false - }) + _append_export_failure(export_summary, f, target) continue - var mat_name_lower = name_to_lower(mat_name) - var mat_author_lower = name_to_lower(mat_author) var has_exportable : bool = false add_child(gen) for c in gen.get_children(): if !c.has_method("export_material"): continue has_exportable = true - var best_target : String = target - if c.has_method("get_export_profiles"): - var profiles : Array = c.get_export_profiles() - if profiles.find(target) == -1: - var best_similarity : float = 0.0 - for p : String in profiles: - var similarity : float = p.similarity(target) - if similarity > best_similarity: - best_similarity = similarity - best_target = p - if best_target == "": - print("No export profile for target ", target, " in ", f.get_file()) - any_failure = true - export_summary.append({ - "input": f, - "requested_target": target, - "chosen_target": "", - "prefix": "", - "output_files": [], - "success": false - }) - continue - print("Using target ", best_target, " (and not ", target, ")") - var target_file_name = target_file - target_file_name = target_file_name.replace("%f", basename) - target_file_name = target_file_name.replace("%N", mat_name) - target_file_name = target_file_name.replace("%A", mat_author) - target_file_name = target_file_name.replace("%n", mat_name_lower) - target_file_name = target_file_name.replace("%a", mat_author_lower) - var prefix : String = output_dir+"/"+target_file_name - - var before_files : Dictionary = _snapshot_output_directory(output_dir) + var chosen_target = choose_export_target(c, target, f) + if chosen_target == "": + any_failure = true + _append_export_failure(export_summary, f, target) + continue + var prefix : String = build_output_prefix(output_dir, output_file, basename, mat_name, mat_author) + var output_files : Array = [] + + var before_files : Dictionary = snapshot_output_directory(output_dir) print("Exporting %s to %s..." % [f.get_file(), prefix]) - await c.export_material(prefix, best_target, image_size, true) + await c.export_material(prefix, chosen_target, image_size, true) print("Done") if from_website: export_list.append("\""+prefix.get_file()+"\": \""+mat_name+","+mat_author+"\"") - var output_files : Array = _list_new_output_files(before_files, _snapshot_output_directory(output_dir)) + output_files = changed_output_files(before_files, snapshot_output_directory(output_dir)) var success : bool = output_files.size() > 0 if !success: any_failure = true @@ -225,23 +317,17 @@ func export_files(files : Array[String], output_dir : String, target : String, t export_summary.append({ "input": f, "requested_target": target, - "chosen_target": best_target, + "chosen_target": chosen_target, "prefix": prefix, "output_files": output_files, "success": success }) if !has_exportable: any_failure = true - export_summary.append({ - "input": f, - "requested_target": target, - "chosen_target": "", - "prefix": "", - "output_files": [], - "success": false - }) + _append_export_failure(export_summary, f, target) gen.queue_free() - if not export_list.is_empty(): + + if !export_list.is_empty(): print(",\n".join(export_list)) var export_success : bool = any_success and !any_failure return { "success": export_success, "files": export_summary } @@ -249,84 +335,9 @@ func export_files(files : Array[String], output_dir : String, target : String, t func _ready(): RenderingServer.set_default_clear_color(Color.BLACK) var args : PackedStringArray = OS.get_cmdline_args() - if ("--export" in args) or ("--export-material" in args) or ("--list-export-profiles" in args): - var image_size : int = 2048 - var dir : DirAccess = DirAccess.open(".") - var output = [] - var target : String = "Godot/Godot 4 Standard" - #TODO: fix this - var output_dir : String = dir.get_current_dir() - var output_file : String = "%f" - var texture_size : int = 0 - var files : Array[String] = [] - var list_export_profiles : bool = false - var json_output : bool = false - var i = 0 - while i < args.size(): - var arg : String = args[i] - match arg: - "--export", "--export-material": - pass - "-t", "--target": - i += 1 - if i >= args.size(): - show_error("ERROR: missing target for " + arg, true) - return - target = args[i] - "-o", "--output-dir": - i += 1 - if i >= args.size(): - show_error("ERROR: missing output dir for " + arg, true) - return - output_dir = args[i] - "--output-file": - i += 1 - if i >= args.size(): - show_error("ERROR: missing output file format for --output-file", true) - return - output_file = args[i] - "--size": - i += 1 - if i >= args.size(): - show_error("ERROR: missing size for --size", true) - return - texture_size = int(args[i]) - if texture_size <= 0: - show_error("ERROR: incorrect size " + args[i], true) - return - image_size = texture_size - "--json": - json_output = true - "--list-export-profiles": - list_export_profiles = true - _: - files.push_back(arg) - i += 1 - - var expanded_files : Array[String] = _expand_input_files(files) - if expanded_files.is_empty(): - show_error("ERROR: No input files provided", true) - return - - if list_export_profiles: - var list_success = await _list_export_profiles(expanded_files) - get_tree().quit(0 if list_success else 1) - return - - if !json_output: - print("Exporting...") - print("Current dir: ", dir.get_current_dir()) - print("Output dir: ", output_dir) - if ! dir.dir_exists(output_dir): - show_error("ERROR: Output directory '%s' does not exist" % output_dir, true) - return - - var export_result = await export_files(expanded_files, output_dir, target, output_file, image_size) - if json_output: - print(JSON.stringify(export_result)) - var export_success = export_result.has("success") and export_result["success"] - get_tree().quit(0 if export_success else 1) - else: + var parsed_args = parse_cli_arguments(args) + + if !parsed_args["enabled"]: var no_logo : bool = ( args.find("--no-splash") != -1 ) var scene : PackedScene if no_logo: @@ -335,3 +346,45 @@ func _ready(): scene = load("res://splash_screen/splash_screen.tscn") await get_tree().process_frame get_tree().change_scene_to_packed(scene) + return + + if !parsed_args["success"]: + show_error(parsed_args["error"], true) + return + + var target : String = parsed_args["target"] + var image_size : int = parsed_args["image_size"] + var output_dir : String = parsed_args["output_dir"] + var output_file : String = parsed_args["output_file"] + var json_output : bool = parsed_args["json_output"] + var list_export_profiles : bool = parsed_args["list_export_profiles"] + var files : Array[String] = [] + for file_name in parsed_args["files"]: + files.append(file_name) + + var expanded_files : Array[String] = expand_input_files(files) + if expanded_files.is_empty(): + show_error("ERROR: No input files provided", true) + return + + if list_export_profiles: + var list_success = await list_export_profiles_for_files(expanded_files) + get_tree().quit(0 if list_success else 1) + return + + if !json_output: + print("Exporting...") + var dir : DirAccess = DirAccess.open(".") + print("Current dir: ", dir.get_current_dir()) + print("Output dir: ", output_dir) + + var root_dir : DirAccess = DirAccess.open(".") + if !root_dir.dir_exists(output_dir): + show_error("ERROR: Output directory '%s' does not exist" % output_dir, true) + return + + var export_result = await export_materials_for_files(expanded_files, output_dir, target, output_file, image_size) + if json_output: + print(JSON.stringify(export_result)) + var export_success = export_result.has("success") and export_result["success"] + get_tree().quit(0 if export_success else 1)