From 7d35b1f784088a2a505bdc800a16569a0aea917b Mon Sep 17 00:00:00 2001 From: "0znightlord0@gmail.com" <0znightlord0@gmail.com> Date: Sun, 30 Jul 2023 14:27:47 +0700 Subject: [PATCH 1/6] Layout things --- MCprep_addon/materials/prep.py | 29 ++++++++++++++++++++--------- MCprep_addon/spawner/spawn_util.py | 9 +++++++++ MCprep_addon/util.py | 26 ++++++++++++++++++++++++-- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/MCprep_addon/materials/prep.py b/MCprep_addon/materials/prep.py index 8434aad6..e87b5003 100644 --- a/MCprep_addon/materials/prep.py +++ b/MCprep_addon/materials/prep.py @@ -207,6 +207,7 @@ def execute(self, context): engine = context.scene.render.engine count = 0 count_lib_skipped = 0 + count_no_prep = 0 for mat in mat_list: if not mat: @@ -214,6 +215,10 @@ def execute(self, context): "During prep, found null material:" + str(mat), vv_only=True) continue + elif util.is_no_prep(mat): + count_no_prep += 1 + continue + elif mat.library: count_lib_skipped += 1 continue @@ -293,20 +298,26 @@ def execute(self, context): if self.optimizeScene and engine == 'CYCLES': bpy.ops.mcprep.optimize_scene() + has_no_prep = count_no_prep > 0 + has_lib_skipped = count_lib_skipped > 0 + has_mat = count > 0 + + _info = {} + _info[f"modified {count}"] = has_mat + _info[f"skipped {count_lib_skipped} linked"] = has_lib_skipped + _info[f"founded {count_no_prep} no prep"] = has_no_prep + + mat_info = ",".join(k for k,v in _info.items() if v).capitalize() + if self.skipUsage is True: pass # Don't report if a meta-call. - elif count_lib_skipped > 0: - self.report( - {"INFO"}, - f"Modified {count} materials, skipped {count_lib_skipped} linked ones.") - elif count > 0: - self.report({"INFO"}, f"Modified {count} materials") + elif has_mat or has_lib_skipped or has_no_prep: + self.report({"INFO"}, mat_info) else: self.report( {"ERROR"}, - "Nothing modified, be sure you selected objects with existing materials!" - ) - + "Nothing modified, be sure you selected objects with existing materials!") + addon_prefs = util.get_user_preferences(context) self.track_param = context.scene.render.engine self.track_exporter = addon_prefs.MCprep_exporter_type diff --git a/MCprep_addon/spawner/spawn_util.py b/MCprep_addon/spawner/spawn_util.py index 50f27aa6..ebb9ec8b 100644 --- a/MCprep_addon/spawner/spawn_util.py +++ b/MCprep_addon/spawner/spawn_util.py @@ -479,6 +479,7 @@ def load_linked(self, context: Context, path: str, name: str) -> None: {'INFO'}, "This addon works better when the root bone's name is 'MAIN'") +# TODO: Add a way to check the version before load_append() def load_append(self, context: Context, path: Path, name: str) -> None: """Append an entire collection/group into this blend file and fix armature. @@ -637,6 +638,14 @@ def load_append(self, context: Context, path: Path, name: str) -> None: # add the original selection back for objs in sel: util.select_set(objs, True) + +def init_entity_prop(obj: bpy.types.Object, name: str, rig_version = (0,0,0)): + # Vanilla mobs name or Custom, useful for skinswap villager rig + rig_type = obj.get("MCPREP_RIGTYPE") + # This will set the current Blender version if not exist + rig_version = obj.get("MCPREP_RIGVERS") + if rig_type == None: + util.set_prop(obj, "MCPREP_RIGTYPE", name) # ----------------------------------------------------------------------------- # class definitions diff --git a/MCprep_addon/util.py b/MCprep_addon/util.py index 125d8c23..4a7456a5 100644 --- a/MCprep_addon/util.py +++ b/MCprep_addon/util.py @@ -17,7 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### from subprocess import Popen, PIPE -from typing import List, Optional, Union, Tuple +from typing import List, Optional, Union, Tuple, Any import enum import json import operator @@ -808,4 +808,26 @@ def move_assets_to_excluded_layer(context: Context, collections: List[Collection if grp.name not in initial_view_coll.collection.children: continue # not linked, likely a sub-group not added to scn spawner_exclude_vl.collection.children.link(grp) - initial_view_coll.collection.children.unlink(grp) \ No newline at end of file + initial_view_coll.collection.children.unlink(grp) + +def set_prop(id_block: ID, key: str, value: Any, **kwargs: Dict[str, Any]): + """Create or set the properties""" + id_block[key] = value + id_props = id_block.id_properties_ui(key) + id_props.update(**kwargs) + overrides = kwargs.get("overridable_library") + if overrides != None: + id_block.property_overridable_library_set(f'["{key}"]', overrides) + +def is_no_prep(mat: Material): + """Check is material has no prep properties + If no_prep is 1 returns True + not exist or 0 returns False + """ + return mat.get("MCPREP_NO_PREP", False) + +def get_entity_prop(obj, prop: Optional[str] = None): + if prop: + return obj.get(prop) + else: + return obj.items() \ No newline at end of file From 9ff1e4e764f9aac2da3817b26a39219c121bc818 Mon Sep 17 00:00:00 2001 From: "0znightlord0@gmail.com" <0znightlord0@gmail.com> Date: Sat, 2 Sep 2023 20:41:28 +0700 Subject: [PATCH 2/6] Add back the enum --- MCprep_addon/spawner/spawn_util.py | 47 ++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/MCprep_addon/spawner/spawn_util.py b/MCprep_addon/spawner/spawn_util.py index ebb9ec8b..8a2e9fbc 100644 --- a/MCprep_addon/spawner/spawn_util.py +++ b/MCprep_addon/spawner/spawn_util.py @@ -45,10 +45,57 @@ else: COLL_ICON = 'GROUP' +class ColorVariation(Enum): + WHITE = auto() + ORANGE = auto() + MAGENTA = auto() + LIGHTBLUE = auto() + YELLOW = auto() + LIME = auto() + PINK = auto() + GRAY = auto() + LIGHTGRAY = auto() + CYAN = auto() + PURPLE = auto() + BLUE = auto() + BROWN = auto() + GREEN = auto() + RED = auto() + BLACK = auto() + +class VillagerProfession(Enum): + """Preserve for villager""" + # def _generate_next_value_(name, start, count, last_values): + # return name + + FARMER = auto() + FISHERMAN = auto() + SHEPHERD = auto() + FLETCHER = auto() + LIBRARIAN = auto() + CARTOGRAPHER = auto() + CLERIC = auto() + ARMORER = auto() + WEAPONSMITH = auto() + TOOLSMITH = auto() + BUTCHER = auto() + LEATHERWORKER = auto() + MASON = auto() + NITWIT = auto() + +class ColorVariationProp(): + def color_items(self, context): + return [(i.value, i.name, i.name) for i in ColorVariation] + + color_variation = bpy.props.EnumProperty(name="Color Variation", items=color_items) + # ----------------------------------------------------------------------------- # Reusable functions for spawners # ----------------------------------------------------------------------------- +def has_color(name): + """Return True if has the color in name""" + return name in ["white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray", "light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black"] def filter_collections(data_from: BlendDataLibraries) -> List[str]: """ TODO 2.7 groups From ea6956cef3845add5f1207e286a8d5198ae53e4d Mon Sep 17 00:00:00 2001 From: "0znightlord0@gmail.com" <0znightlord0@gmail.com> Date: Mon, 11 Sep 2023 01:03:35 +0700 Subject: [PATCH 3/6] unf --- MCprep_addon/materials/skin.py | 117 +++++++++++++++++++++++++++++ MCprep_addon/spawner/mcmodel.py | 4 + MCprep_addon/spawner/spawn_util.py | 52 ++++++++----- 3 files changed, 155 insertions(+), 18 deletions(-) diff --git a/MCprep_addon/materials/skin.py b/MCprep_addon/materials/skin.py index 3ec99f59..37779177 100644 --- a/MCprep_addon/materials/skin.py +++ b/MCprep_addon/materials/skin.py @@ -761,6 +761,123 @@ def execute(self, context): self.report({"INFO"}, f"Downloaded {len(user_list)} skins") return {'FINISHED'} +class MCPREP_OT_load_material(bpy.types.Operator, McprepMaterialProps): + """Load the select material from the active resource pack and prep it""" + bl_idname = "mcprep.load_material" + bl_label = "Generate material" + bl_description = ( + "Generate and apply the selected material based on active resource pack") + bl_options = {'REGISTER', 'UNDO'} + + + skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) + + @classmethod + def poll(cls, context): + return context.object + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog( + self, width=300 * util.ui_scale()) + + def draw(self, context): + draw_mats_common(self, context) + + track_function = "" + track_param = None + @tracking.report_error + def execute(self, context): + + res = loadSkinFile(self, context, self.filepath, self.new_material) + if res != 0: + return {'CANCELLED'} + + mat_name = os.path.splitext(os.path.basename(self.filepath))[0] + if not os.path.isfile(self.filepath): + self.report({"ERROR"}, ( + "File not found! Reset the resource pack under advanced " + "settings (return arrow icon) and press reload materials")) + return {'CANCELLED'} + mat, err = self.generate_base_material( + context, mat_name, self.filepath) + if mat is None and err: + self.report({"ERROR"}, err) + return {'CANCELLED'} + elif mat is None: + self.report({"ERROR"}, "Failed generate base material") + return {'CANCELLED'} + + if not context.object.material_slots: + context.object.data.materials.append(mat) # Auto-creates slot. + else: + mat_ind = context.object.active_material_index + context.object.material_slots[mat_ind].material = mat + # Using the above in place of below due to some errors of: + # "attribute "active_material" from "Object" is read-only" + # context.object.active_material = mat + + # Don't want to generally run prep, as this would affect everything + # selected, as opposed to affecting just the new material + # bpy.ops.mcprep.prep_materials() + # Instead, run the update steps below copied from the MCprep inner loop + res, err = self.update_material(context, mat) + if res is False and err: + self.report({"ERROR"}, err) + return {'CANCELLED'} + elif res is False: + self.report({"ERROR"}, "Failed to prep generated material") + return {'CANCELLED'} + + self.track_param = context.scene.render.engine + return {'FINISHED'} + + def update_material(self, context, mat): + """Update the initially created material""" + if not mat: + env.log(f"During prep, found null material: {mat}", vv_only=True) + return + elif mat.library: + return + + engine = context.scene.render.engine + passes = generate.get_textures(mat) + env.log(f"Load Mat Passes:{passes}", vv_only=True) + if not self.useExtraMaps: + for pass_name in passes: + if pass_name != "diffuse": + passes[pass_name] = None + + if self.autoFindMissingTextures: + for pass_name in passes: + res = generate.replace_missing_texture(passes[pass_name]) + if res > 0: + mat["texture_swapped"] = True # used to apply saturation + + if engine == 'CYCLES' or engine == 'BLENDER_EEVEE': + options = generate.PrepOptions( + passes, + self.useReflections, + self.usePrincipledShader, + self.makeSolid, + self.packFormat, + self.useEmission, + False # This is for an option set in matprep_cycles + ) + res = generate.matprep_cycles( + mat=mat, + options=options + ) + else: + return False, "Only Cycles and Eevee supported" + + success = res == 0 + + if self.animateTextures: + sequences.animate_single_material( + mat, context.scene.render.engine) + + return success, None + # ----------------------------------------------------------------------------- # Registration diff --git a/MCprep_addon/spawner/mcmodel.py b/MCprep_addon/spawner/mcmodel.py index c5d90a93..407a1be1 100644 --- a/MCprep_addon/spawner/mcmodel.py +++ b/MCprep_addon/spawner/mcmodel.py @@ -336,6 +336,10 @@ def add_model(model_filepath: Path, obj_name: str="MinecraftModel") -> bpy.types # make the bmesh the object's mesh bm.to_mesh(mesh) bm.free() + + # + if has_color(obj.name): + return obj diff --git a/MCprep_addon/spawner/spawn_util.py b/MCprep_addon/spawner/spawn_util.py index 8a2e9fbc..6b6afc12 100644 --- a/MCprep_addon/spawner/spawn_util.py +++ b/MCprep_addon/spawner/spawn_util.py @@ -46,22 +46,22 @@ COLL_ICON = 'GROUP' class ColorVariation(Enum): - WHITE = auto() - ORANGE = auto() - MAGENTA = auto() - LIGHTBLUE = auto() - YELLOW = auto() - LIME = auto() - PINK = auto() - GRAY = auto() - LIGHTGRAY = auto() - CYAN = auto() - PURPLE = auto() - BLUE = auto() - BROWN = auto() - GREEN = auto() - RED = auto() - BLACK = auto() + WHITE = 0 + ORANGE = 1 + MAGENTA = 2 + LIGHTBLUE = 3 + YELLOW = 4 + LIME = 5 + PINK = 6 + GRAY = 7 + LIGHTGRAY = 8 + CYAN = 9 + PURPLE = 10 + BLUE = 11 + BROWN = 12 + GREEN = 13 + RED = 14 + BLACK = 15 class VillagerProfession(Enum): """Preserve for villager""" @@ -83,15 +83,31 @@ class VillagerProfession(Enum): MASON = auto() NITWIT = auto() -class ColorVariationProp(): +class VariationProp(): def color_items(self, context): return [(i.value, i.name, i.name) for i in ColorVariation] - color_variation = bpy.props.EnumProperty(name="Color Variation", items=color_items) + def profession_items(self, context): + return [(i.value, i.name, i.name) for i in ColorVariation] + + def level_items(self, context): + return [(i.value, i.name, i.name) for i in ColorVariation] + + color_variation : bpy.props.EnumProperty(name="Color Variation", items=color_items) + # Villagers + profession_variation : bpy.props.EnumProperty(name="Villager Profession", items=profession_items) + level_variation : bpy.props.EnumProperty(name="Villager Level", items=level_items) + region_variation : bpy.props.EnumProperty(name="Villager Region"= items=region_items) + undead_variation : bpy.props.EnumProperty(name="Undead Variation") # ----------------------------------------------------------------------------- # Reusable functions for spawners # ----------------------------------------------------------------------------- +def getmob_type(): + return "foo" + +def add_prop(datablock, prop, prop_value): + datablock[prop] = prop_value def has_color(name): """Return True if has the color in name""" From 8f500da218a17b0307fb9edf89baf9b135b6b90a Mon Sep 17 00:00:00 2001 From: "0znightlord0@gmail.com" <0znightlord0@gmail.com> Date: Thu, 12 Oct 2023 21:45:06 +0700 Subject: [PATCH 4/6] some extra code --- MCprep_addon/materials/skin.py | 18 +++++ MCprep_addon/mcprep_ui.py | 14 +++- MCprep_addon/spawner/spawn_util.py | 121 +++++++++++++++++++++-------- MCprep_addon/util.py | 15 ++-- 4 files changed, 130 insertions(+), 38 deletions(-) diff --git a/MCprep_addon/materials/skin.py b/MCprep_addon/materials/skin.py index 37779177..61718639 100644 --- a/MCprep_addon/materials/skin.py +++ b/MCprep_addon/materials/skin.py @@ -686,6 +686,24 @@ def execute(self, context): return {'FINISHED'} +class MCPREP_OT_swap_skin_variant(bpy.types.Operator): + """Apply the active UIlist skin to select characters""" + bl_idname = "mcprep.swap_skin variant" + bl_label = "Apply skin" + bl_description = "Swap the mobs variant" + bl_options = {'REGISTER', 'UNDO'} + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog( + self, width=300 * util.ui_scale()) + + def draw(self, context: Context): + obj = context.obj + + def check_villager_case(self, materials): + for mat in materials: + + class MCPREP_OT_download_username_list(bpy.types.Operator): """Apply the active UIlist skin to select characters""" diff --git a/MCprep_addon/mcprep_ui.py b/MCprep_addon/mcprep_ui.py index f906bef5..958eeb76 100644 --- a/MCprep_addon/mcprep_ui.py +++ b/MCprep_addon/mcprep_ui.py @@ -1983,7 +1983,18 @@ class McprepProps(bpy.types.PropertyGroup): effects_list: bpy.props.CollectionProperty(type=spawn_util.ListEffectsAssets) effects_list_index: bpy.props.IntProperty(default=0) - +class MCprepWindowManager(PropertyGroup): + + + @classmethod + def register(cls): + bpy.types.WindowManager.mcprep = bpy.props.PointerProperty(type=cls) + + @classmethod + def unregister(cls): + del bpy.types.WindowManager.mcprep + + # ----------------------------------------------------------------------------- # Register functions # ----------------------------------------------------------------------------- @@ -1992,6 +2003,7 @@ class McprepProps(bpy.types.PropertyGroup): classes = ( McprepPreference, McprepProps, + MCprepWindowManager, MCPREP_MT_mob_spawner, MCPREP_MT_meshswap_place, MCPREP_MT_item_spawn, diff --git a/MCprep_addon/spawner/spawn_util.py b/MCprep_addon/spawner/spawn_util.py index 6b6afc12..7359ad15 100644 --- a/MCprep_addon/spawner/spawn_util.py +++ b/MCprep_addon/spawner/spawn_util.py @@ -65,49 +65,103 @@ class ColorVariation(Enum): class VillagerProfession(Enum): """Preserve for villager""" - # def _generate_next_value_(name, start, count, last_values): - # return name + ARMORER = 0 + BUTCHER = 1 + CARTOGRAPHER = 2 + CLERIC = 3 + FARMER = 4 + FISHERMAN = 5 + FLETCHER = 6 + LEATHERWORKER = 7 + LIBRARIAN = 8 + MASON = 9 + NITWIT = 10 + SHEPHERD = 11 + TOOLSMITH = 12 + WEAPONSMITH = 13 + WANDER = 14 # Wandering Trader is not a villager - FARMER = auto() - FISHERMAN = auto() - SHEPHERD = auto() - FLETCHER = auto() - LIBRARIAN = auto() - CARTOGRAPHER = auto() - CLERIC = auto() - ARMORER = auto() - WEAPONSMITH = auto() - TOOLSMITH = auto() - BUTCHER = auto() - LEATHERWORKER = auto() - MASON = auto() - NITWIT = auto() +class VillagerBiome(Enum): + PLAINS = 0 # Favour plains as default then alphabet + DESERT = 1 + JUNGLE = 2 + SAVANNA = 3 + SNOWY = 4 + Swamp = 5 + Taiga = 6 -class VariationProp(): - def color_items(self, context): - return [(i.value, i.name, i.name) for i in ColorVariation] +class VillagerLevel(Enum): + NOVICE = 0 # Stone + APPRENTICE = 1 # Iron + JOURNEYMAN = 2 # Gold + EXPERT = 3 # Emerald + MASTER = 4 # Diamond + +class ZombieVariation(Enum): + DEFAULT = 0 + HUSK = 1 + DROWN = 2 + +class SkeletonVariation(Enum): + DEFAULT = 0 + WITHER = 1 - def profession_items(self, context): - return [(i.value, i.name, i.name) for i in ColorVariation] - def level_items(self, context): +class VariationProp: + def color_items(self): + """Color variation""" return [(i.value, i.name, i.name) for i in ColorVariation] - color_variation : bpy.props.EnumProperty(name="Color Variation", items=color_items) + def profession_items(self): + """Villager Professional""" + return [(i.value, i.name, i.name) for i in VillagerProfession] + + def level_items(self): + """Villager Level method""" + return [(i.value, i.name, i.name) for i in VillagerLevel] + + def biome_items(self): + return [(i.value, i.name, i.name) for i in VillagerBiome] + + def zombie_items(self): + return [(i.value, i.name, i.name) for i in VillagerBiome] + + def skeleton_items(self): + return [(i.value, i.name, i.name) for i in SkeletonVariation] + + + color_variation: bpy.props.EnumProperty(name="Color Variation", items=color_items) # Villagers - profession_variation : bpy.props.EnumProperty(name="Villager Profession", items=profession_items) - level_variation : bpy.props.EnumProperty(name="Villager Level", items=level_items) - region_variation : bpy.props.EnumProperty(name="Villager Region"= items=region_items) - undead_variation : bpy.props.EnumProperty(name="Undead Variation") + profession_variation: bpy.props.EnumProperty(name="Villager Profession", items=profession_items) + level_variation: bpy.props.EnumProperty(name="Villager Level", items=level_items) + biome_variation: bpy.props.EnumProperty(name="Villager Biome", items=biome_items) + zombie_variation: bpy.props.EnumProperty(name="Zombie Variation", items=zombie_items) + skeleton_variation: bpy.props.EnumProperty(name="Skeleton Variation", items=skeleton_items) + is_zombiefied: BoolProperty(name="Is Zombiefied") + + def draw_ui(self, context: Context, layout: UILayout): + obj = context.object + mob_type = getmob_type(obj) + if mob_type in ["Villager", "Trader"]: + layout.prop(self, "profession_variation") + layout.prop(self, "level_variation") + layout.prop(self, "biome_variation") + + elif mob_type == "Zombie": + layout.prop(self, "zombie_variation") + elif mob_type == "Skeleton": + layout.prop(self, "skeleton_variation") + + if mob_type in ["Villager", "Piglin", "Pigman", "Hoglin", "Allay"] + text = "Is Vex" if mob_type == "Allay" else "Is Zombified" + layout.prop(self, "is_zombiefied", text=text) # ----------------------------------------------------------------------------- # Reusable functions for spawners # ----------------------------------------------------------------------------- -def getmob_type(): - return "foo" +def getmob_type(obj: Object): + return obj.get("MCPREP_RIGTYPE") -def add_prop(datablock, prop, prop_value): - datablock[prop] = prop_value def has_color(name): """Return True if has the color in name""" @@ -703,12 +757,17 @@ def load_append(self, context: Context, path: Path, name: str) -> None: util.select_set(objs, True) def init_entity_prop(obj: bpy.types.Object, name: str, rig_version = (0,0,0)): + """ An utility function for adding attribute to object, armature object rigs, collection""" # Vanilla mobs name or Custom, useful for skinswap villager rig rig_type = obj.get("MCPREP_RIGTYPE") # This will set the current Blender version if not exist rig_version = obj.get("MCPREP_RIGVERS") if rig_type == None: util.set_prop(obj, "MCPREP_RIGTYPE", name) + if rig_version == None: + util.set_prop(obj, "MCPREP_RIGVERS", rig_version) + + # ----------------------------------------------------------------------------- # class definitions diff --git a/MCprep_addon/util.py b/MCprep_addon/util.py index 4a7456a5..dbafd636 100644 --- a/MCprep_addon/util.py +++ b/MCprep_addon/util.py @@ -811,13 +811,16 @@ def move_assets_to_excluded_layer(context: Context, collections: List[Collection initial_view_coll.collection.children.unlink(grp) def set_prop(id_block: ID, key: str, value: Any, **kwargs: Dict[str, Any]): - """Create or set the properties""" + """Create or set the properties + 3.0 got more functionalities + """ id_block[key] = value - id_props = id_block.id_properties_ui(key) - id_props.update(**kwargs) - overrides = kwargs.get("overridable_library") - if overrides != None: - id_block.property_overridable_library_set(f'["{key}"]', overrides) + if bv30(): + id_props = id_block.id_properties_ui(key) + id_props.update(**kwargs) + overrides = kwargs.get("overridable_library", True) + if overrides != None: + id_block.property_overridable_library_set(f'["{key}"]', overrides) def is_no_prep(mat: Material): """Check is material has no prep properties From e3f3cfe68edee5bc533005c0651bb88a80b87715 Mon Sep 17 00:00:00 2001 From: "0znightlord0@gmail.com" <0znightlord0@gmail.com> Date: Thu, 25 Jan 2024 13:54:28 +0700 Subject: [PATCH 5/6] swap mob variant --- MCprep_addon/materials/skin.py | 76 +++++++++++++- MCprep_addon/spawner/spawn_util.py | 157 +++++++++++++++-------------- 2 files changed, 150 insertions(+), 83 deletions(-) diff --git a/MCprep_addon/materials/skin.py b/MCprep_addon/materials/skin.py index 61718639..9bb70b00 100644 --- a/MCprep_addon/materials/skin.py +++ b/MCprep_addon/materials/skin.py @@ -31,6 +31,7 @@ from . import generate from .. import tracking from .. import util +from .spawner import spawn_util from ..conf import env @@ -137,6 +138,40 @@ def loadSkinFile(self, context: Context, filepath: Path, new_material: bool=Fals return 0 return 0 +def loadVariantFile(self, context: Context, filepath: Path, new_material: bool=False): + if not os.path.isfile(filepath): + self.report({'ERROR'}, "Image file not found") + return 1 + # special message for library linking? + + # always create a new image block, even if the name already existed + image = util.loadTexture(filepath) + + if image.channels == 0: + self.report({'ERROR'}, "Failed to properly load image") + return 1 + + mats, skipped = getMatsFromSelected(context.selected_objects, new_material) + if not mats: + self.report({'ERROR'}, "No materials found to update") + # special message for library linking? + return 1 + if skipped > 0: + self.report( + {'WARNING'}, "Skinswap skipped {} linked objects".format(skipped)) + + status = generate.assert_textures_on_materials(image, mats) + if status is False: + self.report({'ERROR'}, "No image textures found to update") + return 1 + else: + pass + + if not util.bv28(): + setUVimage(context.selected_objects, image) + + return 0 + def convert_skin_layout(image_file: Path) -> bool: """Convert skin to 1.8+ layout if old format detected @@ -686,7 +721,7 @@ def execute(self, context): return {'FINISHED'} -class MCPREP_OT_swap_skin_variant(bpy.types.Operator): +class MCPREP_OT_swap_skin_variant(bpy.types.Operator, spawn_util.VariationProp): """Apply the active UIlist skin to select characters""" bl_idname = "mcprep.swap_skin variant" bl_label = "Apply skin" @@ -698,12 +733,43 @@ def invoke(self, context, event): self, width=300 * util.ui_scale()) def draw(self, context: Context): - obj = context.obj + layout = self.layout + self.draw_ui(context, layout) - def check_villager_case(self, materials): - for mat in materials: - + def doTextureSwap(self, context: Context): + obj = context.object + mats, skipped = getMatsFromSelected(obj, False) + mobtype = obj.get("MCPREP_MOBTYPE", "CUSTOM") + if mobtype == "Villager": + doVillager(mats) + elif mobtype == "Zombie": + pass + elif mobtype == "Skeleton": + pass + elif mobtype == "Pigman": + pass + elif mobtype == "Hoglin": + pass + elif mobtype in ("Allay", "Vex"): + pass + else: + pass + def doVillager(self, materials: List[Material]): + for mat in materials: + if mat.get("MCPREP_VILLAGER_PROFESSION"): + image = util.loadTexture(filepath) + proStat = generate.assert_textures_on_materials(image, mats) + if mat.get("MCPREP_VILLAGER_BIOME"): + image = util.loadTexture(filepath) + biomeStat = generate.assert_textures_on_materials(image, mats) + if mat.get("MCPREP_VILLAGER_LEVEL"): + image = util.loadTexture(filepath) + levelStat = generate.assert_textures_on_materials(image, mats) + if not all([proStat, biomeStat, levelStat]): + self.report({'ERROR'}, "Something wrong happen during swap variant texture") + else: + pass class MCPREP_OT_download_username_list(bpy.types.Operator): """Apply the active UIlist skin to select characters""" diff --git a/MCprep_addon/spawner/spawn_util.py b/MCprep_addon/spawner/spawn_util.py index 7359ad15..4c483daa 100644 --- a/MCprep_addon/spawner/spawn_util.py +++ b/MCprep_addon/spawner/spawn_util.py @@ -45,90 +45,88 @@ else: COLL_ICON = 'GROUP' -class ColorVariation(Enum): - WHITE = 0 - ORANGE = 1 - MAGENTA = 2 - LIGHTBLUE = 3 - YELLOW = 4 - LIME = 5 - PINK = 6 - GRAY = 7 - LIGHTGRAY = 8 - CYAN = 9 - PURPLE = 10 - BLUE = 11 - BROWN = 12 - GREEN = 13 - RED = 14 - BLACK = 15 - -class VillagerProfession(Enum): - """Preserve for villager""" - ARMORER = 0 - BUTCHER = 1 - CARTOGRAPHER = 2 - CLERIC = 3 - FARMER = 4 - FISHERMAN = 5 - FLETCHER = 6 - LEATHERWORKER = 7 - LIBRARIAN = 8 - MASON = 9 - NITWIT = 10 - SHEPHERD = 11 - TOOLSMITH = 12 - WEAPONSMITH = 13 - WANDER = 14 # Wandering Trader is not a villager - -class VillagerBiome(Enum): - PLAINS = 0 # Favour plains as default then alphabet - DESERT = 1 - JUNGLE = 2 - SAVANNA = 3 - SNOWY = 4 - Swamp = 5 - Taiga = 6 - -class VillagerLevel(Enum): - NOVICE = 0 # Stone - APPRENTICE = 1 # Iron - JOURNEYMAN = 2 # Gold - EXPERT = 3 # Emerald - MASTER = 4 # Diamond - -class ZombieVariation(Enum): - DEFAULT = 0 - HUSK = 1 - DROWN = 2 - -class SkeletonVariation(Enum): - DEFAULT = 0 - WITHER = 1 - - class VariationProp: def color_items(self): - """Color variation""" - return [(i.value, i.name, i.name) for i in ColorVariation] + """Color variation in ID order""" + items = [ + ('WHITE', "White", ""), + ('ORANGE', "Orange", ""), + ('MAGENTA', "Magenta", ""), + ('LIGHTBLUE', "Light Blue", ""), + ('YELLOW', "Yellow", ""), + ('LIME', "Lime", ""), + ('PINK', "Pink", ""), + ('GRAY', "Gray", ""), + ('LIGHTGRAY', "Light Gray", ""), + ('CYAN', "Cyan", ""), + ('PURPLE', "Purple", ""), + ('BLUE', "Blue", ""), + ('BROWN', "Brown", ""), + ('GREEN', "Green", ""), + ('RED', "Red", ""), + ('BLACK', "Black", ""), + ] + return items def profession_items(self): """Villager Professional""" - return [(i.value, i.name, i.name) for i in VillagerProfession] + items = [ + ('ARMORER', "Armorer", ""), + ('BUTCHER', "Butcher", ""), + ('CARTOGRAPHER', "Cartographer", ""), + ('CLERIC', "Cleric", ""), + ('FARMER', "Farmer", ""), + ('FISHERMAN', "Fisherman", ""), + ('FLETCHER', "FLETCHER", ""), + ('LEATHERWORKER', "Leatherworker", ""), + ('LIBRARIAN', "Librarian", ""), + ('MASON', "Mason", ""), + ('NITWIT', "Nitwit", ""), + ('SHEPHERD', "Shepherd", ""), + ('TOOLSMITH', "Toolsmith", ""), + ('WEAPONSMITH', "Weaponsmith", ""), + ('WANDER', "Wandering", ""), # Wandering Trader is not a villager but leave it there, illagers could be in the list too (witch?) + ] + return items def level_items(self): - """Villager Level method""" - return [(i.value, i.name, i.name) for i in VillagerLevel] + """Villager Level """ + items = [ + ('NOVICE', "NOVICE", ""), # Stone + ('APPRENTICE', "Apprentice", ""), # Iron + ('JOURNEYMAN', "Journeyman", ""), # Gold + ('EXPERT', "Expert", ""), # Emerald + ('MASTER', "Master", ""), # Diamond + ] + return items def biome_items(self): - return [(i.value, i.name, i.name) for i in VillagerBiome] - + items = [ + ('DESERT', "Desert", ""), + ('JUNGLE', "Jungle", ""), + ('PLAINS', "Plains", ""), + ('SAVANNA', "Savanna", ""), + ('SNOWY', "Snowy", ""), + ('SWAMP', "Swamp", ""), + ('TAIGA', "Taiga", ""), + ] + return items + def zombie_items(self): - return [(i.value, i.name, i.name) for i in VillagerBiome] - + items = [ + ('DEFAULT', "Default", ""), + ('DROWN', "Drown", "") + ('HUSK', "Husk", ""), + ] + return items + def skeleton_items(self): - return [(i.value, i.name, i.name) for i in SkeletonVariation] - + items = [ + ('DEFAULT', "Default", ""), + ('STRAY', "Stray", ""), + ('WITHER', "Wither", ""), + ] + return items color_variation: bpy.props.EnumProperty(name="Color Variation", items=color_items) # Villagers @@ -137,7 +135,7 @@ def skeleton_items(self): biome_variation: bpy.props.EnumProperty(name="Villager Biome", items=biome_items) zombie_variation: bpy.props.EnumProperty(name="Zombie Variation", items=zombie_items) skeleton_variation: bpy.props.EnumProperty(name="Skeleton Variation", items=skeleton_items) - is_zombiefied: BoolProperty(name="Is Zombiefied") + is_zombiefied: BoolProperty(name="Is Zombiefied") # Use this for Allay-Vex def draw_ui(self, context: Context, layout: UILayout): obj = context.object @@ -152,16 +150,19 @@ def draw_ui(self, context: Context, layout: UILayout): elif mob_type == "Skeleton": layout.prop(self, "skeleton_variation") - if mob_type in ["Villager", "Piglin", "Pigman", "Hoglin", "Allay"] + if mob_type in ["Villager", "Piglin", "Pigman", "Hoglin", "Allay", "Vex"] text = "Is Vex" if mob_type == "Allay" else "Is Zombified" layout.prop(self, "is_zombiefied", text=text) # ----------------------------------------------------------------------------- # Reusable functions for spawners # ----------------------------------------------------------------------------- -def getmob_type(obj: Object): - return obj.get("MCPREP_RIGTYPE") - +def getmob_type(obj: bpy.types.Object): + """ Get mob type from rig + args + obj: Armature Object + """ + return obj.get("MCPREP_RIGTYPE", "Custom") def has_color(name): """Return True if has the color in name""" From 75d9801074dc435d661400a8d037601049a09103 Mon Sep 17 00:00:00 2001 From: Trung <0znightlord0@gmail.com> Date: Mon, 15 Apr 2024 08:56:38 +0700 Subject: [PATCH 6/6] change --- MCprep_addon/materials/skin.py | 305 ++++++++++++----------------- MCprep_addon/mcprep_ui.py | 113 ++++++----- MCprep_addon/spawner/entities.py | 5 +- MCprep_addon/spawner/mcmodel.py | 4 +- MCprep_addon/spawner/spawn_util.py | 270 +++++++++++++++++++------ MCprep_addon/util.py | 16 +- 6 files changed, 417 insertions(+), 296 deletions(-) diff --git a/MCprep_addon/materials/skin.py b/MCprep_addon/materials/skin.py index 9bb70b00..6c52cfb9 100644 --- a/MCprep_addon/materials/skin.py +++ b/MCprep_addon/materials/skin.py @@ -26,14 +26,22 @@ 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 .spawner import spawn_util +from ..spawner import spawn_util from ..conf import env +from .prep import McprepMaterialProps + + +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 @@ -85,7 +93,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) @@ -96,13 +104,20 @@ 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'}, "Image file not found") + self.report({'ERROR'}, f"Image file not found: {filepath}") return 1 # special message for library linking? @@ -122,22 +137,21 @@ 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 else: pass - if not util.bv28(): - setUVimage(context.selected_objects, image) - # TODO: adjust the UVs if appropriate, and fix eyes if image.size[0] != 0 and image.size[1] / image.size[0] != 1: self.report({'INFO'}, "Skin swapper works best on 1.8 skins") return 0 return 0 + def loadVariantFile(self, context: Context, filepath: Path, new_material: bool=False): if not os.path.isfile(filepath): self.report({'ERROR'}, "Image file not found") @@ -317,20 +331,6 @@ def getMatsFromSelected(selected: List[bpy.types.Object], new_material: bool=Fal return mat_ret, linked_objs -def setUVimage(objs: List[bpy.types.Object], image: Image) -> None: - """Set image for each face for viewport displaying (2.7 only)""" - for obj in objs: - if obj.type != "MESH": - continue - if not hasattr(obj.data, "uv_textures"): - env.log("Called setUVimage on object with no uv_textures, 2.8?") - return - if obj.data.uv_textures.active is None: - continue - for uv_face in obj.data.uv_textures.active.data: - uv_face.image = image - - def download_user(self, context: Context, username: str) -> Optional[Path]: """Download user skin from online. @@ -372,7 +372,29 @@ def download_user(self, context: Context, username: str) -> Optional[Path]: self.track_param = "username" return saveloc +def check_entity_texture(texture_path: str) -> Optional[Path]: + context = bpy.context + addon_prefs = util.get_user_preferences(context) + active_pack = bpy.path.abspath(context.scene.mcprep_texturepack_path) + active_pack = os.path.join( + active_pack, "assets", "minecraft", "textures", "entity") + + base_pack = bpy.path.abspath(addon_prefs.custom_texturepack_path) + base_pack = os.path.join( + base_pack, "assets", "minecraft", "textures", "entity") + + # if not os.path.isdir(active_pack): + # env.log(f"No models found for active path {active_pack}") + # return None + base_has_textures = os.path.isdir(base_pack) + + if base_has_textures: + return generate.find_from_texturepack(texture_path) + else: + env.log(f"Base resource pack has no entity texture folder: {base_pack}") + return None + # ----------------------------------------------------------------------------- # Operators / UI classes # ----------------------------------------------------------------------------- @@ -415,19 +437,23 @@ 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'} return {'FINISHED'} - class MCPREP_OT_apply_skin(bpy.types.Operator): """Apply the active UIlist skin to select characters""" bl_idname = "mcprep.applyskin" @@ -443,6 +469,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'}) @@ -451,7 +481,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'} @@ -483,6 +514,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): @@ -504,24 +539,28 @@ def execute(self, context): self.report({"ERROR"}, "Invalid username") return {'CANCELLED'} + user_ref = self.username.lower() + ".png" + skins = [str(skin[0]).lower() for skin in env.skin_list] paths = [skin[1] for skin in env.skin_list] - if self.username.lower() not in skins or not self.skip_redownload: + if user_ref not in skins or not self.skip_redownload: # Do the download saveloc = download_user(self, context, self.username) if not saveloc: 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(self.username.lower()) - res = loadSkinFile(self, context, paths[ind][1], self.new_material) + ind = skins.index(user_ref) + res = loadSkinFile( + self, context, paths[ind], self.new_material, self.swap_all_imgs) if res != 0: return {'CANCELLED'} return {'FINISHED'} @@ -614,7 +653,7 @@ def draw(self, context): skin_path = env.skin_list[context.scene.mcprep_skins_list_index] col = self.layout.column() col.scale_y = 0.7 - col.label(text= f"Warning, will delete file {os.path.basename(skin_path[0])} from") + col.label(text=f"Warning, will delete file {os.path.basename(skin_path[0])} from") col.label(text=os.path.dirname(skin_path[-1])) @tracking.report_error @@ -721,51 +760,73 @@ def execute(self, context): return {'FINISHED'} + class MCPREP_OT_swap_skin_variant(bpy.types.Operator, spawn_util.VariationProp): """Apply the active UIlist skin to select characters""" - bl_idname = "mcprep.swap_skin variant" - bl_label = "Apply skin" + bl_idname = "mcprep.swap_skin" + bl_label = "Swap mob skin" bl_description = "Swap the mobs variant" bl_options = {'REGISTER', 'UNDO'} - def invoke(self, context, event): - return context.window_manager.invoke_props_dialog( - self, width=300 * util.ui_scale()) - - def draw(self, context: Context): - layout = self.layout - self.draw_ui(context, layout) + # def invoke(self, context, event): + # return context.window_manager.invoke_props_dialog( + # self, width=300 * util.ui_scale()) + + # def draw(self, context: Context): + # layout = self.layout + # self.draw_variation_ui(context, layout) - def doTextureSwap(self, context: Context): + @tracking.report_error + def execute(self, context: Context): obj = context.object + if obj.type != 'ARMATURE': + self.report({'ERROR'}, "Please select an armature") + return {'CANCELLED'} mats, skipped = getMatsFromSelected(obj, False) - mobtype = obj.get("MCPREP_MOBTYPE", "CUSTOM") - if mobtype == "Villager": - doVillager(mats) - elif mobtype == "Zombie": - pass - elif mobtype == "Skeleton": - pass - elif mobtype == "Pigman": - pass - elif mobtype == "Hoglin": - pass - elif mobtype in ("Allay", "Vex"): + mob_type = obj.get("MCPREP_mob_type", "Custom") + + if mob_type == "Custom": + self.report({'ERROR'}, "You are not allowed to use on Custom rig") + return {'CANCELLED'} + + texture_paths = self.get_texture_paths(mob_type) + name = mob_type.lower().replace(" ", "_") + if mob_type == "Villager": + self.doVillager(mats, texture_paths) + elif mob_type == "Zombie": + check_entity_texture(context) + if self.zombie_variation in ["ZOMBIE", "HUSK"]: + # Using Player model so convert player skin to 1.8 format + # loadSkinFile() + pass + else: + # Assign textures materials for drown + pass + elif mob_type == "Skeleton": pass + + elif mob_type in ("Allay", "Vex"): + if mob_type == "Allay": + name += "/" + name + else: + name = "illager" + else: pass + return {'FINISHED'} - def doVillager(self, materials: List[Material]): - for mat in materials: - if mat.get("MCPREP_VILLAGER_PROFESSION"): - image = util.loadTexture(filepath) - proStat = generate.assert_textures_on_materials(image, mats) + def doVillager(self, materials: List[Material], texture_paths: List[str]): + """ materials texture path order follows by profession, profession level, type """ + prof_mat, biome_mat, level_mat = None, None, None + if mat[0].get("MCPREP_VILLAGER_PROFESSION"): + image = util.loadTexture(texture_paths[0]) + proStat = generate.assert_textures_on_materials(image, [mat]) if mat.get("MCPREP_VILLAGER_BIOME"): - image = util.loadTexture(filepath) - biomeStat = generate.assert_textures_on_materials(image, mats) + image = util.loadTexture(texture_paths[1]) + biomeStat = generate.assert_textures_on_materials(image, [mat]) if mat.get("MCPREP_VILLAGER_LEVEL"): - image = util.loadTexture(filepath) - levelStat = generate.assert_textures_on_materials(image, mats) + image = util.loadTexture(texture_paths[2]) + levelStat = generate.assert_textures_on_materials(image, [mat]) if not all([proStat, biomeStat, levelStat]): self.report({'ERROR'}, "Something wrong happen during swap variant texture") else: @@ -845,123 +906,6 @@ def execute(self, context): self.report({"INFO"}, f"Downloaded {len(user_list)} skins") return {'FINISHED'} -class MCPREP_OT_load_material(bpy.types.Operator, McprepMaterialProps): - """Load the select material from the active resource pack and prep it""" - bl_idname = "mcprep.load_material" - bl_label = "Generate material" - bl_description = ( - "Generate and apply the selected material based on active resource pack") - bl_options = {'REGISTER', 'UNDO'} - - - skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) - - @classmethod - def poll(cls, context): - return context.object - - def invoke(self, context, event): - return context.window_manager.invoke_props_dialog( - self, width=300 * util.ui_scale()) - - def draw(self, context): - draw_mats_common(self, context) - - track_function = "" - track_param = None - @tracking.report_error - def execute(self, context): - - res = loadSkinFile(self, context, self.filepath, self.new_material) - if res != 0: - return {'CANCELLED'} - - mat_name = os.path.splitext(os.path.basename(self.filepath))[0] - if not os.path.isfile(self.filepath): - self.report({"ERROR"}, ( - "File not found! Reset the resource pack under advanced " - "settings (return arrow icon) and press reload materials")) - return {'CANCELLED'} - mat, err = self.generate_base_material( - context, mat_name, self.filepath) - if mat is None and err: - self.report({"ERROR"}, err) - return {'CANCELLED'} - elif mat is None: - self.report({"ERROR"}, "Failed generate base material") - return {'CANCELLED'} - - if not context.object.material_slots: - context.object.data.materials.append(mat) # Auto-creates slot. - else: - mat_ind = context.object.active_material_index - context.object.material_slots[mat_ind].material = mat - # Using the above in place of below due to some errors of: - # "attribute "active_material" from "Object" is read-only" - # context.object.active_material = mat - - # Don't want to generally run prep, as this would affect everything - # selected, as opposed to affecting just the new material - # bpy.ops.mcprep.prep_materials() - # Instead, run the update steps below copied from the MCprep inner loop - res, err = self.update_material(context, mat) - if res is False and err: - self.report({"ERROR"}, err) - return {'CANCELLED'} - elif res is False: - self.report({"ERROR"}, "Failed to prep generated material") - return {'CANCELLED'} - - self.track_param = context.scene.render.engine - return {'FINISHED'} - - def update_material(self, context, mat): - """Update the initially created material""" - if not mat: - env.log(f"During prep, found null material: {mat}", vv_only=True) - return - elif mat.library: - return - - engine = context.scene.render.engine - passes = generate.get_textures(mat) - env.log(f"Load Mat Passes:{passes}", vv_only=True) - if not self.useExtraMaps: - for pass_name in passes: - if pass_name != "diffuse": - passes[pass_name] = None - - if self.autoFindMissingTextures: - for pass_name in passes: - res = generate.replace_missing_texture(passes[pass_name]) - if res > 0: - mat["texture_swapped"] = True # used to apply saturation - - if engine == 'CYCLES' or engine == 'BLENDER_EEVEE': - options = generate.PrepOptions( - passes, - self.useReflections, - self.usePrincipledShader, - self.makeSolid, - self.packFormat, - self.useEmission, - False # This is for an option set in matprep_cycles - ) - res = generate.matprep_cycles( - mat=mat, - options=options - ) - else: - return False, "Only Cycles and Eevee supported" - - success = res == 0 - - if self.animateTextures: - sequences.animate_single_material( - mat, context.scene.render.engine) - - return success, None - # ----------------------------------------------------------------------------- # Registration @@ -973,6 +917,7 @@ def update_material(self, context, mat): ListColl, MCPREP_OT_swap_skin_from_file, MCPREP_OT_apply_skin, + MCPREP_OT_swap_skin_variant, MCPREP_OT_apply_username_skin, MCPREP_OT_download_username_list, # MCPREP_OT_skin_fix_eyes, diff --git a/MCprep_addon/mcprep_ui.py b/MCprep_addon/mcprep_ui.py index 958eeb76..9345d4b0 100644 --- a/MCprep_addon/mcprep_ui.py +++ b/MCprep_addon/mcprep_ui.py @@ -954,6 +954,8 @@ class MCPREP_PT_skins(bpy.types.Panel): def draw(self, context): layout = self.layout + wm_props = context.window_manager.mcprep + if addon_just_updated(): restart_layout(layout) return @@ -962,56 +964,63 @@ def draw(self, context): sind = context.scene.mcprep_skins_list_index mob_ind = context.scene.mcprep_props.mob_list_index skinname = None - row = layout.row() row.label(text="Select skin") row.operator( "mcprep.open_help", text="", icon="QUESTION", emboss=False ).url = "https://theduckcow.com/dev/blender/mcprep/skin-swapping/" - - # set size of UIlist row = layout.row() - col = row.column() - - is_sortable = len(env.skin_list) > 1 - rows = 1 - if (is_sortable): - rows = 4 - - # any other conditions for needing reloading? - if not env.skin_list: - col = layout.column() - col.label(text="No skins found/loaded") - p = col.operator( - "mcprep.reload_skins", text="Press to reload", icon="ERROR") - elif env.skin_list and len(env.skin_list) <= sind: - col = layout.column() - col.label(text="Reload skins") - p = col.operator( - "mcprep.reload_skins", text="Press to reload", icon="ERROR") - else: - col.template_list( - "MCPREP_UL_skins", "", - context.scene, "mcprep_skins_list", - context.scene, "mcprep_skins_list_index", - rows=rows) - - col = layout.column(align=True) - - row = col.row(align=True) - row.scale_y = 1.5 - if env.skin_list: - skinname = bpy.path.basename(env.skin_list[sind][0]) - p = row.operator("mcprep.applyskin", text=f"Apply {skinname}") - p.filepath = env.skin_list[sind][1] + row.prop(wm_props, "skin_modes",expand=True) + if wm_props.skin_modes == 'PLAYER': + + # set size of UIlist + row = layout.row() + col = row.column() + + is_sortable = len(env.skin_list) > 1 + rows = 1 + if (is_sortable): + rows = 4 + + # any other conditions for needing reloading? + if not env.skin_list: + col = layout.column() + col.label(text="No skins found/loaded") + p = col.operator( + "mcprep.reload_skins", text="Press to reload", icon="ERROR") + elif env.skin_list and len(env.skin_list) <= sind: + col = layout.column() + col.label(text="Reload skins") + p = col.operator( + "mcprep.reload_skins", text="Press to reload", icon="ERROR") else: - row.enabled = False - p = row.operator("mcprep.skin_swapper", text="No skins found") - row = col.row(align=True) - row.operator("mcprep.skin_swapper", text="Skin from file") - row = col.row(align=True) - row.operator("mcprep.applyusernameskin", text="Skin from username") + col.template_list( + "MCPREP_UL_skins", "", + context.scene, "mcprep_skins_list", + context.scene, "mcprep_skins_list_index", + rows=rows) + + col = layout.column(align=True) + + row = col.row(align=True) + row.scale_y = 1.5 + if env.skin_list: + skinname = bpy.path.basename(env.skin_list[sind][0]) + p = row.operator("mcprep.applyskin", text=f"Apply {skinname}") + p.filepath = env.skin_list[sind][1] + else: + row.enabled = False + p = row.operator("mcprep.skin_swapper", text="No skins found") + row = col.row(align=True) + row.operator("mcprep.skin_swapper", text="Skin from file") + row = col.row(align=True) + row.operator("mcprep.applyusernameskin", text="Skin from username") + else: + row = layout.row() + col = row.column() + wm_props.draw_variation_ui(context, col) + col.operator("mcprep.swap_skin") split = layout.split() col = split.column(align=True) row = col.row(align=True) @@ -1055,6 +1064,12 @@ def draw(self, context): # datapass = scn_props.mob_list[mob_ind].mcmob_type tx = f"Spawn {name} with {skinname}" row.operator("mcprep.spawn_with_skin", text=tx) + b_row.label(text="Resource pack") + subrow = b_row.row(align=True) + subrow.prop(context.scene, "mcprep_texturepack_path", text="") + subrow.operator( + "mcprep.reset_texture_path", icon=LOAD_FACTORY, text="") + class MCPREP_PT_materials(bpy.types.Panel): @@ -1983,9 +1998,17 @@ class McprepProps(bpy.types.PropertyGroup): effects_list: bpy.props.CollectionProperty(type=spawn_util.ListEffectsAssets) effects_list_index: bpy.props.IntProperty(default=0) -class MCprepWindowManager(PropertyGroup): - - + +class MCprepWindowManager(spawn_util.VariationProp, bpy.types.PropertyGroup): + skin_modes : bpy.props.EnumProperty( + name="Modes", + description="Skinswap modes", + items=( + ('PLAYER', "Player", ""), + ('MOB', "Mob/Entity", "") + ) + ) + @classmethod def register(cls): bpy.types.WindowManager.mcprep = bpy.props.PointerProperty(type=cls) diff --git a/MCprep_addon/spawner/entities.py b/MCprep_addon/spawner/entities.py index b21a89d3..221d8950 100644 --- a/MCprep_addon/spawner/entities.py +++ b/MCprep_addon/spawner/entities.py @@ -21,7 +21,10 @@ import bpy -from bpy.types import Context +from bpy.types import ( + Context, + Event +) from ..conf import env, Entity from .. import util from .. import tracking diff --git a/MCprep_addon/spawner/mcmodel.py b/MCprep_addon/spawner/mcmodel.py index 407a1be1..e635c399 100644 --- a/MCprep_addon/spawner/mcmodel.py +++ b/MCprep_addon/spawner/mcmodel.py @@ -337,9 +337,7 @@ def add_model(model_filepath: Path, obj_name: str="MinecraftModel") -> bpy.types bm.to_mesh(mesh) bm.free() - # - if has_color(obj.name): - + # if has_color(obj.name): return obj diff --git a/MCprep_addon/spawner/spawn_util.py b/MCprep_addon/spawner/spawn_util.py index 4c483daa..4a370da5 100644 --- a/MCprep_addon/spawner/spawn_util.py +++ b/MCprep_addon/spawner/spawn_util.py @@ -18,11 +18,15 @@ import os import re -from typing import List, Optional +from typing import List, Optional, Tuple, Union from pathlib import Path import bpy -from bpy.types import Context, Collection, BlendDataLibraries +from bpy.types import ( + Context, Collection, + BlendDataLibraries, + UILayout +) from ..conf import env from .. import util @@ -38,15 +42,11 @@ SKIP_COLL_LEGACY = "noimport" # Supporting older MCprep Meshswap lib. # Icon backwards compatibility. -if util.bv30(): - COLL_ICON = 'OUTLINER_COLLECTION' -elif util.bv28(): - COLL_ICON = 'COLLECTION_NEW' -else: - COLL_ICON = 'GROUP' +COLL_ICON = 'OUTLINER_COLLECTION' if util.bv30() else 'COLLECTION_NEW' + class VariationProp: - def color_items(self): + def color_items(self, context: Context) -> List[Tuple[str,str,str]]: """Color variation in ID order""" items = [ ('WHITE', "White", ""), @@ -67,8 +67,8 @@ def color_items(self): ('BLACK', "Black", ""), ] return items - - def profession_items(self): + + def profession_items(self, context: Context) -> List[Tuple[str, str, str]]: """Villager Professional""" items = [ ('ARMORER', "Armorer", ""), @@ -77,7 +77,7 @@ def profession_items(self): ('CLERIC', "Cleric", ""), ('FARMER', "Farmer", ""), ('FISHERMAN', "Fisherman", ""), - ('FLETCHER', "FLETCHER", ""), + ('FLETCHER', "Fletcher", ""), ('LEATHERWORKER', "Leatherworker", ""), ('LIBRARIAN', "Librarian", ""), ('MASON', "Mason", ""), @@ -89,10 +89,10 @@ def profession_items(self): ] return items - def level_items(self): + def level_items(self, context: Context) -> List[Tuple[str, str, str]]: """Villager Level """ items = [ - ('NOVICE', "NOVICE", ""), # Stone + ('NOVICE', "Novice", ""), # Stone ('APPRENTICE', "Apprentice", ""), # Iron ('JOURNEYMAN', "Journeyman", ""), # Gold ('EXPERT', "Expert", ""), # Emerald @@ -100,7 +100,7 @@ def level_items(self): ] return items - def biome_items(self): + def biome_items(self, context: Context) -> List[Tuple[str, str, str]]: items = [ ('DESERT', "Desert", ""), ('JUNGLE', "Jungle", ""), @@ -112,32 +112,179 @@ def biome_items(self): ] return items - def zombie_items(self): + def zombie_items(self, context: Context) -> List[Tuple[str, str, str]]: items = [ ('DEFAULT', "Default", ""), - ('DROWN', "Drown", "") + ('DROWN', "Drown", ""), ('HUSK', "Husk", ""), ] return items - def skeleton_items(self): + def skeleton_items(self, context: Context) -> List[Tuple[str, str, str]]: items = [ ('DEFAULT', "Default", ""), ('STRAY', "Stray", ""), ('WITHER', "Wither", ""), + # ('BOGGED', "Bogged", "") # 1.21 Added new skeleton varia + ] + return items + + def axolotl_items(self, context: Context) -> List[Tuple[str, str, str]]: + items = [ + ('BLUE', "Blue", ""), + ('CYAN', "Cyan", ""), + ('GOLD', "Gold", ""), + ('LUCY', "Lucy", ""), + ('WILD', "Wild", ""), ] return items - color_variation: bpy.props.EnumProperty(name="Color Variation", items=color_items) - # Villagers - profession_variation: bpy.props.EnumProperty(name="Villager Profession", items=profession_items) - level_variation: bpy.props.EnumProperty(name="Villager Level", items=level_items) - biome_variation: bpy.props.EnumProperty(name="Villager Biome", items=biome_items) + def rabbit_items(self, context: Context) -> List[Tuple[str, str, str]]: + items = [ + ('BLACK', "Black", ""), + ('BROWN', "Brown", ""), + ('CAERBANNOG', "Caerbannog", ""), + ('GOLD', "Gold", ""), + ('SALT', "Salt", ""), + ('TOAST',"Toast", ""), + ('WHITE', "White", ""), + ('WHITE_SPLOTCHED',"White splotched", ""), + ] + return items + + def frog_items(self, context: Context) -> List[Tuple[str, str, str]]: + items = [ + ('COLD', "Cold", ""), + ('TEMPERATE', "Temperate", ""), + ('WARM', "Warm", ""), + ] + return items + + def parrot_items(self, context: Context) -> List[Tuple[str, str, str]]: + items = [ + ('BLUE', "Blue", ""), + ('GREEN',"Green", ""), + ('GREY', "Grey", ""), + ('RED_BLUE', "Red blue", ""), + ('YELLOW_BLUE', "Yellow blue", ""), + ] + return items + + def llama_items(self, context: Context) -> List[Tuple[str, str, str]]: + items = [ + ('BROWN',"Brown", ""), + ('CREAMY',"Creamy",""), + ('GRAY', "Gray", ""), + ('WHITE', "White", ""), + ] + return items + + def horse_items(self, context: Context) -> List[Tuple[str, str, str]]: + items = [ + ('BLACK', "Black", ""), + ('BROWN', "BROWN", ""), + ('CHESTNUT', "CHESTNUT", ""), + ('CREAMY', "CREAMY", ""), + ('DARK_BROWN', "DARK_BROWN", ""), + ('GRAY', "GRAY", ""), + ('ZOMBIE', "ZOMBIE", ""), + ('SKELETON', "SKELETON", ""), + ] + return items + + def cat_items(self, context: Context) -> List[Tuple[str, str, str]]: + items = [ + ('ALL_BLACK', "All Black",""), + ('BLACK', "Black", ""), + ('BRITISH_SHORTHAIR', "British Shorthair", ""), + ('CALICO', "Calico", ""), + ('JELLIE', "Jellie", ""), + ('OCELOT', "Ocelot", ""), + ('PERSIAN', "Persian", ""), + ('RAGDOLL', "Ragdoll", ""), + ('RED', "Red", ""), + ('SIAMESE', "Siamese", ""), + ('TABBY', "Tabby", ""), + ('WHITE', "White", ""), + # ('CAT_COLLAR', "", "") + ] + return items + + def fox_items(self, context: Context) -> List[Tuple[str, str, str]]: + items = [ + ('NORMAL', "Normal", ""), + ('SNOW', "Snow", "") + ] + return items + + def squid_items(self, context: Context) -> List[Tuple[str, str, str]]: + items = [ + ('NORMAL', "Squid", ""), + ('GLOW', "Glow", "") + ] + return items + + def tropical_pattern_items(self, context: Context) -> List[Tuple[str, str, str]]: + items = [ + ('PATTERN_1', "Pattern 1", ""), + ('PATTERN_2', "Pattern 2", ""), + ('PATTERN_3', "Pattern 3", ""), + ('PATTERN_4', "Pattern 4", ""), + ('PATTERN_5', "Pattern 5", ""), + ("PATTERN_6", "Pattern 6", ""), + ] + return items + + def dog_items(self, context) -> List[Tuple[str, str, str]]: + items = [ + # ignore this just placeholder for 1.21 wolf variations + ('DEF', "def", ""), + ('DEA', "dea", "") + ] + return items + + def get_villager_names(self) -> Tuple[str, str, str]: + """Returns profession, profession level, type path in order""" + # (adds villager/{}.png to get the texture path, same for zombie_villager/) + return f"profession/{self.profession_variation}", f"profession_level/{self.level_variation}", f"type/{self.biome_variation}" + + def get_squid_name(self) -> str: + return self.squid_variation == 'GLOW' if 'glow_squid' else 'squid' + + def get_fox_name(self) -> Tuple[str, str]: + return ("snow_fox", "snow_fox_sleeping") if self.fox_variation == 'SNOW' else ("fox", "fox_sleeping") + + def get_tropical_name(self) -> str: + """Returns the tropical fish type and pattern""" + # (adds fish/{}.png to get the texture path) + type = self.tropical_type_variation.lower() + pattern = self.tropical_pattern_variation.replace("PATTERN_", "") + return f"tropical_{type}_pattern_{pattern}" + + def get_texture_path(self, mob_type: str, is_zombified: bool = False) -> List[Union[str, Path]]: + if mob_type == "Villager": + names = self.get_villager_names() + return [f"zombie_villager/{t}.png" for t in names] if is_zombified else[f"villager/{t}.png" for t in names] + elif mob_type == "Zombie": + return [f"zombie/{self.zombie_variation.lower()}.png"] + elif mob_type == "Skeleton": + return [f"zombie/{self.skeleton_variation.lower()}.png"] + elif mob_type == "Allay" or mob_type == "Vex": + return [] if self.is_zombiefied or mob_type == "Vex" else [] + # TODO: Add the rest of the mobs + + profession_variation: bpy.props.EnumProperty(name="Profession", items=profession_items) + level_variation: bpy.props.EnumProperty(name="Level", items=level_items) + biome_variation: bpy.props.EnumProperty(name="Biome", items=biome_items) zombie_variation: bpy.props.EnumProperty(name="Zombie Variation", items=zombie_items) skeleton_variation: bpy.props.EnumProperty(name="Skeleton Variation", items=skeleton_items) - is_zombiefied: BoolProperty(name="Is Zombiefied") # Use this for Allay-Vex + is_zombiefied: bpy.props.BoolProperty(name="Is Zombiefied") # Use this for Allay-Vex + fox_variation: bpy.props.EnumProperty(items=fox_items) + dog_variation: bpy.props.EnumProperty(items=dog_items) + cat_variation: bpy.props.EnumProperty(items=cat_items) - def draw_ui(self, context: Context, layout: UILayout): + def draw_variation_ui(self, context: Context, layout: UILayout): + """ Sharable method for drawing""" obj = context.object mob_type = getmob_type(obj) if mob_type in ["Villager", "Trader"]: @@ -149,24 +296,46 @@ def draw_ui(self, context: Context, layout: UILayout): layout.prop(self, "zombie_variation") elif mob_type == "Skeleton": layout.prop(self, "skeleton_variation") - - if mob_type in ["Villager", "Piglin", "Pigman", "Hoglin", "Allay", "Vex"] + elif mob_type == "Axolotl": + layout.prop(self, "axolotl_variation") + elif mob_type == "Rabbit": + layout.prop(self, "rabbit_variation") + elif mob_type == "Frog": + layout.prop(self, "frog_variation") + elif mob_type == "Parrot": + layout.prop(self, "parrot_variation") + elif mob_type == "Llama": + layout.prop(self, "llama_variation") + elif mob_type == "Horse": + layout.prop(self, "horse_variation") + elif mob_type == "Cat" or mob_type == "Ocelot": + layout.prop(self, "cat_variation") + elif mob_type == "Dog" or mob_type == "Wolf": + layout.prop(self, "dog_variation") + + counter_variant = ["Villager", "Piglin", "Hoglin", "Allay", "Vex"] + if mob_type in counter_variant: text = "Is Vex" if mob_type == "Allay" else "Is Zombified" layout.prop(self, "is_zombiefied", text=text) + + has_variant = counter_variant + ["Zombie", "Skeleton", "Llama", "Axolotl", "Rabbit", "Llama", "Parrot", "Frog", "Horse", "Cat", "Ocelot"] + if mob_type == "Custom" or mob_type not in has_variant: + layout.label(text="This mob doesn't has any variant to swap skin yet") + elif mob_type == "Player": + layout.label(text="Please use Player for this") # ----------------------------------------------------------------------------- # Reusable functions for spawners # ----------------------------------------------------------------------------- -def getmob_type(obj: bpy.types.Object): - """ Get mob type from rig - args - obj: Armature Object - """ - return obj.get("MCPREP_RIGTYPE", "Custom") -def has_color(name): - """Return True if has the color in name""" - return name in ["white", "orange", "magenta", "light_blue", "yellow", "lime", "pink", "gray", "light_gray", "cyan", "purple", "blue", "brown", "green", "red", "black"] + +def getmob_type(rig: bpy.types.Object): + """ Get mob type from rig + args + obj: Armature Object + """ + return rig.type == 'ARMATURE' and rig.get("MCPREP_MOBTYPE", "Custom") + def filter_collections(data_from: BlendDataLibraries) -> List[str]: """ TODO 2.7 groups @@ -597,7 +766,6 @@ def load_linked(self, context: Context, path: str, name: str) -> None: {'INFO'}, "This addon works better when the root bone's name is 'MAIN'") -# TODO: Add a way to check the version before load_append() def load_append(self, context: Context, path: Path, name: str) -> None: """Append an entire collection/group into this blend file and fix armature. @@ -677,17 +845,10 @@ def load_append(self, context: Context, path: Path, name: str) -> None: # without deleting them, just unlinking them from the scene util.obj_unlink_remove(ob, False, context) - if not util.bv28(): - grp_added.name = "reload-blend-to-remove-this-empty-group" - for obj in grp_added.objects: - grp_added.objects.unlink(obj) - util.select_set(obj, True) - grp_added.user_clear() - else: - for obj in grp_added.objects: - if obj not in context.view_layer.objects[:]: - continue - util.select_set(obj, True) + for obj in grp_added.objects: + if obj not in context.view_layer.objects[:]: + continue + util.select_set(obj, True) # try: # util.collections().remove(grp_added) @@ -756,19 +917,6 @@ def load_append(self, context: Context, path: Path, name: str) -> None: # add the original selection back for objs in sel: util.select_set(objs, True) - -def init_entity_prop(obj: bpy.types.Object, name: str, rig_version = (0,0,0)): - """ An utility function for adding attribute to object, armature object rigs, collection""" - # Vanilla mobs name or Custom, useful for skinswap villager rig - rig_type = obj.get("MCPREP_RIGTYPE") - # This will set the current Blender version if not exist - rig_version = obj.get("MCPREP_RIGVERS") - if rig_type == None: - util.set_prop(obj, "MCPREP_RIGTYPE", name) - if rig_version == None: - util.set_prop(obj, "MCPREP_RIGVERS", rig_version) - - # ----------------------------------------------------------------------------- # class definitions diff --git a/MCprep_addon/util.py b/MCprep_addon/util.py index dbafd636..a3919021 100644 --- a/MCprep_addon/util.py +++ b/MCprep_addon/util.py @@ -17,7 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### from subprocess import Popen, PIPE -from typing import List, Optional, Union, Tuple, Any +from typing import List, Optional, Union, Tuple, Any, Dict import enum import json import operator @@ -35,7 +35,8 @@ Material, Image, Node, - UILayout + UILayout, + ID ) from mathutils import Vector, Matrix @@ -809,7 +810,8 @@ def move_assets_to_excluded_layer(context: Context, collections: List[Collection continue # not linked, likely a sub-group not added to scn spawner_exclude_vl.collection.children.link(grp) initial_view_coll.collection.children.unlink(grp) - + + def set_prop(id_block: ID, key: str, value: Any, **kwargs: Dict[str, Any]): """Create or set the properties 3.0 got more functionalities @@ -819,18 +821,20 @@ def set_prop(id_block: ID, key: str, value: Any, **kwargs: Dict[str, Any]): id_props = id_block.id_properties_ui(key) id_props.update(**kwargs) overrides = kwargs.get("overridable_library", True) - if overrides != None: + if overrides is not None: id_block.property_overridable_library_set(f'["{key}"]', overrides) + def is_no_prep(mat: Material): """Check is material has no prep properties If no_prep is 1 returns True not exist or 0 returns False """ return mat.get("MCPREP_NO_PREP", False) - + + def get_entity_prop(obj, prop: Optional[str] = None): if prop: return obj.get(prop) else: - return obj.items() \ No newline at end of file + return obj.items()