-
Notifications
You must be signed in to change notification settings - Fork 0
Optional Helper Scripts
This page contains optional helper scripts/tools that make the Blender part of the workflow less annoying.
None of this is strictly required. You can set up the materials and reload textures manually if you want to. The helper script just adds a small panel inside Blender so you do not need to keep re-running random script snippets every time you open the file.
The StS2 Tool Kit is a small Blender add-on that adds a panel to the right side of the 3D viewport.
It can:
- Apply recommended material settings for 2D puppet-style image planes.
- Make materials unlit.
- Change texture filtering.
- Reload external images after you update/export them again from Krita.
- Optionally only affect selected objects.
This is mainly meant for the Krita → Blender → Godot workflow described in this guide.
The script is provided as plain source code so you can inspect it before using it.
- Copy the script below.
- Save it as
sts2_tool_kit.py. - Open Blender.
- Go to
Edit. - Open
Preferences. - Go to
Add-ons. - Click
Install.... - Select
sts2_tool_kit.py. - Enable the add-on.
- In the 3D viewport, press
Nto open the right sidebar. - Open the
StS2tab.
You should now see a panel called StS2 Tool Kit.
Controls how much transparency gets cut off.
Lower values keep softer edges. Higher values make cleaner but sharper cutouts.
For this workflow, values around 0.45 worked reasonably well for me, but you may need to adjust it depending on your art.
Controls how Blender displays image textures.
Options:
-
Linear- smooth texture filtering -
Closest- pixelated texture filtering -
Cubic- smoother texture filtering -
Smart- lets Blender decide automatically
For most painted character art, Linear is probably fine.
If enabled, the tool only changes materials used by selected objects.
If disabled, the tool searches through all objects in the file and changes any materials it can find.
This is useful if you only want to test settings on a few parts before applying them everywhere.
Rebuilds the image materials as simple unlit materials.
This is useful because the final character should look like flat 2D art, not like a shaded 3D model.
If this is disabled, the tool keeps the current material node setup and only changes the basic transparency/filtering settings.
Applies the current material settings.
Use this after importing your image planes, or after changing options like Alpha Threshold, Texture Filtering, or Make Materials Unlit.
Reloads external image files and then reapplies the current material settings.
This is useful when you update/export your character parts from Krita and want Blender to refresh them without manually reloading every texture.
Click to expand the script
bl_info = {
"name": "StS2 Tool Kit",
"author": "lovecMC",
"version": (1, 0, 4),
"blender": (3, 6, 0),
"location": "View3D > Sidebar > StS2",
"description": "Simple tools for 2D puppet materials and image reloading.",
"category": "Material",
}
import bpy
class STS2ToolKitSettings(bpy.types.PropertyGroup):
alpha_threshold: bpy.props.FloatProperty(
name="Alpha Threshold",
description="Lower keeps softer edges. Higher makes cleaner but sharper cutouts.",
default=0.45,
min=0.0,
max=1.0,
)
texture_filtering: bpy.props.EnumProperty(
name="Texture Filtering",
description="How Blender displays image textures",
items=[
("Linear", "Linear", "Smooth texture filtering"),
("Closest", "Closest", "Pixelated texture filtering"),
("Cubic", "Cubic", "Smoother texture filtering"),
("Smart", "Smart", "Blender decides automatically"),
],
default="Linear",
)
selected_only: bpy.props.BoolProperty(
name="Selected Objects Only",
description="Only edit materials used by selected objects",
default=False,
)
make_unlit: bpy.props.BoolProperty(
name="Make Materials Unlit",
description="Rebuild image materials as simple unlit materials",
default=False,
)
def get_materials(context, selected_only):
found = set()
objects = context.selected_objects if selected_only else bpy.data.objects
for obj in objects:
data = getattr(obj, "data", None)
materials = getattr(data, "materials", None)
if not materials:
continue
for material in materials:
if material and material.as_pointer() not in found:
found.add(material.as_pointer())
yield material
def find_image_texture(material):
if not material.use_nodes or not material.node_tree:
return None
for node in material.node_tree.nodes:
if node.type == "TEX_IMAGE" and node.image:
return node
return None
def apply_basic_material_settings(material, settings):
image_nodes_changed = 0
# Alpha Clip usually exports to Godot as masked / alpha scissor transparency.
if hasattr(material, "blend_method"):
material.blend_method = "CLIP"
if hasattr(material, "alpha_threshold"):
material.alpha_threshold = settings.alpha_threshold
# 2D puppet parts usually need to be visible from both sides.
if hasattr(material, "use_backface_culling"):
material.use_backface_culling = False
if hasattr(material, "show_transparent_back"):
material.show_transparent_back = False
# Keep the current shader setup. Only change image filtering.
if material.use_nodes and material.node_tree:
for node in material.node_tree.nodes:
if node.type == "TEX_IMAGE" and hasattr(node, "interpolation"):
node.interpolation = settings.texture_filtering
image_nodes_changed += 1
return image_nodes_changed
def make_material_unlit(material, settings):
old_image_node = find_image_texture(material)
if old_image_node is None:
return False
image = old_image_node.image
material.use_nodes = True
material.blend_method = "CLIP"
material.alpha_threshold = settings.alpha_threshold
if hasattr(material, "use_backface_culling"):
material.use_backface_culling = False
if hasattr(material, "show_transparent_back"):
material.show_transparent_back = False
nodes = material.node_tree.nodes
links = material.node_tree.links
nodes.clear()
image_node = nodes.new("ShaderNodeTexImage")
image_node.location = (-400, 0)
image_node.image = image
image_node.interpolation = settings.texture_filtering
transparent_node = nodes.new("ShaderNodeBsdfTransparent")
transparent_node.location = (-150, 100)
unlit_node = nodes.new("ShaderNodeBackground")
unlit_node.location = (-150, -100)
unlit_node.inputs["Strength"].default_value = 1.0
mix_node = nodes.new("ShaderNodeMixShader")
mix_node.location = (100, 0)
output_node = nodes.new("ShaderNodeOutputMaterial")
output_node.location = (350, 0)
links.new(image_node.outputs["Alpha"], mix_node.inputs["Fac"])
links.new(transparent_node.outputs["BSDF"], mix_node.inputs[1])
links.new(image_node.outputs["Color"], unlit_node.inputs["Color"])
links.new(unlit_node.outputs["Background"], mix_node.inputs[2])
links.new(mix_node.outputs["Shader"], output_node.inputs["Surface"])
return True
def apply_material_settings(context, settings):
changed = 0
skipped = 0
for material in get_materials(context, settings.selected_only):
if settings.make_unlit:
if make_material_unlit(material, settings):
changed += 1
else:
skipped += 1
else:
apply_basic_material_settings(material, settings)
changed += 1
return changed, skipped
def reload_images(context):
reloaded = 0
skipped = 0
for image in bpy.data.images:
is_external_file = (
image.source == "FILE"
and image.filepath
and image.packed_file is None
)
if not is_external_file:
skipped += 1
continue
# Do not overwrite unsaved image edits made inside Blender.
if image.is_dirty:
skipped += 1
continue
try:
image.reload()
reloaded += 1
except RuntimeError:
skipped += 1
if context.screen:
for area in context.screen.areas:
area.tag_redraw()
return reloaded, skipped
class STS2_OT_apply_material_settings(bpy.types.Operator):
bl_idname = "sts2.apply_material_settings"
bl_label = "Apply Material Settings"
bl_description = "Apply alpha clip, texture filtering, and optionally make materials unlit"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
settings = context.scene.sts2_tool_kit
changed, skipped = apply_material_settings(context, settings)
if changed == 0:
self.report({"WARNING"}, "No materials found.")
return {"CANCELLED"}
if settings.make_unlit:
self.report(
{"INFO"},
f"Updated {changed} unlit material(s). Skipped {skipped}.",
)
else:
self.report(
{"INFO"},
f"Updated {changed} material(s).",
)
return {"FINISHED"}
class STS2_OT_reload_images(bpy.types.Operator):
bl_idname = "sts2.reload_images"
bl_label = "Reload Images"
bl_description = "Reload images and apply the current material settings"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
settings = context.scene.sts2_tool_kit
reloaded, image_skipped = reload_images(context)
changed, material_skipped = apply_material_settings(context, settings)
self.report(
{"INFO"},
f"Reloaded {reloaded} image(s). Updated {changed} material(s).",
)
return {"FINISHED"}
class STS2_PT_tool_kit(bpy.types.Panel):
bl_label = "StS2 Tool Kit"
bl_idname = "STS2_PT_tool_kit"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "StS2"
def draw(self, context):
layout = self.layout
settings = context.scene.sts2_tool_kit
layout.prop(settings, "alpha_threshold")
layout.prop(settings, "texture_filtering")
layout.prop(settings, "selected_only")
layout.prop(settings, "make_unlit")
layout.separator()
layout.operator("sts2.apply_material_settings", icon="MATERIAL")
layout.operator("sts2.reload_images", icon="FILE_REFRESH")
classes = (
STS2ToolKitSettings,
STS2_OT_apply_material_settings,
STS2_OT_reload_images,
STS2_PT_tool_kit,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.sts2_tool_kit = bpy.props.PointerProperty(
type=STS2ToolKitSettings,
)
def unregister():
del bpy.types.Scene.sts2_tool_kit
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()This script is intentionally simple. It is not a full asset pipeline or a replacement for understanding what Blender is doing.
It is mostly there to remove repeated busywork:
- fixing imported materials,
- making them unlit,
- changing alpha cutoff,
- reloading exported textures after edits.
If something looks wrong after applying the settings, try selecting only one or two planes and testing the settings there first.