Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change Skin Swap's behavior for more nuanced swapping #498

Merged
merged 6 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 85 additions & 60 deletions MCprep_addon/materials/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,8 @@ def matprep_cycles(mat: Material, options: PrepOptions) -> Optional[bool]:
return res


def set_texture_pack(material: Material, folder: Path, use_extra_passes: bool) -> bool:
def set_texture_pack(
material: Material, folder: Path, use_extra_passes: bool) -> bool:
"""Replace existing material's image with texture pack's.

Run through and check for each if counterpart material exists, then
Expand All @@ -347,29 +348,43 @@ def set_texture_pack(material: Material, folder: Path, use_extra_passes: bool) -
return 0

image_data = util.loadTexture(image)
_ = set_cycles_texture(image_data, material, True)
_ = set_cycles_texture(
image_data, material, use_extra_passes=use_extra_passes)
return 1


def assert_textures_on_materials(image: Image, materials: List[Material]) -> int:
"""Called for any texture changing, e.g. skin, input a list of material and
an already loaded image datablock."""
# TODO: Add option to search for or ignore/remove extra maps (normal, etc)
def assert_textures_on_materials(
image: Image,
materials: List[Material],
extra_passes: bool = False,
swap_all_imgs: bool = True) -> int:
"""Sets and returns number og modified textures across input mats.

Called for any texture changing, e.g. skin, input a list of material and
an already loaded image datablock.
"""
count = 0
for mat in materials:
status = set_cycles_texture(image, mat)
status = set_cycles_texture(
image, mat, extra_passes=extra_passes, swap_all_imgs=swap_all_imgs)
if status:
count += 1
return count


def set_cycles_texture(image: Image, material: Material, extra_passes: bool=False) -> bool:
"""
def set_cycles_texture(
image: Image,
material: Material,
extra_passes: bool = False,
swap_all_imgs: bool = True) -> bool:
"""Assigns

Used by skin swap and assiging missing textures or tex swapping.
Args:
image: already loaded image datablock
material: existing material datablock
extra_passes: whether to include or hard exclude non diffuse passes
swap_all_imgs: whether to force swap all images, or only do selectively
"""
env.log(f"Setting cycles texture for img: {image.name} mat: {material.name}")
if material.node_tree is None:
Expand All @@ -392,15 +407,15 @@ def set_cycles_texture(image: Image, material: Material, extra_passes: bool=Fals
node.hide = not is_grayscale
env.log(" mix_rgb to saturate texture")

# if node.type != "TEX_IMAGE": continue

# check to see nodes and their respective pre-named field,
# saved as an attribute on the node
if "MCPREP_diffuse" in node:
if node.type != 'TEX_IMAGE':
continue
elif "MCPREP_diffuse" in node:
node.image = image
node.mute = False
node.hide = False
elif "MCPREP_normal" in node and node.type == 'TEX_IMAGE':
elif "MCPREP_normal" in node:
if "normal" in img_sets:
new_img = util.loadTexture(img_sets["normal"])
node.image = new_img
Expand All @@ -415,7 +430,7 @@ def set_cycles_texture(image: Image, material: Material, extra_passes: bool=Fals
# normal_map = node.outputs[0].links[0].to_node
# principled = ...

elif "MCPREP_specular" in node and node.type == 'TEX_IMAGE':
elif "MCPREP_specular" in node:
if "specular" in img_sets:
new_img = util.loadTexture(img_sets["specular"])
node.image = new_img
Expand All @@ -426,7 +441,14 @@ def set_cycles_texture(image: Image, material: Material, extra_passes: bool=Fals
node.mute = True
node.hide = True

elif node.type == "TEX_IMAGE":
# Unlike the other names, this one is set with the Name option in the
# Blender UI, and thus is mapped to node.name and not node itself
elif util.nameGeneralize(node.name) == "MCPREP_SKIN_SWAP":
node.image = image
node.mute = False
node.hide = False

elif swap_all_imgs is True:
# assume all unlabeled texture nodes should be the diffuse pass
node["MCPREP_diffuse"] = True # annotate node for future reference
node.image = image
Expand Down Expand Up @@ -1118,54 +1140,57 @@ def texgen_seus(mat: Material, passes: Dict[str, Image], nodeInputs: List, use_r
# nodeTexDisp["MCPREP_disp"] = True
nodeTexDiff.image = image_diff


def generate_base_material(
context: Context, name: str,
path: Union[Path, str], useExtraMaps: bool
context: Context,
name: str,
path: Union[Path, str],
useExtraMaps: bool
) -> Tuple[Optional[Material], Optional[str]]:
"""Generate a base material from name and active resource pack"""
try:
image = bpy.data.images.load(path, check_existing=True)
except: # if Image is not found
image = None
mat = bpy.data.materials.new(name=name)

engine = context.scene.render.engine
if engine in ['CYCLES','BLENDER_EEVEE']:
# need to create at least one texture node first, then the rest works
mat.use_nodes = True
nodes = mat.node_tree.nodes
node_diff = create_node(
nodes, 'ShaderNodeTexImage',
name="Diffuse Texture",
label="Diffuse Texture",
location=(-380, 140),
interpolation='Closest',
image=image
)
node_diff["MCPREP_diffuse"] = True

# The offset and link diffuse is for default no texture setup
links = mat.node_tree.links
for n in nodes:
if n.bl_idname == 'ShaderNodeBsdfPrincipled':
links.new(node_diff.outputs[0], n.inputs[0])
links.new(node_diff.outputs[1], n.inputs["Alpha"])
break
env.log("Added blank texture node")
# Initialize extra passes as well
if image:
node_spec = create_node(nodes, 'ShaderNodeTexImage')
node_spec["MCPREP_specular"] = True
node_nrm = create_node(nodes, 'ShaderNodeTexImage')
node_nrm["MCPREP_normal"] = True
# now use standard method to update textures
set_cycles_texture(image, mat, useExtraMaps)
else:
return None, "Only Cycles and Eevee supported"
"""Generate a base material from name and active resource pack"""
try:
image = bpy.data.images.load(path, check_existing=True)
except Exception: # if Image is not found
image = None
mat = bpy.data.materials.new(name=name)

engine = context.scene.render.engine
if engine in ['CYCLES', 'BLENDER_EEVEE']:
# need to create at least one texture node first, then the rest works
mat.use_nodes = True
nodes = mat.node_tree.nodes
node_diff = create_node(
nodes, 'ShaderNodeTexImage',
name="Diffuse Texture",
label="Diffuse Texture",
location=(-380, 140),
interpolation='Closest',
image=image
)
node_diff["MCPREP_diffuse"] = True

# The offset and link diffuse is for default no texture setup
links = mat.node_tree.links
for n in nodes:
if n.bl_idname == 'ShaderNodeBsdfPrincipled':
links.new(node_diff.outputs[0], n.inputs[0])
links.new(node_diff.outputs[1], n.inputs["Alpha"])
break

env.log("Added blank texture node")
# Initialize extra passes as well
if image:
node_spec = create_node(nodes, 'ShaderNodeTexImage')
node_spec["MCPREP_specular"] = True
node_nrm = create_node(nodes, 'ShaderNodeTexImage')
node_nrm["MCPREP_normal"] = True
# now use standard method to update textures
set_cycles_texture(image, mat, extra_passes=useExtraMaps)

else:
return None, "Only Cycles and Eevee supported"

return mat, None
return mat, None


def matgen_cycles_simple(mat: Material, options: PrepOptions) -> Optional[bool]:
Expand Down
49 changes: 40 additions & 9 deletions MCprep_addon/materials/skin.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,21 @@
import bpy
from bpy_extras.io_utils import ImportHelper
from bpy.app.handlers import persistent
from bpy.types import Context, Image, Material
from bpy.types import Context, Material

from . import generate
from .. import tracking
from .. import util

from ..conf import env


swap_all_imgs_desc = (
"Swap textures in all image nodes that exist on the selected \n"
"material; if off, will instead seek to only replace the images of \n"
"nodes (not image blocks) named MCPREP_SKIN_SWAP"
)

# -----------------------------------------------------------------------------
# Support functions
# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -84,7 +91,7 @@ def handler_skins_enablehack(scene):
"""Scene update to auto load skins on load after new file."""
try:
bpy.app.handlers.scene_update_pre.remove(handler_skins_enablehack)
except:
except Exception:
pass
env.log("Triggering Handler_skins_load from first enable", vv_only=True)
handler_skins_load(scene)
Expand All @@ -95,11 +102,18 @@ def handler_skins_load(scene):
try:
env.log("Reloading skins", vv_only=True)
reloadSkinList(bpy.context)
except:
except Exception as e:
print(e)
env.log("Didn't run skin reloading callback", vv_only=True)


def loadSkinFile(self, context: Context, filepath: Path, new_material: bool=False):
def loadSkinFile(
self,
context: Context,
filepath: Path,
new_material: bool = False,
swap_all_imgs: bool = False) -> int:
"""Replaces image textures with target path for use in operator."""
if not os.path.isfile(filepath):
self.report({'ERROR'}, f"Image file not found: {filepath}")
return 1
Expand All @@ -121,7 +135,8 @@ def loadSkinFile(self, context: Context, filepath: Path, new_material: bool=Fals
self.report(
{'WARNING'}, "Skinswap skipped {} linked objects".format(skipped))

status = generate.assert_textures_on_materials(image, mats)
status = generate.assert_textures_on_materials(
image, mats, swap_all_imgs=swap_all_imgs)
if status is False:
self.report({'ERROR'}, "No image textures found to update")
return 1
Expand Down Expand Up @@ -363,13 +378,18 @@ class MCPREP_OT_swap_skin_from_file(bpy.types.Operator, ImportHelper):
name="New Material",
description="Create a new material instead of overwriting existing one",
default=True)
swap_all_imgs: bpy.props.BoolProperty(
name="Swap All Images",
description=swap_all_imgs_desc,
default=True)
skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'})

track_function = "skin"
track_param = "file import"
@tracking.report_error
def execute(self, context):
res = loadSkinFile(self, context, self.filepath, self.new_material)
res = loadSkinFile(
self, context, self.filepath, self.new_material, self.swap_all_imgs)
if res != 0:
return {'CANCELLED'}

Expand All @@ -391,6 +411,10 @@ class MCPREP_OT_apply_skin(bpy.types.Operator):
name="New Material",
description="Create a new material instead of overwriting existing one",
default=True)
swap_all_imgs: bpy.props.BoolProperty(
name="Swap All Images",
description=swap_all_imgs_desc,
default=True)
skipUsage: bpy.props.BoolProperty(
default=False,
options={'HIDDEN'})
Expand All @@ -399,7 +423,8 @@ class MCPREP_OT_apply_skin(bpy.types.Operator):
track_param = "ui list"
@tracking.report_error
def execute(self, context):
res = loadSkinFile(self, context, self.filepath, self.new_material)
res = loadSkinFile(
self, context, self.filepath, self.new_material, self.swap_all_imgs)
if res != 0:
return {'CANCELLED'}

Expand Down Expand Up @@ -431,6 +456,10 @@ class MCPREP_OT_apply_username_skin(bpy.types.Operator):
"If an older skin layout (pre Minecraft 1.8) is detected, convert "
"to new format (with clothing layers)"),
default=True)
swap_all_imgs: bpy.props.BoolProperty(
name="Use Legacy Skin Swap Behavior",
description=swap_all_imgs_desc,
default=False)
skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'})

def invoke(self, context, event):
Expand Down Expand Up @@ -463,15 +492,17 @@ def execute(self, context):
return {'CANCELLED'}

# Now load the skin
res = loadSkinFile(self, context, saveloc, self.new_material)
res = loadSkinFile(
self, context, saveloc, self.new_material, self.swap_all_imgs)
if res != 0:
return {'CANCELLED'}
bpy.ops.mcprep.reload_skins()
return {'FINISHED'}
else:
env.log("Reusing downloaded skin")
ind = skins.index(user_ref)
res = loadSkinFile(self, context, paths[ind], self.new_material)
res = loadSkinFile(
self, context, paths[ind], self.new_material, self.swap_all_imgs)
if res != 0:
return {'CANCELLED'}
return {'FINISHED'}
Expand Down