Godot Version
4.6.1.rc1
Question
I am trying to automate the steps in this video by Kay Lousberg on importing their models: Using KayKit Characters in Godot (Detailed Version). I’ve created an import script to do so.
I am using the Barbarian model found here for free: KayKit - Character Pack : Adventurers by Kay Lousberg (To be clear, I am using this paid version: The Complete KayKit by Kay Lousberg)
My current issues are twofold:
- When I create an image (code below) by extracting the texture from the material and save it, I have to use
EditorInterface.get_resource_filesystem().call_deferred("scan")for it to show up in the FileSystem windows without a reboot, but it throws errors. NOTE: No errors were thrown when I did this with just the material, once I added in thecall_deferred(). - I cannot figure out how to assign the saved texture to the material. Currently I’m saving it, then loading it to assign it back to where I got it from.
The manual equivalent of this is dragging and dropping the texture into the material after saving the material externally.
Problem Code:
# Create Texture
var texture_path: String = GameConstants.TEXTURES_PATH + "kay_kit_models/" + str(owner_scene.name).to_snake_case() + "_texture.png"
ensure_path_exists(texture_path)
var texture = material.albedo_texture
var image = texture.get_image()
var error= image.save_png(texture_path)
if error != OK:
print("Saving texture failed.")
else:
print("Image saved to %s" % [texture_path])
#Create material
material.albedo_texture = ResourceLoader.load(texture_path)
ensure_path_exists(material_path)
ResourceSaver.save(material, material_path)
print("Material saved to %s" % [material_path])
var saved_material: StandardMaterial3D = ResourceLoader.load(material_path)
mesh.surface_set_material(index, saved_material)
#Refresh FileSystem so saved texture(s) and material(s) appear immeditaely
EditorInterface.get_resource_filesystem().call_deferred("scan")
Full Import Script:
@tool
extends EditorScenePostImport
enum LoopMode { LOOP_NONE, LOOP_LINEAR, LOOP_PINGPONG }
const LOOPMODE = ["LOOP_NONE", "[b][color=green]LOOP_LINEAR[/color][/b]", "[b][color=orange]LOOP_PINGPONG[/color][/b]"]
var owner_scene
var slots: Dictionary = {
"HeadSlot" : "head",
"RightHandSlot": "handslot.r",
"LeftHandSlot": "handslot.l",
"BackSlot": "chest"
}
# Import script for rig to set up everything so little editing is needed once the character is
# instantiated.
func _post_import(scene):
owner_scene = scene
print_rich("\n[b][color=red]Begin Post-import[/color] -> [color=purple]%s[/color] as [color=green]%s[/color][/b]" % [scene.name + "Skin", scene.get_class()])
print_rich("[b]Time/Date Stamp: %s[/b]\n" % [Time.get_datetime_string_from_system(false, true)])
prepare(scene)
enhance(scene)
scene.name = scene.name + "Skin"
print_rich("\n[b][color=red]NOTE:[/color] Ignore any [color=salmon]reimport[/color] or [color=salmon]editor/gui/progress_dialog[/color] errors that appear below this line. This happens when an import script creates new directories or files and we scan so they will show up in FileSystem.[/b]")
return scene
func prepare(node: Node) -> void:
# Rotating the model to face the other direction so it moves in the correct direction when animated.
if node is Skeleton3D:
node.rotate_y(deg_to_rad(-180.0))
print_rich("Post-import: [b]Rotated [color=green]%s[/color] [color=yellow]-180 degrees[/color][/b] on the [b][color=green]y-axis[/color][/b]" % [node.get_class()])
if node is MeshInstance3D:
var mesh: Mesh = node.mesh
for index in mesh.get_surface_count():
var material: StandardMaterial3D = mesh.surface_get_material(index)
var material_path: String = GameConstants.MATERIALS_PATH + "kay_kit_models/" + str(owner_scene.name).to_snake_case() + ".material"
if ResourceLoader.exists(material_path):
var saved_material: StandardMaterial3D = ResourceLoader.load(material_path)
print("Assigning saved material")
mesh.surface_set_material(index, saved_material)
else:
# Create Texture
var texture_path: String = GameConstants.TEXTURES_PATH + "kay_kit_models/" + str(owner_scene.name).to_snake_case() + "_texture.png"
ensure_path_exists(texture_path)
var texture = material.albedo_texture
var image = texture.get_image()
var error= image.save_png(texture_path)
if error != OK:
print("Saving texture failed.")
else:
print("Image saved to %s" % [texture_path])
#Create material
material.albedo_texture = ResourceLoader.load(texture_path)
ensure_path_exists(material_path)
ResourceSaver.save(material, material_path)
print("Material saved to %s" % [material_path])
var saved_material: StandardMaterial3D = ResourceLoader.load(material_path)
mesh.surface_set_material(index, saved_material)
#Refresh FileSystem so saved texture(s) and material(s) appear immeditaely
EditorInterface.get_resource_filesystem().call_deferred("scan")
# Recursively call this function on any subnodes that exist.
for subnode in node.get_children():
prepare(subnode)
func enhance(node: Node) -> void:
#Add slots for head, hands and chest (which is where the capes go).
if node is Skeleton3D:
for slot in slots:
var bone_attachment_3d: BoneAttachment3D = BoneAttachment3D.new()
node.add_child(bone_attachment_3d)
bone_attachment_3d.owner = owner_scene
bone_attachment_3d.name = slot
bone_attachment_3d.bone_name = slots[slot]
print_rich("Post-import: [b]Added [color=green]%s[/color] -> Slot: [color=yellow]%s[/color][/b]" % [slot, slots[slot]])
# Recursively call this function on any subnodes that exist.
for subnode in node.get_children():
enhance(subnode)
#Checks to make sure the path exists, and if not, creates the requisite folders.
func ensure_path_exists(path: String) -> void:
var directory: String = path.get_base_dir()
if DirAccess.dir_exists_absolute(directory):
return
else:
DirAccess.make_dir_recursive_absolute(directory)
GameConstants File:
class_name GameConstants
#Filepaths
const MATERIALS_PATH = "res://assets/materials/"
const TEXTURES_PATH = "res://assets/textures/"
Console output when I run the script:
Begin Post-import -> BarbarianSkin as Node3D
Time/Date Stamp: 2026-02-23 12:02:41
Post-import: Rotated Skeleton3D -180 degrees on the y-axis
Image saved to res://assets/textures/kay_kit_models/barbarian_texture.png
ERROR: Failed loading resource: res://assets/textures/kay_kit_models/barbarian_texture.png.
ERROR: Error loading resource: 'res://assets/textures/kay_kit_models/barbarian_texture.png'.
Material saved to res://assets/materials/kay_kit_models/barbarian.material
Assigning saved material
Assigning saved material
Assigning saved material
Assigning saved material
Assigning saved material
Assigning saved material
Post-import: Added HeadSlot -> Slot: head
Post-import: Added RightHandSlot -> Slot: handslot.r
Post-import: Added LeftHandSlot -> Slot: handslot.l
Post-import: Added BackSlot -> Slot: chest
NOTE: Ignore any reimport or editor/gui/progress_dialog errors that appear below this line. This happens when an import script creates new directories or files and we scan so they will show up in FileSystem.
ERROR: Task 'reimport' already exists.
ERROR: editor/gui/progress_dialog.cpp:230 - Condition "!tasks.has(p_task)" is true. Returning: canceled
ERROR: editor/gui/progress_dialog.cpp:253 - Condition "!tasks.has(p_task)" is true.





