diff --git a/.gitignore b/.gitignore index 51ed3dd7..f8810fd1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ test_results.tsv debug_save_state.blend1 MCprep_addon/MCprep_resources/resourcepacks/mcprep_default/materials.blend1 mcprep_venv_* +.cache diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e1e025d..5164fff7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -211,6 +211,23 @@ If you're using an IDE, it's recommened to install `bpy` as a Python module. In It's also recommened to use a virtual environment (especially if you're on Linux) as to avoid issues with system wide packages and different versions of `bpy`. [See this for more details](https://realpython.com/python-virtual-environments-a-primer/) +There are 2 methods to do this: +- Poetry +- Manualy + +Both are listed here. + +### With Poetry +[Poetry](https://python-poetry.org/) is a useful tool that allows easy dependency handling. To quote the website: + +> Python packaging and dependency management made easy + +If you decide to use Poetry, then simply run the following command: + +`poetry install` + +To enable the virtual environment, run `poetry shell`, then type `exit` when you're done. + ### Creating a Virtual Environment and Setting up `bpy` First, we need to come up with a name. For MCprep development, it's recommended to use the following convention: `mcprep_venv_` diff --git a/MCprep_addon/__init__.py b/MCprep_addon/__init__.py index 1982f630..dc2ade99 100755 --- a/MCprep_addon/__init__.py +++ b/MCprep_addon/__init__.py @@ -60,7 +60,6 @@ import bpy - def register(): load_modules.register(bl_info) diff --git a/MCprep_addon/addon_updater_ops.py b/MCprep_addon/addon_updater_ops.py index ba1c39d8..3d79f59c 100644 --- a/MCprep_addon/addon_updater_ops.py +++ b/MCprep_addon/addon_updater_ops.py @@ -81,7 +81,11 @@ def check_for_update(self, now): # Blender version utils # ----------------------------------------------------------------------------- def make_annotations(cls): - """Add annotation attribute to fields to avoid Blender 2.8+ warnings""" + """Add annotation attribute to fields to avoid Blender 2.8+ warnings. + + Deprecated operator as MCprep 3.5 moves to support 2.8+ only and using + native python annotations. + """ if not hasattr(bpy.app, "version") or bpy.app.version < (2, 80): return cls if bpy.app.version < (2, 93, 0): @@ -1357,7 +1361,7 @@ def select_link_function(self, tag): def register(bl_info): """Registering the operators in this module""" from . import conf - updater.verbose = conf.v + updater.verbose = conf.env.verbose # safer failure in case of issue loading module if updater.error != None: diff --git a/MCprep_addon/conf.py b/MCprep_addon/conf.py index ab5cc13e..ef7a1f8d 100644 --- a/MCprep_addon/conf.py +++ b/MCprep_addon/conf.py @@ -17,7 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### import os - +from pathlib import Path import bpy # check if custom preview icons available @@ -32,18 +32,152 @@ # ----------------------------------------------------------------------------- +class MCprepEnv: + def __init__(self): + self.data = None + self.json_data = None + self.json_path: Path = Path(os.path.dirname(__file__), "MCprep_resources", "mcprep_data.json") + self.json_path_update: Path = Path(os.path.dirname(__file__), "MCprep_resources", "mcprep_data_update.json") + + self.dev_file: Path = Path(os.path.dirname(__file__), "mcprep_dev.txt") + + # if new update file found from install, replace old one with new + if self.json_path_update.exists(): + self.json_path_update.replace(self.json_path) + + self.last_check_for_updated = 0 + + # Check to see if there's a text file for a dev build. If so, + if self.dev_file.exists(): + self.dev_build = True + self.verbose = True + self.very_verbose = True + self.log("Dev Build!") + + else: + self.dev_build = False + self.verbose = False + self.very_verbose = False + + # lazy load json, ie only load it when needed (util function defined) + + # ----------------------------------------------- + # For preview icons + # ----------------------------------------------- + + self.use_icons: bool = True + self.preview_collections: dict = {} + + # ----------------------------------------------- + # For initializing the custom icons + # ----------------------------------------------- + self.icons_init() + + # ----------------------------------------------- + # For cross-addon lists + # ----------------------------------------------- + + # To ensure shift-A starts drawing sub menus after pressing load all spawns + # as without this, if any one of the spawners loads nothing (invalid folder, + # no blend files etc), then it would continue to ask to reload spanwers. + self.loaded_all_spawners: bool = False + + self.skin_list: list = [] # each is: [ basename, path ] + self.rig_categories: list = [] # simple list of directory names + self.entity_list: list = [] + + # ----------------------------------------------- + # Matieral sync cahce, to avoid repeat lib reads + # ----------------------------------------------- + + # list of material names, each is a string. None by default to indicate + # that no reading has occurred. If lib not found, will update to []. + # If ever changing the resource pack, should also reset to None. + self.material_sync_cache = [] + + # ----------------------------------------------------------------------------- + # ICONS INIT + # ----------------------------------------------------------------------------- + + + def icons_init(self): + collection_sets = [ + "main", "skins", "mobs", "entities", "blocks", "items", "effects", "materials"] + + try: + for iconset in collection_sets: + self.preview_collections[iconset] = bpy.utils.previews.new() + + script_path = bpy.path.abspath(os.path.dirname(__file__)) + icons_dir = os.path.join(script_path, 'icons') + self.preview_collections["main"].load( + "crafting_icon", + os.path.join(icons_dir, "crafting_icon.png"), + 'IMAGE') + self.preview_collections["main"].load( + "meshswap_icon", + os.path.join(icons_dir, "meshswap_icon.png"), + 'IMAGE') + self.preview_collections["main"].load( + "spawner_icon", + os.path.join(icons_dir, "spawner_icon.png"), + 'IMAGE') + self.preview_collections["main"].load( + "sword_icon", + os.path.join(icons_dir, "sword_icon.png"), + 'IMAGE') + self.preview_collections["main"].load( + "effects_icon", + os.path.join(icons_dir, "effects_icon.png"), + 'IMAGE') + self.preview_collections["main"].load( + "entity_icon", + os.path.join(icons_dir, "entity_icon.png"), + 'IMAGE') + self.preview_collections["main"].load( + "model_icon", + os.path.join(icons_dir, "model_icon.png"), + 'IMAGE') + except Exception as e: + self.log("Old verison of blender, no custom icons available") + self.log("\t" + str(e)) + global use_icons + self.use_icons = False + for iconset in collection_sets: + self.preview_collections[iconset] = "" + + def log(self, statement: str, vv_only: bool=False): + if self.verbose and vv_only and self.very_verbose: + print(statement) + elif self.verbose: + print(statement) + + def deprecation_warning(self): + if self.dev_build: + import traceback + self.log("Deprecation Warning: This will be removed in MCprep 3.5.1!") + traceback.print_stack() + + +env = MCprepEnv() + + +# ! Deprecated as of MCprep 3.4.2 def init(): - + env.deprecation_warning() # ----------------------------------------------- - # Verbose, use as conf.v + # Verbose, use as env.verbose # Used to print out extra information, set false with distribution # ----------------------------------------------- + # ! Deprecated as of MCprep 3.4.2 global dev dev = True + # ! Deprecated as of MCprep 3.4.2 global v v = True # $VERBOSE, UI setting + # ! Deprecated as of MCprep 3.4.2 global vv vv = dev # $VERYVERBOSE @@ -53,13 +187,16 @@ def init(): # shouldn't load here, just globalize any json data? + # ! Deprecated as of MCprep 3.4.2 global data # import json + # ! Deprecated as of MCprep 3.4.2 global json_data # mcprep_data.json json_data = None # later will load addon information etc # if existing json_data_update exists, overwrite it + # ! Deprecated as of MCprep 3.4.2 global json_path json_path = os.path.join( os.path.dirname(__file__), @@ -87,8 +224,10 @@ def init(): # For preview icons # ----------------------------------------------- + # ! Deprecated as of MCprep 3.4.2 global use_icons use_icons = True + # ! Deprecated as of MCprep 3.4.2 global preview_collections preview_collections = {} @@ -104,15 +243,19 @@ def init(): # To ensure shift-A starts drawing sub menus after pressing load all spawns # as without this, if any one of the spawners loads nothing (invalid folder, # no blend files etc), then it would continue to ask to reload spanwers. + # ! Deprecated as of MCprep 3.4.2 global loaded_all_spawners loaded_all_spawners = False + # ! Deprecated as of MCprep 3.4.2 global skin_list skin_list = [] # each is: [ basename, path ] + # ! Deprecated as of MCprep 3.4.2 global rig_categories rig_categories = [] # simple list of directory names + # ! Deprecated as of MCprep 3.4.2 global entity_list entity_list = [] @@ -123,16 +266,18 @@ def init(): # list of material names, each is a string. None by default to indicate # that no reading has occurred. If lib not found, will update to []. # If ever changing the resource pack, should also reset to None. + # ! Deprecated as of MCprep 3.4.2 global material_sync_cache material_sync_cache = None +# ! Deprecated as of MCprep 3.4.2 # ----------------------------------------------------------------------------- # ICONS INIT # ----------------------------------------------------------------------------- - def icons_init(): + env.deprecation_warning() # start with custom icons # put into a try statement in case older blender version! global preview_collections @@ -184,13 +329,12 @@ def icons_init(): preview_collections[iconset] = "" +# ! Deprecated as of MCprep 3.4.2 def log(statement, vv_only=False): - """General purpose simple logging function.""" - global v - global vv - if v and vv_only and vv: + env.deprecation_warning() + if env.verbose and vv_only and env.very_verbose: print(statement) - elif v: + elif env.verbose: print(statement) @@ -211,29 +355,24 @@ def updater_select_link_function(self, tag): # GLOBAL REGISTRATOR INIT # ----------------------------------------------------------------------------- - +# ! Deprecated as of MCprep 3.4.2 def register(): + env.deprecation_warning() init() def unregister(): - global preview_collections - if use_icons: - for pcoll in preview_collections.values(): + if env.use_icons: + for pcoll in env.preview_collections.values(): try: bpy.utils.previews.remove(pcoll) except: - log('Issue clearing preview set ' + str(pcoll)) - preview_collections.clear() + env.log('Issue clearing preview set ' + str(pcoll)) + env.preview_collections.clear() - global json_data - json_data = None # actively clearing out json data for next open + env.json_data = None # actively clearing out json data for next open - global loaded_all_spawners - loaded_all_spawners = False - global skin_list - skin_list = [] - global rig_categories - rig_categories = [] - global material_sync_cache - material_sync_cache = [] + env.loaded_all_spawners = False + env.skin_list = [] + env.rig_categories = [] + env.material_sync_cache = [] diff --git a/MCprep_addon/import_bridge/bridge.py b/MCprep_addon/import_bridge/bridge.py index c86657dc..857156a9 100644 --- a/MCprep_addon/import_bridge/bridge.py +++ b/MCprep_addon/import_bridge/bridge.py @@ -25,7 +25,7 @@ from .mineways_connector import MinewaysConnector from .jmc_connector import JmcConnector -from .. import conf +from ..conf import env from .. import util from .. import tracking @@ -131,14 +131,14 @@ class MCPREP_OT_import_world_from_objmeta(bpy.types.Operator, ImportHelper): bl_label = "Import World from Reference" bl_options = {'REGISTER', 'UNDO'} - filter_glob = bpy.props.StringProperty( + filter_glob: bpy.props.StringProperty( default=".obj", # verify this works options={'HIDDEN'}) fileselectparams = "use_filter_blender" - files = bpy.props.CollectionProperty( + files: bpy.props.CollectionProperty( type=bpy.types.PropertyGroup, options={'HIDDEN', 'SKIP_SAVE'}) - # filter_image = bpy.props.BoolProperty( + # filter_image: bpy.props.BoolProperty( # default=True, # options={'HIDDEN', 'SKIP_SAVE'}) @@ -165,75 +165,75 @@ class MCPREP_OT_import_new_world(bpy.types.Operator): bl_label = "Import World" bl_options = {'REGISTER', 'UNDO'} - world_center = bpy.props.IntVectorProperty( + world_center: bpy.props.IntVectorProperty( name = "World center", description = "Select the X-Z center of import from the Minecraft save", default = (0, 0), subtype = 'XZ', size = 2, ) - import_center = bpy.props.IntVectorProperty( + import_center: bpy.props.IntVectorProperty( name = "Import location", description = "Move the center of the imported world to this location", default = (0, 0, 0), subtype = 'XYZ', size = 3, ) - import_height_offset = bpy.props.IntProperty( + import_height_offset: bpy.props.IntProperty( name = "Height offset", description = "Lower or raise the imported world by this amount", default = 0, ) - block_radius = bpy.props.IntProperty( + block_radius: bpy.props.IntProperty( name = "Block radius", description = "Radius of export, from World Center to all directions around", default = 100, min = 1 ) - ceiling = bpy.props.IntProperty( + ceiling: bpy.props.IntProperty( name = "Height/ceiling", description = "The top level of the world to import", default = 256, min = 1, max = 512 ) - floor = bpy.props.IntProperty( + floor: bpy.props.IntProperty( name = "Depth/floor", description = "The bottom level of the world to import", default = 0, min = 1, max = 512 ) - use_chunks = bpy.props.BoolProperty( + use_chunks: bpy.props.BoolProperty( name = "Use chunks", description = "Export the world with separate sections per chunk. In the background, each chunk is one export from Mineways", default = True ) - chunk_size = bpy.props.IntProperty( + chunk_size: bpy.props.IntProperty( name = "Chunk size", description = "Custom size of chunks to import", default = 16, min = 8 ) - separate_blocks = bpy.props.BoolProperty( + separate_blocks: bpy.props.BoolProperty( name = "Object per block", description = "Create one object per each block in the world (warning: will be slow, make exports larger, and make Blender slower!!)", default = False ) - prep_materials = bpy.props.BoolProperty( + prep_materials: bpy.props.BoolProperty( name = "Prep materials", default = True ) - animate_textures = bpy.props.BoolProperty( + animate_textures: bpy.props.BoolProperty( name = "Animate textures", default = False ) - single_texture = bpy.props.BoolProperty( + single_texture: bpy.props.BoolProperty( name = "Single texture file", description = "Export the world with a single texture. Cannot use with animate textures", default = False ) - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default = False, options={'HIDDEN'} ) @@ -286,7 +286,7 @@ def execute(self, context): "_" + connector.world + "_exp.mtl" ) - conf.log("Running Mineways bridge to import world "+ connector.world) + env.log("Running Mineways bridge to import world "+ connector.world) connector.run_export_single( obj_path, list(self.first_corner), @@ -297,14 +297,14 @@ def execute(self, context): # TODO: Implement check/connector class check for success, not just file existing if not os.path.isfile(obj_path): self.report({"ERROR"}, "OBJ file not exported, try using Mineways on its own to export OBJ") - conf.log("OBJ file not found to import: "+obj_path) + env.log("OBJ file not found to import: "+obj_path) return {"CANCELLED"} - conf.log("Now importing the exported obj into blender") + env.log("Now importing the exported obj into blender") bpy.ops.import_scene.obj(filepath=obj_path) # consider removing old world obj's? t2 = time.time() - conf.log("Mineways bridge completed in: {}s (Mineways: {}s, obj import: {}s".format( + env.log("Mineways bridge completed in: {}s (Mineways: {}s, obj import: {}s".format( int(t2-t0), int(t1-t0), int(t2-t1))) self.report({'INFO'}, "Bridge completed finished") @@ -317,7 +317,7 @@ class MCPREP_OT_refresh_world(bpy.types.Operator): bl_label = "Refresh World" bl_options = {'REGISTER', 'UNDO'} - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default = False, options={'HIDDEN'} ) @@ -334,7 +334,7 @@ class MCPREP_OT_extend_world(bpy.types.Operator): bl_idname = "mcprep.bridge_world_extend" bl_label = "Extend World" - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default = False, options={'HIDDEN'} ) @@ -361,7 +361,6 @@ def execute(self, context): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) diff --git a/MCprep_addon/load_modules.py b/MCprep_addon/load_modules.py index f000c7a1..dd76d2cd 100644 --- a/MCprep_addon/load_modules.py +++ b/MCprep_addon/load_modules.py @@ -172,7 +172,6 @@ def register(bl_info): - conf.register() tracking.register(bl_info) for mod in module_list: mod.register() @@ -183,8 +182,8 @@ def register(bl_info): # Inject the custom updater function, to use release zip instead src. addon_updater_ops.updater.select_link = conf.updater_select_link_function - conf.log("MCprep: Verbose is enabled") - conf.log("MCprep: Very Verbose is enabled", vv_only=True) + conf.env.log("MCprep: Verbose is enabled") + conf.env.log("MCprep: Very Verbose is enabled", vv_only=True) def unregister(bl_info): diff --git a/MCprep_addon/materials/default_materials.py b/MCprep_addon/materials/default_materials.py index d1b737dc..f8d3b6a4 100644 --- a/MCprep_addon/materials/default_materials.py +++ b/MCprep_addon/materials/default_materials.py @@ -21,28 +21,29 @@ import bpy -from .. import conf from .. import tracking from .. import util from . import sync +from ..conf import env + def default_material_in_sync_library(default_material, context): """Returns true if the material is in the sync mat library blend file.""" - if conf.material_sync_cache is None: + if env.material_sync_cache is None: sync.reload_material_sync_library(context) - if util.nameGeneralize(default_material) in conf.material_sync_cache: + if util.nameGeneralize(default_material) in env.material_sync_cache: return True - elif default_material in conf.material_sync_cache: + elif default_material in env.material_sync_cache: return True return False def sync_default_material(context, material, default_material, engine): """Normal sync material method but with duplication and name change.""" - if default_material in conf.material_sync_cache: + if default_material in env.material_sync_cache: import_name = default_material - elif util.nameGeneralize(default_material) in conf.material_sync_cache: + elif util.nameGeneralize(default_material) in env.material_sync_cache: import_name = util.nameGeneralize(default_material) # If link is true, check library material not already linked. @@ -98,12 +99,12 @@ class MCPREP_OT_default_material(bpy.types.Operator): bl_label = "Sync Default Materials" bl_options = {'REGISTER', 'UNDO'} - use_pbr = bpy.props.BoolProperty( + use_pbr: bpy.props.BoolProperty( name="Use PBR", description="Use PBR or not", default=False) - engine = bpy.props.StringProperty( + engine: bpy.props.StringProperty( name="engine To Use", description="Defines the engine to use", default="cycles") @@ -137,7 +138,7 @@ def execute(self, context): try: err = sync_default_material(context, mat, material_name, self.engine) # no linking if err: - conf.log(err) + env.log(err) except Exception as e: print(e) @@ -210,7 +211,6 @@ def create_default_material(self, context, engine, type): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) bpy.app.handlers.load_post.append(sync.clear_sync_cache) diff --git a/MCprep_addon/materials/generate.py b/MCprep_addon/materials/generate.py index 9021e599..706bc4e5 100644 --- a/MCprep_addon/materials/generate.py +++ b/MCprep_addon/materials/generate.py @@ -18,10 +18,11 @@ import os import bpy +from dataclasses import dataclass -from .. import conf from .. import util +from ..conf import env # ----------------------------------------------------------------------------- # Material prep and generation functions (no registration) @@ -33,7 +34,7 @@ def update_mcprep_texturepack_path(self, context): bpy.ops.mcprep.reload_items() bpy.ops.mcprep.reload_materials() bpy.ops.mcprep.reload_models() - conf.material_sync_cache = None + env.material_sync_cache = None # Forces particle plane emitter to now use the newly set resource pack # the first time, but the value gets saved again after. @@ -48,7 +49,7 @@ def get_mc_canonical_name(name): form (mc, jmc, or mineways) """ general_name = util.nameGeneralize(name) - if not conf.json_data: + if not env.json_data: res = util.load_mcprep_json() if not res: return general_name, None @@ -59,13 +60,13 @@ def get_mc_canonical_name(name): if ".emit" in general_name and general_name != ".emit": general_name = general_name.replace(".emit", "") - no_missing = "blocks" in conf.json_data - no_missing &= "block_mapping_mc" in conf.json_data["blocks"] - no_missing &= "block_mapping_jmc" in conf.json_data["blocks"] - no_missing &= "block_mapping_mineways" in conf.json_data["blocks"] + no_missing = "blocks" in env.json_data + no_missing &= "block_mapping_mc" in env.json_data["blocks"] + no_missing &= "block_mapping_jmc" in env.json_data["blocks"] + no_missing &= "block_mapping_mineways" in env.json_data["blocks"] if no_missing is False: - conf.log("Missing key values in json") + env.log("Missing key values in json") return general_name, None # The below workaround is to account for the jmc2obj v113+ which changes @@ -84,30 +85,30 @@ def get_mc_canonical_name(name): # mixed up with the new "water": "painting/water" texture. general_name = "water_still" - if general_name in conf.json_data["blocks"]["block_mapping_mc"]: - canon = conf.json_data["blocks"]["block_mapping_mc"][general_name] + if general_name in env.json_data["blocks"]["block_mapping_mc"]: + canon = env.json_data["blocks"]["block_mapping_mc"][general_name] form = "mc" if not jmc_prefix else "jmc2obj" - elif general_name in conf.json_data["blocks"]["block_mapping_jmc"]: - canon = conf.json_data["blocks"]["block_mapping_jmc"][general_name] + elif general_name in env.json_data["blocks"]["block_mapping_jmc"]: + canon = env.json_data["blocks"]["block_mapping_jmc"][general_name] form = "jmc2obj" - elif general_name in conf.json_data["blocks"]["block_mapping_mineways"]: - canon = conf.json_data["blocks"]["block_mapping_mineways"][general_name] + elif general_name in env.json_data["blocks"]["block_mapping_mineways"]: + canon = env.json_data["blocks"]["block_mapping_mineways"][general_name] form = "mineways" - elif general_name.lower() in conf.json_data["blocks"]["block_mapping_jmc"]: - canon = conf.json_data["blocks"]["block_mapping_jmc"][ + elif general_name.lower() in env.json_data["blocks"]["block_mapping_jmc"]: + canon = env.json_data["blocks"]["block_mapping_jmc"][ general_name.lower()] form = "jmc2obj" - elif general_name.lower() in conf.json_data["blocks"]["block_mapping_mineways"]: - canon = conf.json_data["blocks"]["block_mapping_mineways"][ + elif general_name.lower() in env.json_data["blocks"]["block_mapping_mineways"]: + canon = env.json_data["blocks"]["block_mapping_mineways"][ general_name.lower()] form = "mineways" else: - conf.log("Canonical name not matched: " + general_name, vv_only=True) + env.log("Canonical name not matched: " + general_name, vv_only=True) canon = general_name form = None if canon is None or canon == '': - conf.log("Error: Encountered None canon value with " + str(general_name)) + env.log("Error: Encountered None canon value with " + str(general_name)) canon = general_name return canon, form @@ -126,7 +127,7 @@ def find_from_texturepack(blockname, resource_folder=None): resource_folder = bpy.path.abspath(bpy.context.scene.mcprep_texturepack_path) if not os.path.isdir(resource_folder): - conf.log("Error, resource folder does not exist") + env.log("Error, resource folder does not exist") return # Check multiple paths, picking the first match (order is important), @@ -227,15 +228,15 @@ def detect_form(materials): def checklist(matName, listName): """Helper to expand single wildcard within generalized material names""" - if not conf.json_data: - conf.log("No json_data for checklist to call from!") - if "blocks" not in conf.json_data or listName not in conf.json_data["blocks"]: - conf.log( - "conf.json_data is missing blocks or listName " + str(listName)) + if not env.json_data: + env.log("No json_data for checklist to call from!") + if "blocks" not in env.json_data or listName not in env.json_data["blocks"]: + env.log( + "env.json_data is missing blocks or listName " + str(listName)) return False - if matName in conf.json_data["blocks"][listName]: + if matName in env.json_data["blocks"][listName]: return True - for name in conf.json_data["blocks"][listName]: + for name in env.json_data["blocks"][listName]: if '*' not in name: continue x = name.split('*') @@ -245,129 +246,17 @@ def checklist(matName, listName): return True return False - -def matprep_internal(mat, passes, use_reflections, only_solid): - """Update existing internal materials with improved settings. - Will not necessarily properly convert cycles materials into internal.""" - - if not conf.json_data: - _ = util.load_mcprep_json() - - newName = mat.name + '_tex' - texList = mat.texture_slots.values() - try: - bpy.data.textures[texList[0].name].name = newName - except: - conf.log( - '\twarning: material ' + mat.name + ' has no texture slot. skipping...') - return - - # disable all but first slot, ensure first slot enabled - mat.use_textures[0] = True - diff_layer = 0 - spec_layer = None - norm_layer = None - disp_layer = None - saturate_layer = None - first_unused = None - for index in range(1, len(texList)): - if not mat.texture_slots[index] or not mat.texture_slots[index].texture: - mat.use_textures[index] = False - if not first_unused: - first_unused = index - elif "MCPREP_diffuse" in mat.texture_slots[index].texture: - diff_layer = index - mat.use_textures[index] = True - elif "MCPREP_specular" in mat.texture_slots[index].texture: - spec_layer = index - mat.use_textures[index] = True - elif "MCPREP_normal" in mat.texture_slots[index].texture: - norm_layer = index - mat.use_textures[index] = True - elif "SATURATE" in mat.texture_slots[index].texture: - saturate_layer = index - mat.use_textures[index] = True - else: - mat.use_textures[index] = False - - if mat.texture_slots[diff_layer].texture.type != "IMAGE": - conf.log("No diffuse-detected texture, skipping material: " + mat.name) - return 1 - - # strip out the .00# - canon, _ = get_mc_canonical_name(util.nameGeneralize(mat.name)) - mat.use_nodes = False - - mat.use_transparent_shadows = True # all materials receive trans - mat.specular_intensity = 0 - mat.texture_slots[diff_layer].texture.use_interpolation = False - mat.texture_slots[diff_layer].texture.filter_type = 'BOX' - mat.texture_slots[diff_layer].texture.filter_size = 0 - mat.texture_slots[diff_layer].use_map_color_diffuse = True - mat.texture_slots[diff_layer].diffuse_color_factor = 1 - - if only_solid is False and not checklist(canon, "solid"): # alpha default on - bpy.data.textures[newName].use_alpha = True - mat.texture_slots[diff_layer].use_map_alpha = True - mat.use_transparency = True - mat.alpha = 0 - mat.texture_slots[diff_layer].alpha_factor = 1 - for index in [spec_layer, norm_layer, disp_layer]: - if index: - mat.texture_slots[index].use_map_alpha = False - - if use_reflections and checklist(canon, "reflective"): - mat.alpha = 0 - mat.raytrace_mirror.use = True - mat.raytrace_mirror.reflect_factor = 0.15 - else: - mat.raytrace_mirror.use = False - mat.alpha = 0 - - if checklist(canon, "emit") or "emit" in mat.name.lower(): - mat.emit = 1 - else: - mat.emit = 0 - - # cycle through and see if the layer exists to enable/disable blend - if not checklist(canon, "desaturated"): - pass - else: - diff_img = mat.texture_slots[diff_layer].texture.image - is_grayscale = is_image_grayscale(diff_img) - - # TODO: code is duplicative to below, consolidate later - if mat.name + "_saturate" in bpy.data.textures: - new_tex = bpy.data.textures[mat.name + "_saturate"] - else: - new_tex = bpy.data.textures.new(name=mat.name + "_saturate", type="BLEND") - if not saturate_layer: - if not first_unused: - first_unused = len(mat.texture_slots) - 1 # force reuse last at worst - sl = mat.texture_slots.create(first_unused) - else: - sl = mat.texture_slots[saturate_layer] - sl.texture = new_tex - sl.texture["SATURATE"] = True - sl.use_map_normal = False - sl.use_map_color_diffuse = True - sl.use_map_specular = False - sl.use_map_alpha = False - sl.blend_type = 'MULTIPLY' # changed from OVERLAY - sl.use = bool(is_grayscale) # turns off if not grayscale (or None) - new_tex.use_color_ramp = True - for _ in range(len(new_tex.color_ramp.elements) - 1): - new_tex.color_ramp.elements.remove(new_tex.color_ramp.elements[0]) - desat_color = conf.json_data['blocks']['desaturated'][canon] - if len(desat_color) < len(new_tex.color_ramp.elements[0].color): - desat_color.append(1.0) - new_tex.color_ramp.elements[0].color = desat_color - - return 0 - - -def matprep_cycles( - mat, passes, use_reflections, use_principled, only_solid, pack_format, use_emission_nodes): +@dataclass +class PrepOptions: + passes: dict[str] + use_reflections: bool + use_principled: bool + only_solid: bool + pack_format: str + use_emission_nodes: bool + use_emission: bool + +def matprep_cycles(mat, options: PrepOptions): """Determine how to prep or generate the cycles materials. Args: @@ -386,23 +275,20 @@ def matprep_cycles( mat.use_nodes = True matGen = util.nameGeneralize(mat.name) - canon, form = get_mc_canonical_name(matGen) - use_emission = checklist(canon, "emit") or "emit" in mat.name.lower() + canon, _ = get_mc_canonical_name(matGen) + options.use_emission = checklist(canon, "emit") or "emit" in mat.name.lower() # TODO: Update different options for water before enabling this # if use_reflections and checklist(canon, "water"): # res = matgen_special_water(mat, passes) # if use_reflections and checklist(canon, "glass"): # res = matgen_special_glass(mat, passes) - if pack_format == "simple" and util.bv28(): - res = matgen_cycles_simple( - mat, passes, use_reflections, use_emission, only_solid, use_principled, use_emission_nodes) - elif use_principled and hasattr(bpy.types, 'ShaderNodeBsdfPrincipled'): - res = matgen_cycles_principled( - mat, passes, use_reflections, use_emission, only_solid, pack_format, use_emission_nodes) + if options.pack_format == "simple" and util.bv28(): + res = matgen_cycles_simple(mat, options) + elif options.use_principled and hasattr(bpy.types, 'ShaderNodeBsdfPrincipled'): + res = matgen_cycles_principled(mat, options) else: - res = matgen_cycles_original( - mat, passes, use_reflections, use_emission, only_solid, pack_format, use_emission_nodes) + res = matgen_cycles_original(mat, options) return res @@ -418,12 +304,7 @@ def set_texture_pack(material, folder, use_extra_passes): return 0 image_data = util.loadTexture(image) - engine = bpy.context.scene.render.engine - - if engine == 'CYCLES' or engine == 'BLENDER_EEVEE': - _ = set_cycles_texture(image_data, material, True) - elif engine == 'BLENDER_RENDER' or engine == 'BLENDER_GAME': - _ = set_internal_texture(image_data, material, use_extra_passes) + _ = set_cycles_texture(image_data, material, True) return 1 @@ -431,19 +312,11 @@ def assert_textures_on_materials(image, materials): """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) - engine = bpy.context.scene.render.engine count = 0 - - if engine == 'BLENDER_RENDER' or engine == 'BLENDER_GAME': - for mat in materials: - status = set_internal_texture(image, mat) - if status: - count += 1 - elif engine == 'CYCLES' or engine == 'BLENDER_EEVEE': - for mat in materials: - status = set_cycles_texture(image, mat) - if status: - count += 1 + for mat in materials: + status = set_cycles_texture(image, mat) + if status: + count += 1 return count @@ -455,7 +328,7 @@ def set_cycles_texture(image, material, extra_passes=False): material: existing material datablock extra_passes: whether to include or hard exclude non diffuse passes """ - conf.log("Setting cycles texture for img: {} mat: {}".format( + env.log("Setting cycles texture for img: {} mat: {}".format( image.name, material.name)) if material.node_tree is None: return False @@ -475,7 +348,7 @@ def set_cycles_texture(image, material, extra_passes=False): if node.type == "MIX_RGB" and "SATURATE" in node: node.mute = not is_grayscale node.hide = not is_grayscale - conf.log(" mix_rgb to saturate texture") + env.log(" mix_rgb to saturate texture") # if node.type != "TEX_IMAGE": continue @@ -524,135 +397,6 @@ def set_cycles_texture(image, material, extra_passes=False): return changed - -def set_internal_texture(image, material, extra_passes=False): - """Set texture for internal engine. Input is image datablock.""" - # TODO: when going through layers, see if enabled already for normal / - # spec or not and enabled/disable accordingly (e.g. if was resource - # with normal, now is not) - - # check if there is more data to see pass types - canon, _ = get_mc_canonical_name(util.nameGeneralize(material.name)) - - is_grayscale = False - if checklist(canon, "desaturated"): - is_grayscale = is_image_grayscale(image) - - img_sets = {} - if extra_passes: - img_sets = find_additional_passes(image.filepath) - if is_grayscale is True: - img_sets["saturate"] = True - - base = None - tex = None - - # set primary diffuse color as the first image found - for i, sl in enumerate(material.texture_slots): - if sl is None or sl.texture is None or sl.texture.type != 'IMAGE': - continue - sl.texture.image = image - sl.use = True - tex = sl.texture - base = i - sl.use_map_normal = False - sl.use_map_color_diffuse = True - sl.use_map_specular = False - sl.blend_type = 'MIX' - break - - # if no textures found, assert adding this one as the first - if tex is None: - conf.log("Found no textures, asserting texture onto material") - name = material.name + "_tex" - if name not in bpy.data.textures: - tex = bpy.data.textures.new(name=name, type="IMAGE") - else: - tex = bpy.data.textures[name] - tex.image = image - if material.texture_slots[0] is None: - material.texture_slots.create(0) - material.texture_slots[0].texture = tex - material.texture_slots[0].texture["MCPREP_diffuse"] = True - material.texture_slots[0].use = True - base = 0 - - # go through and turn off any previous passes not in img_sets - for i, sl in enumerate(material.texture_slots): - if i == base: - continue # skip primary texture set - if "normal" in img_sets and img_sets["normal"]: # pop item each time - if tex and tex.name + "_n" in bpy.data.textures: - new_tex = bpy.data.textures[tex.name + "_n"] - else: - new_tex = bpy.data.textures.new( - name=tex.name + "_n", type="IMAGE") - print(sl) - if not sl: - sl = material.texture_slots.create(i) - f = img_sets.pop("normal") - new_img = util.loadTexture(f) - new_tex.image = new_img - sl.texture = new_tex - sl.texture["MCPREP_normal"] = True - sl.use_map_normal = True - sl.normal_factor = 0.1 - sl.use_map_color_diffuse = False - sl.use_map_specular = False - sl.use_map_alpha = False - sl.blend_type = 'MIX' - sl.use = True - elif "spec" in img_sets and img_sets["spec"]: - if tex and tex.name + "_s" in bpy.data.textures: - new_tex = bpy.data.textures[tex.name + "_s"] - else: - new_tex = bpy.data.textures.new( - name=tex.name + "_s", type="IMAGE") - if not sl: - sl = material.texture_slots.create(i) - f = img_sets.pop("specular") - new_img = util.loadTexture(f) - new_img.use_alpha = False # would mess up material - new_tex.image = new_img - sl.texture = new_tex - sl.texture["MCPREP_specular"] = True - sl.use_map_normal = False - sl.use_map_color_diffuse = False - sl.use_map_specular = True - sl.use_map_alpha = False - sl.blend_type = 'MIX' - sl.use = True - elif "saturate" in img_sets: - img_sets.pop("saturate") - print("Running saturate") - if not checklist(canon, "desaturated"): - continue - if tex and tex.name + "_saturate" in bpy.data.textures: - new_tex = bpy.data.textures[tex.name + "_saturate"] - else: - new_tex = bpy.data.textures.new( - name=tex.name + "_saturate", type="BLEND") - if not sl: - sl = material.texture_slots.create(i) - sl.texture = new_tex - sl.texture["SATURATE"] = True - sl.use_map_normal = False - sl.use_map_color_diffuse = True - sl.use_map_specular = False - sl.use_map_alpha = False - sl.blend_type = 'OVERLAY' - sl.use = True - new_tex.use_color_ramp = True - for _ in range(len(new_tex.color_ramp.elements) - 1): - new_tex.color_ramp.elements.remove(new_tex.color_ramp.elements[0]) - desat_color = conf.json_data['blocks']['desaturated'][canon] - if len(desat_color) < len(new_tex.color_ramp.elements[0].color): - desat_color.append(1.0) - new_tex.color_ramp.elements[0].color = desat_color - - return True - - def get_node_for_pass(material, pass_name): """Assumes cycles material, returns texture node for given pass in mat.""" if pass_name not in ["diffuse", "specular", "normal", "displace"]: @@ -749,7 +493,7 @@ def get_textures(material): def find_additional_passes(image_file): """Find relevant passes like normal and spec in same folder as image.""" abs_img_file = bpy.path.abspath(image_file) - conf.log("\tFind additional passes for: " + image_file, vv_only=True) + env.log("\tFind additional passes for: " + image_file, vv_only=True) if not os.path.isfile(abs_img_file): return {} @@ -813,7 +557,7 @@ def replace_missing_texture(image): elif os.path.isfile(bpy.path.abspath(image.filepath)): # ... or the filepath is present. return False - conf.log("Missing datablock detected: " + image.name) + env.log("Missing datablock detected: " + image.name) name = image.name if len(name) > 4 and name[-4] == ".": @@ -846,11 +590,11 @@ def rgb_to_saturation(r, g, b): if not image: return None - conf.log("Checking image for grayscale " + image.name, vv_only=True) + env.log("Checking image for grayscale " + image.name, vv_only=True) if 'grayscale' in image: # cache return image['grayscale'] if not image.pixels: - conf.log("Not an image / no pixels", vv_only=True) + env.log("Not an image / no pixels", vv_only=True) return None # setup sampling to limit number of processed pixels @@ -898,14 +642,14 @@ def rgb_to_saturation(r, g, b): pixels_saturated += 1 if pixels_saturated >= max_thresh: is_grayscale = False - conf.log("Image not grayscale: " + image.name, vv_only=True) + env.log("Image not grayscale: " + image.name, vv_only=True) break if datablock_copied: # Cleanup if image was copied to scale down size. bpy.data.images.remove(imgcp) image['grayscale'] = is_grayscale # set cache - conf.log("Image is grayscale: " + image.name, vv_only=True) + env.log("Image is grayscale: " + image.name, vv_only=True) return is_grayscale @@ -916,59 +660,38 @@ def set_saturation_material(mat): canon, _ = get_mc_canonical_name(mat.name) if not checklist(canon, "desaturated"): - conf.log("debug: not eligible for saturation", vv_only=True) + env.log("debug: not eligible for saturation", vv_only=True) return - conf.log("Running set_saturation on " + mat.name, vv_only=True) + env.log("Running set_saturation on " + mat.name, vv_only=True) diff_pass = get_node_for_pass(mat, "diffuse") if not diff_pass: return diff_img = diff_pass.image if not diff_img: - conf.log("debug: No diffuse", vv_only=True) + env.log("debug: No diffuse", vv_only=True) return saturate = is_image_grayscale(diff_img) - desat_color = conf.json_data['blocks']['desaturated'][canon] - engine = bpy.context.scene.render.engine - - if engine == 'BLENDER_RENDER' or engine == 'BLENDER_GAME': - # get the saturation textureslot, or create - sat_slot_ind = None - first_unused = None - for index in range(len(mat.texture_slots)): - slot = mat.texture_slots[index] - if not slot or not slot.texture: - if not first_unused: - first_unused = index - continue - elif "SATURATE" not in slot.texture: - continue - sat_slot_ind = index - break - if not sat_slot_ind: - return - mat.use_textures[sat_slot_ind] = bool(saturate) - - elif engine == 'CYCLES' or engine == 'BLENDER_EEVEE': - sat_node = None - for node in mat.node_tree.nodes: - if "SATURATE" not in node: - continue - sat_node = node - break + desat_color = env.json_data['blocks']['desaturated'][canon] + sat_node = None + for node in mat.node_tree.nodes: + if "SATURATE" not in node: + continue + sat_node = node + break - if not sat_node: - return # requires regenerating material to add back - if len(desat_color) == 3: - desat_color += [1] # add in alpha + if not sat_node: + return # requires regenerating material to add back + if len(desat_color) == 3: + desat_color += [1] # add in alpha - sat_node_in = get_node_socket(node, is_input=True) # Get the node sockets in a version agnostic way - sat_node.inputs[sat_node_in[2]].default_value = desat_color - sat_node.mute = not bool(saturate) - sat_node.hide = not bool(saturate) + sat_node_in = get_node_socket(node, is_input=True) # Get the node sockets in a version agnostic way + sat_node.inputs[sat_node_in[2]].default_value = desat_color + sat_node.mute = not bool(saturate) + sat_node.hide = not bool(saturate) def create_node(tree_nodes, node_type, **attrs): @@ -1087,7 +810,6 @@ def apply_texture_animation_pass_settings(mat, animated_data): def texgen_specular(mat, passes, nodeInputs, use_reflections): - matGen = util.nameGeneralize(mat.name) canon, form = get_mc_canonical_name(matGen) @@ -1192,8 +914,8 @@ def texgen_specular(mat, passes, nodeInputs, use_reflections): elif not is_image_grayscale(image_diff): pass else: - conf.log("Texture desaturated: " + canon, vv_only=True) - desat_color = conf.json_data['blocks']['desaturated'][canon] + env.log("Texture desaturated: " + canon, vv_only=True) + desat_color = env.json_data['blocks']['desaturated'][canon] if len(desat_color) < len(nodeSaturateMix.inputs[saturateMixIn[2]].default_value): desat_color.append(1.0) nodeSaturateMix.inputs[saturateMixIn[2]].default_value = desat_color @@ -1334,8 +1056,8 @@ def texgen_seus(mat, passes, nodeInputs, use_reflections, use_emission): elif not is_image_grayscale(image_diff): pass else: - conf.log("Texture desaturated: " + canon, vv_only=True) - desat_color = conf.json_data['blocks']['desaturated'][canon] + env.log("Texture desaturated: " + canon, vv_only=True) + desat_color = env.json_data['blocks']['desaturated'][canon] desat_cmpr = len(nodeSaturateMix.inputs[saturateMixIn[2]].default_value) if len(desat_color) < desat_cmpr: desat_color.append(1.0) @@ -1354,14 +1076,13 @@ def texgen_seus(mat, passes, nodeInputs, use_reflections, use_emission): nodeTexDiff.image = image_diff -def matgen_cycles_simple( - mat, passes, use_reflections, use_emission, only_solid, use_principled, use_emission_nodes): +def matgen_cycles_simple(mat, options: PrepOptions): """Generate principled cycles material.""" matGen = util.nameGeneralize(mat.name) canon, form = get_mc_canonical_name(matGen) - image_diff = passes["diffuse"] + image_diff = options.passes["diffuse"] if not image_diff: print("Could not find diffuse image, halting generation: " + mat.name) @@ -1402,13 +1123,13 @@ def matgen_cycles_simple( node_out = create_node(nodes, "ShaderNodeOutputMaterial", location=(900, 0)) # Sets default reflective values - if use_reflections and checklist(canon, "reflective"): + if options.use_reflections and checklist(canon, "reflective"): principled.inputs["Roughness"].default_value = 0 else: principled.inputs["Roughness"].default_value = 0.7 # Sets default metallic values - if use_reflections and checklist(canon, "metallic"): + if options.use_reflections and checklist(canon, "metallic"): principled.inputs["Metallic"].default_value = 1 if principled.inputs["Roughness"].default_value < 0.2: principled.inputs["Roughness"].default_value = 0.2 @@ -1429,7 +1150,7 @@ def matgen_cycles_simple( links.new(nodeSaturateMix.outputs[saturateMixOut[0]], principled.inputs[0]) links.new(principled.outputs["BSDF"], node_out.inputs[0]) - if only_solid is True or checklist(canon, "solid"): + if options.only_solid is True or checklist(canon, "solid"): # faster, and appropriate for non-transparent (and refelctive?) materials principled.distribution = 'GGX' if hasattr(mat, "blend_method"): @@ -1443,7 +1164,7 @@ def matgen_cycles_simple( if hasattr(mat, "shadow_method"): mat.shadow_method = 'HASHED' - if use_emission_nodes and use_emission: + if options.use_emission_nodes and options.use_emission: inputs = [inp.name for inp in principled.inputs] if 'Emission Strength' in inputs: # Later 2.9 versions only. principled.inputs['Emission Strength'].default_value = 1 @@ -1460,11 +1181,10 @@ def matgen_cycles_simple( elif not is_image_grayscale(image_diff): pass else: - conf.log("Texture desaturated: " + canon, vv_only=True) - desat_color = conf.json_data['blocks']['desaturated'][canon] - desat_cmpr = len(nodeSaturateMix.inputs[saturateMixIn[2]].default_value) - if len(desat_color) < desat_cmpr: - desat_color.append(1.0) + env.log("Texture desaturated: " + canon, vv_only=True) + desat_color = env.json_data['blocks']['desaturated'][canon] + if len(desat_color) < len(nodeSaturateMix.inputs[2].default_value): + desat_color.append(1.0) nodeSaturateMix.inputs[saturateMixIn[2]].default_value = desat_color nodeSaturateMix.mute = False nodeSaturateMix.hide = False @@ -1476,14 +1196,13 @@ def matgen_cycles_simple( return 0 -def matgen_cycles_principled( - mat, passes, use_reflections, use_emission, only_solid, pack_format, use_emission_nodes): +def matgen_cycles_principled(mat, options: PrepOptions): """Generate principled cycles material""" matGen = util.nameGeneralize(mat.name) canon, form = get_mc_canonical_name(matGen) - image_diff = passes["diffuse"] + image_diff = options.passes["diffuse"] if not image_diff: print("Could not find diffuse image, halting generation: " + mat.name) @@ -1517,13 +1236,13 @@ def matgen_cycles_principled( nodeMixTrans.inputs[0].default_value = 1 # Sets default reflective values - if use_reflections and checklist(canon, "reflective"): + if options.use_reflections and checklist(canon, "reflective"): principled.inputs["Roughness"].default_value = 0 else: principled.inputs["Roughness"].default_value = 0.7 # Sets default metallic values - if use_reflections and checklist(canon, "metallic"): + if options.use_reflections and checklist(canon, "metallic"): principled.inputs["Metallic"].default_value = 1 if principled.inputs["Roughness"].default_value < 0.2: principled.inputs["Roughness"].default_value = 0.2 @@ -1540,7 +1259,7 @@ def matgen_cycles_principled( nodes, "ShaderNodeEmission", location=(120, 260)) nodeMixEmit = create_node( nodes, "ShaderNodeMixShader", location=(420, 0)) - if use_emission_nodes: + if options.use_emission_nodes: # Create emission nodes nodeMixCam = create_node( nodes, "ShaderNodeMixShader", location=(320, 260)) @@ -1562,7 +1281,7 @@ def matgen_cycles_principled( links.new(nodeMixCam.outputs["Shader"], nodeMixEmit.inputs[2]) links.new(nodeMixEmit.outputs["Shader"], nodeMixTrans.inputs[2]) - if use_emission: + if options.use_emission: nodeMixEmit.inputs[0].default_value = 1 else: nodeMixEmit.inputs[0].default_value = 0 @@ -1582,22 +1301,22 @@ def matgen_cycles_principled( [principled.inputs["Specular"]], [principled.inputs["Normal"]]] - if not use_emission_nodes: + if not options.use_emission_nodes: nodes.remove(nodeEmit) nodes.remove(nodeEmitCam) nodes.remove(nodeMixEmit) # generate texture format and connect - if pack_format == "specular": - texgen_specular(mat, passes, nodeInputs, use_reflections) - elif pack_format == "seus": - texgen_seus(mat, passes, nodeInputs, use_reflections, use_emission_nodes) + if options.pack_format == "specular": + texgen_specular(mat, options.passes, nodeInputs, options.use_reflections) + elif options.pack_format == "seus": + texgen_seus(mat, options.passes, nodeInputs, options.use_reflections, options.use_emission_nodes) - if only_solid is True or checklist(canon, "solid"): + if options.only_solid is True or checklist(canon, "solid"): nodes.remove(nodeTrans) nodes.remove(nodeMixTrans) nodeOut.location = (620, 0) - if use_emission_nodes: + if options.use_emission_nodes: links.new(nodeMixEmit.outputs[0], nodeOut.inputs[0]) # faster, and appropriate for non-transparent (and refelctive?) materials @@ -1633,14 +1352,13 @@ def matgen_cycles_principled( return 0 -def matgen_cycles_original( - mat, passes, use_reflections, use_emission, only_solid, pack_format, use_emission_nodes): +def matgen_cycles_original(mat, options: PrepOptions): """Generate principled cycles material""" matGen = util.nameGeneralize(mat.name) canon, form = get_mc_canonical_name(matGen) - image_diff = passes["diffuse"] + image_diff = options.passes["diffuse"] if not image_diff: print("Could not find diffuse image, halting generation: " + mat.name) @@ -1734,7 +1452,7 @@ def matgen_cycles_original( nodeMixRGBDiff.inputs[mixDiffIn[2]].default_value = [1, 1, 1, 1] # Sets default reflective values - if use_reflections and checklist(canon, "reflective"): + if options.use_reflections and checklist(canon, "reflective"): nodeGlossMetallic.inputs["Roughness"].default_value = 0 nodeMathPower.inputs[0].default_value = 0 nodeGlossDiff.inputs["Roughness"].default_value = 0 @@ -1744,7 +1462,7 @@ def matgen_cycles_original( nodeGlossDiff.inputs["Roughness"].default_value = 0.7 # Sets default metallic values - if use_reflections and checklist(canon, "metallic"): + if options.use_reflections and checklist(canon, "metallic"): nodeMixMetallic.inputs["Fac"].default_value = 1 if nodeGlossMetallic.inputs["Roughness"].default_value < 0.2: @@ -1817,12 +1535,12 @@ def matgen_cycles_original( nodeBump.inputs["Normal"]]] # generate texture format and connect - if pack_format == "specular": - texgen_specular(mat, passes, nodeInputs, use_reflections) - elif pack_format == "seus": - texgen_seus(mat, passes, nodeInputs, use_reflections, use_emission_nodes) + if options.pack_format == "specular": + texgen_specular(mat, options.passes, nodeInputs, options.use_reflections) + elif options.pack_format == "seus": + texgen_seus(mat, options.passes, nodeInputs, options.use_reflections, options.use_emission_nodes) - if only_solid is True or checklist(canon, "solid"): + if options.only_solid is True or checklist(canon, "solid"): nodes.remove(nodeTrans) nodes.remove(nodeMixTrans) nodeOut.location = (1540, 0) @@ -1853,7 +1571,7 @@ def matgen_cycles_original( # but, BLEND does NOT work well with Depth of Field or layering - if use_emission: + if options.use_emission: nodeMixEmit.inputs[0].default_value = 1 else: nodeMixEmit.inputs[0].default_value = 0 @@ -2001,8 +1719,8 @@ def matgen_special_water(mat, passes): elif not is_image_grayscale(image_diff): pass else: - conf.log("Texture desaturated: " + canon, vv_only=True) - desat_color = conf.json_data['blocks']['desaturated'][canon] + env.log("Texture desaturated: " + canon, vv_only=True) + desat_color = env.json_data['blocks']['desaturated'][canon] if len(desat_color) < len(nodeSaturateMix.inputs[saturateMixIn[2]].default_value): desat_color.append(1.0) nodeSaturateMix.inputs[saturateMixIn[2]].default_value = desat_color diff --git a/MCprep_addon/materials/material_manager.py b/MCprep_addon/materials/material_manager.py index d6bd58b3..b21927d3 100644 --- a/MCprep_addon/materials/material_manager.py +++ b/MCprep_addon/materials/material_manager.py @@ -21,12 +21,13 @@ import bpy -from .. import conf from . import generate from . import sequences from .. import tracking from .. import util +from ..conf import env + # ----------------------------------------------------------------------------- # UI and utility functions @@ -40,14 +41,14 @@ def reload_materials(context): extensions = [".png", ".jpg", ".jpeg"] mcprep_props.material_list.clear() - if conf.use_icons and conf.preview_collections["materials"]: + if env.use_icons and env.preview_collections["materials"]: try: - bpy.utils.previews.remove(conf.preview_collections["materials"]) + bpy.utils.previews.remove(env.preview_collections["materials"]) except: - conf.log("Failed to remove icon set, materials") + env.log("Failed to remove icon set, materials") if not os.path.isdir(resource_folder): - conf.log("Error, resource folder does not exist") + env.log("Error, resource folder does not exist") return # Check multiple paths, picking the first match (order is important), @@ -88,9 +89,9 @@ def reload_materials(context): asset.index = i # if available, load the custom icon too - if not conf.use_icons or conf.preview_collections["materials"] == "": + if not env.use_icons or env.preview_collections["materials"] == "": continue - conf.preview_collections["materials"].load( + env.preview_collections["materials"].load( "material-{}".format(i), image_file, 'IMAGE') if mcprep_props.material_list_index >= len(mcprep_props.material_list): @@ -100,9 +101,9 @@ def reload_materials(context): class ListMaterials(bpy.types.PropertyGroup): """For UI drawing of item assets and holding data""" # inherited: name - description = bpy.props.StringProperty() - path = bpy.props.StringProperty(subtype='FILE_PATH') - index = bpy.props.IntProperty(min=0, default=0) # for icon drawing + description: bpy.props.StringProperty() + path: bpy.props.StringProperty(subtype='FILE_PATH') + index: bpy.props.IntProperty(min=0, default=0) # for icon drawing # ----------------------------------------------------------------------------- @@ -143,11 +144,11 @@ class MCPREP_OT_combine_materials(bpy.types.Operator): bl_options = {'REGISTER', 'UNDO'} # arg to auto-force remove old? versus just keep as 0-users - selection_only = bpy.props.BoolProperty( + selection_only: bpy.props.BoolProperty( name="Selection only", description="Build materials to consoldiate based on selected objects only", default=True) - skipUsage = bpy.props.BoolProperty(default=False, options={'HIDDEN'}) + skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) track_function = "combine_materials" @tracking.report_error @@ -196,7 +197,7 @@ def getMaterials(self, context): elif mat.name not in name_cat[base]: name_cat[base].append(mat.name) else: - conf.log("Skipping, already added material", True) + env.log("Skipping, already added material", True) # Pre 2.78 solution, deep loop. if bpy.app.version < (2, 78): @@ -226,7 +227,7 @@ def getMaterials(self, context): name_cat[base].sort() # in-place sorting baseMat = bpy.data.materials[name_cat[base][0]] - conf.log([name_cat[base], " ## ", baseMat], vv_only=True) + env.log([name_cat[base], " ## ", baseMat], vv_only=True) for matname in name_cat[base][1:]: # skip if fake user set @@ -238,9 +239,9 @@ def getMaterials(self, context): self.report({'ERROR'}, str(res)) return {'CANCELLED'} old = bpy.data.materials[matname] - conf.log("removing old? " + matname, vv_only=True) + env.log("removing old? " + matname, vv_only=True) if removeold is True and old.users == 0: - conf.log("removing old:" + matname, vv_only=True) + env.log("removing old:" + matname, vv_only=True) try: data.remove(old) except ReferenceError as err: @@ -261,7 +262,7 @@ def getMaterials(self, context): baseMat.name = gen_base else: baseMat.name = gen_base - conf.log(["Final: ", baseMat], vv_only=True) + env.log(["Final: ", baseMat], vv_only=True) postcount = len(["x" for x in getMaterials(self, context) if x.users > 0]) self.report({"INFO"}, "Consolidated {x} materials down to {y}".format( @@ -277,12 +278,12 @@ class MCPREP_OT_combine_images(bpy.types.Operator): bl_description = "Consolidate the same images together e.g. img.001 and img.002" # arg to auto-force remove old? versus just keep as 0-users - selection_only = bpy.props.BoolProperty( + selection_only: bpy.props.BoolProperty( name="Selection only", description=( "Build images to consoldiate based on selected objects' materials only"), default=False) - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -323,7 +324,7 @@ def execute(self, context): elif im.name not in name_cat[base]: name_cat[base].append(im.name) else: - conf.log("Skipping, already added image", vv_only=True) + env.log("Skipping, already added image", vv_only=True) # pre 2.78 solution, deep loop if bpy.app.version < (2, 78): @@ -386,11 +387,11 @@ class MCPREP_OT_replace_missing_textures(bpy.types.Operator): bl_options = {'REGISTER', 'UNDO'} # deleteAlpha = False - animateTextures = bpy.props.BoolProperty( + animateTextures: bpy.props.BoolProperty( name="Animate textures (may be slow first time)", description="Convert tiled images into image sequence for material.", default=True) - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -417,7 +418,7 @@ def execute(self, context): updated = False passes = generate.get_textures(mat) if not passes: - conf.log("No images found within material") + env.log("No images found within material") for pass_name in passes: if pass_name == 'diffuse' and passes[pass_name] is None: res = self.load_from_texturepack(mat) @@ -427,7 +428,7 @@ def execute(self, context): updated = True if updated: count += 1 - conf.log("Updated " + mat.name) + env.log("Updated " + mat.name) if self.animateTextures: sequences.animate_single_material( mat, context.scene.render.engine) @@ -445,15 +446,15 @@ def execute(self, context): def load_from_texturepack(self, mat): """If image datablock not found in passes, try to directly load and assign""" - conf.log("Loading from texpack for " + mat.name, vv_only=True) + env.log("Loading from texpack for " + mat.name, vv_only=True) canon, _ = generate.get_mc_canonical_name(mat.name) image_path = generate.find_from_texturepack(canon) if not image_path or not os.path.isfile(image_path): - conf.log("Find missing images: No source file found for " + mat.name) + env.log("Find missing images: No source file found for " + mat.name) return False # even if images of same name already exist, load new block - conf.log("Find missing images: Creating new image datablock for " + mat.name) + env.log("Find missing images: Creating new image datablock for " + mat.name) # do not use 'check_existing=False' to keep compatibility pre 2.79 image = bpy.data.images.load(image_path, check_existing=True) @@ -483,7 +484,6 @@ def load_from_texturepack(self, mat): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) diff --git a/MCprep_addon/materials/prep.py b/MCprep_addon/materials/prep.py index 9a8f27ce..6e466dd2 100755 --- a/MCprep_addon/materials/prep.py +++ b/MCprep_addon/materials/prep.py @@ -22,13 +22,14 @@ import bpy from bpy_extras.io_utils import ImportHelper -from .. import conf from . import generate from . import sequences from .. import tracking from .. import util from . import uv_tools +from ..conf import env + # ----------------------------------------------------------------------------- # Material class functions # ----------------------------------------------------------------------------- @@ -52,47 +53,47 @@ def pack_formats(self, context): itms.append(("seus", "SEUS", "Sets the pack format to SEUS.")) return itms - animateTextures = bpy.props.BoolProperty( + animateTextures: bpy.props.BoolProperty( name="Animate textures (may be slow first time)", description=( "Swap still images for the animated sequenced found in " "the active or default texture pack."), default=False) - autoFindMissingTextures = bpy.props.BoolProperty( + autoFindMissingTextures: bpy.props.BoolProperty( name="Find missing images", description=( "If the texture for an existing material is missing, try " "to load from the default texture pack instead"), default=True) - combineMaterials = bpy.props.BoolProperty( + combineMaterials: bpy.props.BoolProperty( name="Combine materials", description="Consolidate duplciate materials & textures", default=False) - improveUiSettings = bpy.props.BoolProperty( + improveUiSettings: bpy.props.BoolProperty( name="Improve UI", description="Automatically improve relevant UI settings", default=True) - optimizeScene = bpy.props.BoolProperty( + optimizeScene: bpy.props.BoolProperty( name="Optimize scene (cycles)", description="Optimize the scene for faster cycles rendering", default=False) - usePrincipledShader = bpy.props.BoolProperty( + usePrincipledShader: bpy.props.BoolProperty( name="Use Principled Shader (if available)", description=( "If available and using cycles, build materials using the " "principled shader"), default=True) - useReflections = bpy.props.BoolProperty( + useReflections: bpy.props.BoolProperty( name="Use reflections", description="Allow appropriate materials to be rendered reflective", default=True) - useExtraMaps = bpy.props.BoolProperty( + useExtraMaps: bpy.props.BoolProperty( name="Use extra maps", description=( "load other image passes like normal and " "spec maps if available"), default=True) - normalIntensity = bpy.props.FloatProperty( + normalIntensity: bpy.props.FloatProperty( name="Normal map intensity", description=( "Set normal map intensity, if normal maps are found in " @@ -100,26 +101,26 @@ def pack_formats(self, context): default=1.0, max=1, min=0) - makeSolid = bpy.props.BoolProperty( + makeSolid: bpy.props.BoolProperty( name="Make all materials solid", description="Make all materials solid only, for shadows and rendering", default=False) - syncMaterials = bpy.props.BoolProperty( + syncMaterials: bpy.props.BoolProperty( name="Sync materials", description=( "Synchronize materials with those in the active " "pack's materials.blend file"), default=True) - # newDefault = bpy.props.BoolProperty( + # newDefault: bpy.props.BoolProperty( # name="Use custom default material", # description="Use a custom default material if you have one set up", # default=False) - packFormat = bpy.props.EnumProperty( + packFormat: bpy.props.EnumProperty( name="Pack Format", description="Change the pack format when using a PBR resource pack.", items=pack_formats ) - useEmission = bpy.props.BoolProperty( + useEmission: bpy.props.BoolProperty( name="Use Emission", description="Make emmisive materials emit light", default=True @@ -170,7 +171,7 @@ class MCPREP_OT_prep_materials(bpy.types.Operator, McprepMaterialProps): bl_label = "MCprep Materials" bl_options = {'REGISTER', 'UNDO'} - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -208,7 +209,7 @@ def execute(self, context): for mat in mat_list: if not mat: - conf.log( + env.log( "During prep, found null material:" + str(mat), vv_only=True) continue @@ -244,26 +245,26 @@ def execute(self, context): if res > 0: mat["texture_swapped"] = True # used to apply saturation - if engine == 'BLENDER_RENDER' or engine == 'BLENDER_GAME': - res = generate.matprep_internal( - mat, passes, self.useReflections, self.makeSolid) - if res == 0: - count += 1 - elif engine == 'CYCLES' or engine == 'BLENDER_EEVEE': + 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, - passes=passes, - use_reflections=self.useReflections, - use_principled=self.usePrincipledShader, - only_solid=self.makeSolid, - pack_format=self.packFormat, - use_emission_nodes=self.useEmission) + options=options + ) if res == 0: count += 1 else: self.report( {'ERROR'}, - "Only Blender Internal, Cycles, and Eevee are supported") + "Only Cycles and Eevee are supported") return {'CANCELLED'} if self.animateTextures: @@ -317,15 +318,15 @@ class MCPREP_OT_materials_help(bpy.types.Operator): def update_supress_help(self, context): """Update trigger for supressing help popups.""" - if conf.v and self.suppress_help: + if env.verbose and self.suppress_help: print("Supressing future help popups") - elif conf.v and not self.suppress_help: + elif env.verbose and not self.suppress_help: print("Re-enabling popup warnings") - help_num = bpy.props.IntProperty( + help_num: bpy.props.IntProperty( default=0, options={'HIDDEN'}) - suppress_help = bpy.props.BoolProperty( + suppress_help: bpy.props.BoolProperty( name="Don't show this again", default=False, options={'HIDDEN'}, @@ -386,23 +387,23 @@ class MCPREP_OT_swap_texture_pack( "select a folder path for an unzipped resource pack or texture folder") bl_options = {'REGISTER', 'UNDO'} - filter_glob = bpy.props.StringProperty( + filter_glob: bpy.props.StringProperty( default="", options={'HIDDEN'}) use_filter_folder = True fileselectparams = "use_filter_blender" - filepath = bpy.props.StringProperty(subtype='DIR_PATH') - filter_image = bpy.props.BoolProperty( + filepath: bpy.props.StringProperty(subtype='DIR_PATH') + filter_image: bpy.props.BoolProperty( default=True, options={'HIDDEN', 'SKIP_SAVE'}) - filter_folder = bpy.props.BoolProperty( + filter_folder: bpy.props.BoolProperty( default=True, options={'HIDDEN', 'SKIP_SAVE'}) - prepMaterials = bpy.props.BoolProperty( + prepMaterials: bpy.props.BoolProperty( name="Prep materials", description="Runs prep materials after texture swap to regenerate materials", default=False) - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -446,7 +447,7 @@ def execute(self, context): folder = self.filepath if os.path.isfile(bpy.path.abspath(folder)): folder = os.path.dirname(folder) - conf.log("Folder: " + folder) + env.log("Folder: " + folder) if not os.path.isdir(bpy.path.abspath(folder)): self.report({'ERROR'}, "Selected folder does not exist") @@ -471,7 +472,7 @@ def execute(self, context): # set the scene's folder for the texturepack being swapped context.scene.mcprep_texturepack_path = folder - conf.log("Materials detected: " + str(len(mat_list))) + env.log("Materials detected: " + str(len(mat_list))) res = 0 for mat in mat_list: self.preprocess_material(mat) @@ -501,8 +502,8 @@ def execute(self, context): self.report({'ERROR'}, ( "Detected scaled UV's (all in one texture), be sure to use " "Mineway's 'Export Individual Textures To..'' feature")) - conf.log("Detected scaledd UV's, incompatible with swap textures") - conf.log([ob.name for ob in affected_objs], vv_only=True) + env.log("Detected scaledd UV's, incompatible with swap textures") + env.log([ob.name for ob in affected_objs], vv_only=True) else: self.report({'INFO'}, "{} materials affected".format(res)) self.track_param = context.scene.render.engine @@ -515,7 +516,7 @@ def preprocess_material(self, material): # but in Mineways export, this is the flattened grass/drit block side if material.name == "grass_block_side_overlay": material.name = "grass_block_side" - conf.log("Renamed material: grass_block_side_overlay to grass_block_side") + env.log("Renamed material: grass_block_side_overlay to grass_block_side") class MCPREP_OT_load_material(bpy.types.Operator, McprepMaterialProps): @@ -526,8 +527,8 @@ class MCPREP_OT_load_material(bpy.types.Operator, McprepMaterialProps): "Generate and apply the selected material based on active resource pack") bl_options = {'REGISTER', 'UNDO'} - filepath = bpy.props.StringProperty(default="") - skipUsage = bpy.props.BoolProperty(default=False, options={'HIDDEN'}) + filepath: bpy.props.StringProperty(default="") + skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) @classmethod def poll(cls, context): @@ -589,9 +590,7 @@ def generate_base_material(self, context, name, path): mat = bpy.data.materials.new(name=name) engine = context.scene.render.engine - if engine == 'BLENDER_RENDER' or engine == 'BLENDER_GAME': - generate.set_internal_texture(image, mat, self.useExtraMaps) - elif engine == 'CYCLES' or engine == 'BLENDER_EEVEE': + if engine == 'CYCLES' or engine == 'BLENDER_EEVEE': # need to create at least one texture node first, then the rest works mat.use_nodes = True nodes = mat.node_tree.nodes @@ -604,26 +603,26 @@ def generate_base_material(self, context, name, path): node_nrm = generate.create_node(nodes, 'ShaderNodeTexImage') node_nrm["MCPREP_normal"] = True - conf.log("Added blank texture node") + env.log("Added blank texture node") # now use standard method to update textures generate.set_cycles_texture(image, mat, self.useExtraMaps) else: - return None, "Only Blender Internal, Cycles, or Eevee supported" + return None, "Only Cycles and Eevee supported" return mat, None def update_material(self, context, mat): """Update the initially created material""" if not mat: - conf.log("During prep, found null material:" + str(mat), vv_only=True) + env.log("During prep, found null material:" + str(mat), vv_only=True) return elif mat.library: return engine = context.scene.render.engine passes = generate.get_textures(mat) - conf.log("Load Mat Passes:" + str(passes), vv_only=True) + env.log("Load Mat Passes:" + str(passes), vv_only=True) if not self.useExtraMaps: for pass_name in passes: if pass_name != "diffuse": @@ -635,20 +634,22 @@ def update_material(self, context, mat): if res > 0: mat["texture_swapped"] = True # used to apply saturation - if engine == 'BLENDER_RENDER' or engine == 'BLENDER_GAME': - res = generate.matprep_internal( - mat, passes, self.useReflections, self.makeSolid) - elif engine == 'CYCLES' or engine == 'BLENDER_EEVEE': + 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, - passes=passes, - use_reflections=self.useReflections, - use_principled=self.usePrincipledShader, - only_solid=self.makeSolid, - pack_format=self.packFormat, - use_emission_nodes=self.useEmission) + options=options + ) else: - return False, "Only Blender Internal, Cycles, or Eevee supported" + return False, "Only Cycles and Eevee supported" success = res == 0 @@ -673,9 +674,7 @@ def update_material(self, context, mat): def register(): - util.make_annotations(McprepMaterialProps) for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) diff --git a/MCprep_addon/materials/sequences.py b/MCprep_addon/materials/sequences.py index 8b9db870..c090eddb 100644 --- a/MCprep_addon/materials/sequences.py +++ b/MCprep_addon/materials/sequences.py @@ -24,12 +24,12 @@ import bpy -from .. import conf from . import generate from .. import tracking from .. import util from . import uv_tools +from ..conf import env # ----------------------------------------------------------------------------- # Supporting functions @@ -63,12 +63,12 @@ def animate_single_material( # get the base image from the texturepack (cycles/BI general) image_path_canon = generate.find_from_texturepack(canon) if not image_path_canon: - conf.log("Canon path not found for {}:{}, form {}, path: {}".format( + env.log("Canon path not found for {}:{}, form {}, path: {}".format( mat_gen, canon, form, image_path_canon), vv_only=True) return affectable, False, None if not os.path.isfile(image_path_canon + ".mcmeta"): - conf.log(".mcmeta not found for " + mat_gen, vv_only=True) + env.log(".mcmeta not found for " + mat_gen, vv_only=True) affectable = False return affectable, False, None affectable = True @@ -78,7 +78,7 @@ def animate_single_material( mcmeta = json.load(mcf) except json.JSONDecodeError: print("Failed to parse the mcmeta data") - conf.log("MCmeta for {}: {}".format(diffuse_block, mcmeta)) + env.log("MCmeta for {}: {}".format(diffuse_block, mcmeta)) # apply the sequence if any found, will be empty dict if not if diffuse_block and hasattr(diffuse_block, "filepath"): @@ -87,12 +87,12 @@ def animate_single_material( source_path = None if not source_path: source_path = image_path_canon - conf.log("Fallback to using image canon path instead of source path") + env.log("Fallback to using image canon path instead of source path") tile_path_dict, err = generate_material_sequence( source_path, image_path_canon, form, export_location, clear_cache) if err: - conf.log("Error occured during sequence generation:") - conf.log(err) + env.log("Error occured during sequence generation:") + env.log(err) return affectable, False, err if tile_path_dict == {}: return affectable, False, None @@ -100,7 +100,7 @@ def animate_single_material( affected_materials = 0 for pass_name in tile_path_dict: if not tile_path_dict[pass_name]: # ie '' - conf.log("Skipping passname: " + pass_name) + env.log("Skipping passname: " + pass_name) continue if engine == 'CYCLES' or engine == 'BLENDER_EEVEE': node = generate.get_node_for_pass(mat, pass_name) @@ -186,20 +186,20 @@ def generate_material_sequence( # export to save frames in currently selected texturepack (could be addon) seq_path_base = os.path.dirname(bpy.path.abspath(image_path)) - if conf.vv: - conf.log("Pre-sequence details") - conf.log(image_path) - conf.log(image_dict) - conf.log(img_pass_dict) - conf.log(seq_path_base) - conf.log("---") + if env.very_verbose: + env.log("Pre-sequence details") + env.log(image_path) + env.log(image_dict) + env.log(img_pass_dict) + env.log(seq_path_base) + env.log("---") if form == "jmc2obj": - conf.log("DEBUG - jmc2obj aniamted texture detected") + env.log("DEBUG - jmc2obj aniamted texture detected") elif form == "mineways": - conf.log("DEBUG - mineways aniamted texture detected") + env.log("DEBUG - mineways aniamted texture detected") else: - conf.log("DEBUG - other form of animated texture detected") + env.log("DEBUG - other form of animated texture detected") perm_denied = ( "Permission denied, could not make folder - " @@ -207,8 +207,8 @@ def generate_material_sequence( for img_pass in img_pass_dict: passfile = img_pass_dict[img_pass] - conf.log("Running on file:") - conf.log(bpy.path.abspath(passfile)) + env.log("Running on file:") + env.log(bpy.path.abspath(passfile)) # Create the sequence subdir (without race condition) pass_name = os.path.splitext(os.path.basename(passfile))[0] @@ -216,7 +216,7 @@ def generate_material_sequence( seq_path = os.path.join(os.path.dirname(passfile), pass_name) else: seq_path = os.path.join(seq_path_base, pass_name) - conf.log("Using sequence directory: " + seq_path) + env.log("Using sequence directory: " + seq_path) try: # check parent folder exists/create if needed os.mkdir(os.path.dirname(seq_path)) @@ -242,7 +242,7 @@ def generate_material_sequence( if os.path.isfile(os.path.join(seq_path, tile)) and tile.startswith(pass_name)] if cached: - conf.log("Cached detected") + env.log("Cached detected") if clear_cache and cached: for tile in cached: @@ -283,12 +283,12 @@ def export_image_to_sequence(image_path, params, output_folder=None, form=None): image = bpy.data.images.load(image_path) tiles = image.size[1] / image.size[0] if int(tiles) != tiles: - conf.log("Not perfectly tiled image - " + image_path) + env.log("Not perfectly tiled image - " + image_path) image.user_clear() if image.users == 0: bpy.data.images.remove(image) else: - conf.log("Couldn't remove image, shouldn't keep: " + image.name) + env.log("Couldn't remove image, shouldn't keep: " + image.name) return None # any non-titled materials will exit here else: tiles = int(tiles) @@ -310,7 +310,7 @@ def export_image_to_sequence(image_path, params, output_folder=None, form=None): pxlen = len(image.pixels) first_img = None for i in range(tiles): - conf.log("Exporting sequence tile " + str(i)) + env.log("Exporting sequence tile " + str(i)) tile_name = basename + "_" + str(i + 1).zfill(4) out_path = os.path.join(output_folder, tile_name + ext) if not first_img: @@ -341,17 +341,17 @@ def export_image_to_sequence(image_path, params, output_folder=None, form=None): if img_tile.users == 0: bpy.data.images.remove(img_tile) else: - conf.log("Couldn't remove tile, shouldn't keep: " + img_tile.name) + env.log("Couldn't remove tile, shouldn't keep: " + img_tile.name) else: img_tile = None raise Exception("No Animate Textures Mineways support yet") - conf.log("Finished exporting frame sequence: " + basename) + env.log("Finished exporting frame sequence: " + basename) image.user_clear() if image.users == 0: bpy.data.images.remove(image) else: - conf.log("Couldn't remove image block, shouldn't keep: " + image.name) + env.log("Couldn't remove image block, shouldn't keep: " + image.name) return bpy.path.abspath(first_img) @@ -372,11 +372,11 @@ def set_sequence_to_texnode(node, image_path): Note: this also works as-is where "node" is actually a texture block """ - conf.log("Sequence exporting " + os.path.basename(image_path), vv_only=True) + env.log("Sequence exporting " + os.path.basename(image_path), vv_only=True) image_path = bpy.path.abspath(image_path) base_dir = os.path.dirname(image_path) first_img = os.path.splitext(os.path.basename(image_path))[0] - conf.log("IMAGE path to apply: {}, node/tex: {}".format( + env.log("IMAGE path to apply: {}, node/tex: {}".format( image_path, node.name)) ind = get_sequence_int_index(first_img) @@ -386,7 +386,7 @@ def set_sequence_to_texnode(node, image_path): img_count = len(img_sets) image_data = bpy.data.images.load(image_path) - conf.log("Loaded in " + str(image_data)) + env.log("Loaded in " + str(image_data)) image_data.source = 'SEQUENCE' node.image = image_data node.image_user.frame_duration = img_count @@ -406,12 +406,12 @@ class MCPREP_OT_prep_animated_textures(bpy.types.Operator): bl_idname = "mcprep.animate_textures" bl_label = "Animate textures" - clear_cache = bpy.props.BoolProperty( + clear_cache: bpy.props.BoolProperty( default=False, name="Clear cache of previous animated sequence exports", description="Always regenerate tile files, even if tiles already exist" ) - export_location = bpy.props.EnumProperty( + export_location: bpy.props.EnumProperty( name="Save location", items=[ ("original", "Next to current source image", @@ -422,7 +422,7 @@ class MCPREP_OT_prep_animated_textures(bpy.types.Operator): "Save animation tiles next to current saved blend file")], description="Set where to export (or duplicate to) tile sequence images." ) - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'} ) @@ -499,7 +499,7 @@ def execute(self, context): {'ERROR'}, ("Detected scaled UV's (all in one texture), be sure to use " "Mineway's 'Export Individual Textures To..' feature")) - conf.log("Detected scaled UV's, incompatible with animate textures") + env.log("Detected scaled UV's, incompatible with animate textures") return {'FINISHED'} else: self.report({'INFO'}, "Modified {} material(s)".format( @@ -533,7 +533,6 @@ def process_single_material(self, context, mat): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) diff --git a/MCprep_addon/materials/skin.py b/MCprep_addon/materials/skin.py index 9a596669..1c85a685 100644 --- a/MCprep_addon/materials/skin.py +++ b/MCprep_addon/materials/skin.py @@ -24,11 +24,11 @@ import urllib.request from bpy.app.handlers import persistent -from .. import conf from . import generate from .. import tracking from .. import util +from ..conf import env # ----------------------------------------------------------------------------- # Support functions @@ -57,12 +57,12 @@ def reloadSkinList(context): # clear lists context.scene.mcprep_skins_list.clear() - conf.skin_list = [] + env.skin_list = [] # recreate for i, (skin, description) in enumerate(skinlist, 1): item = context.scene.mcprep_skins_list.add() - conf.skin_list.append( + env.skin_list.append( (skin, os.path.join(skinfolder, skin)) ) item.label = description @@ -72,7 +72,7 @@ def reloadSkinList(context): def update_skin_path(self, context): """For UI list path callback""" - conf.log("Updating rig path", vv_only=True) + env.log("Updating rig path", vv_only=True) reloadSkinList(context) @@ -82,17 +82,17 @@ def handler_skins_enablehack(scene): bpy.app.handlers.scene_update_pre.remove(handler_skins_enablehack) except: pass - conf.log("Triggering Handler_skins_load from first enable", vv_only=True) + env.log("Triggering Handler_skins_load from first enable", vv_only=True) handler_skins_load(scene) @persistent def handler_skins_load(scene): try: - conf.log("Reloading skins", vv_only=True) + env.log("Reloading skins", vv_only=True) reloadSkinList(bpy.context) except: - conf.log("Didn't run skin reloading callback", vv_only=True) + env.log("Didn't run skin reloading callback", vv_only=True) def loadSkinFile(self, context, filepath, new_material=False): @@ -141,7 +141,7 @@ def convert_skin_layout(image_file): """ if not os.path.isfile(image_file): - conf.log("Error! Image file does not exist: " + image_file) + env.log("Error! Image file does not exist: " + image_file) return False img = bpy.data.images.load(image_file) @@ -149,13 +149,13 @@ def convert_skin_layout(image_file): return False elif img.size[0] != img.size[1] * 2: # some image that isn't the normal 64x32 of old skin formats - conf.log("Unknown skin image format, not converting layout") + env.log("Unknown skin image format, not converting layout") return False elif img.size[0] / 64 != int(img.size[0] / 64): - conf.log("Non-regular scaling of skin image, can't process") + env.log("Non-regular scaling of skin image, can't process") return False - conf.log("Old image format detected, converting to post 1.8 layout") + env.log("Old image format detected, converting to post 1.8 layout") scale = int(img.size[0] / 64) has_alpha = img.channels == 4 @@ -195,7 +195,7 @@ def convert_skin_layout(image_file): end = (row * block_width * 4) + block_width + (block_width * 2.5) lower_half += upper_half[int(start):int(end)] else: - conf.log("Bad math! Should never go above 4 blocks") + env.log("Bad math! Should never go above 4 blocks") failout = True break @@ -206,7 +206,7 @@ def convert_skin_layout(image_file): new_image.pixels = new_pixels new_image.filepath_raw = image_file new_image.save() - conf.log("Saved out post 1.8 converted skin file") + env.log("Saved out post 1.8 converted skin file") # cleanup files img.user_clear() @@ -243,7 +243,7 @@ def getMatsFromSelected(selected, new_material=False): for ob in obj_list: if ob.data.library: - conf.log("Library object, skipping") + env.log("Library object, skipping") linked_objs += 1 continue elif new_material is False: @@ -283,7 +283,7 @@ def setUVimage(objs, image): if obj.type != "MESH": continue if not hasattr(obj.data, "uv_textures"): - conf.log("Called setUVimage on object with no uv_textures, 2.8?") + env.log("Called setUVimage on object with no uv_textures, 2.8?") return if obj.data.uv_textures.active is None: continue @@ -297,7 +297,7 @@ def download_user(self, context, username): Reusable function from within two common operators for downloading skin. Example link: http://minotar.net/skin/theduckcow """ - conf.log("Downloading skin: " + username) + env.log("Downloading skin: " + username) src_link = "http://minotar.net/skin/" saveloc = os.path.join( @@ -305,7 +305,7 @@ def download_user(self, context, username): username.lower() + ".png") try: - if conf.vv: + if env.very_verbose: print("Download starting with url: " + src_link + username.lower()) print("to save location: " + saveloc) urllib.request.urlretrieve(src_link + username.lower(), saveloc) @@ -351,8 +351,8 @@ def draw_item( class ListColl(bpy.types.PropertyGroup): """For asset listing""" - label = bpy.props.StringProperty() - description = bpy.props.StringProperty() + label: bpy.props.StringProperty() + description: bpy.props.StringProperty() class MCPREP_OT_swap_skin_from_file(bpy.types.Operator, ImportHelper): @@ -361,21 +361,21 @@ class MCPREP_OT_swap_skin_from_file(bpy.types.Operator, ImportHelper): bl_label = "Swap skin" bl_options = {'REGISTER', 'UNDO'} - filter_glob = bpy.props.StringProperty( + filter_glob: bpy.props.StringProperty( default="", options={'HIDDEN'}) fileselectparams = "use_filter_blender" - files = bpy.props.CollectionProperty( + files: bpy.props.CollectionProperty( type=bpy.types.PropertyGroup, options={'HIDDEN', 'SKIP_SAVE'}) - filter_image = bpy.props.BoolProperty( + filter_image: bpy.props.BoolProperty( default=True, options={'HIDDEN', 'SKIP_SAVE'}) - new_material = bpy.props.BoolProperty( + new_material: bpy.props.BoolProperty( name="New Material", description="Create a new material instead of overwriting existing one", default=True) - skipUsage = bpy.props.BoolProperty(default=False, options={'HIDDEN'}) + skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) track_function = "skin" track_param = "file import" @@ -395,15 +395,15 @@ class MCPREP_OT_apply_skin(bpy.types.Operator): bl_description = "Apply the active UV image to selected character materials" bl_options = {'REGISTER', 'UNDO'} - filepath = bpy.props.StringProperty( + filepath: bpy.props.StringProperty( name="Skin", description="selected", options={'HIDDEN'}) - new_material = bpy.props.BoolProperty( + new_material: bpy.props.BoolProperty( name="New Material", description="Create a new material instead of overwriting existing one", default=True) - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -425,25 +425,25 @@ class MCPREP_OT_apply_username_skin(bpy.types.Operator): bl_description = "Download and apply skin from specific username" bl_options = {'REGISTER', 'UNDO'} - username = bpy.props.StringProperty( + username: bpy.props.StringProperty( name="Username", description="Exact name of user to get texture from", default="") - skip_redownload = bpy.props.BoolProperty( + skip_redownload: bpy.props.BoolProperty( name="Skip download if skin already local", description="Avoid re-downloading skin and apply local file instead", default=True) - new_material = bpy.props.BoolProperty( + new_material: bpy.props.BoolProperty( name="New Material", description="Create a new material instead of overwriting existing one", default=True) - convert_layout = bpy.props.BoolProperty( + convert_layout: bpy.props.BoolProperty( name="Convert pre 1.8 skins", description=( "If an older skin layout (pre Minecraft 1.8) is detected, convert " "to new format (with clothing layers)"), default=True) - skipUsage = bpy.props.BoolProperty(default=False, options={'HIDDEN'}) + skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) def invoke(self, context, event): return context.window_manager.invoke_props_dialog( @@ -464,8 +464,8 @@ def execute(self, context): self.report({"ERROR"}, "Invalid username") return {'CANCELLED'} - skins = [str(skin[0]).lower() for skin in conf.skin_list] - paths = [skin[1] for skin in conf.skin_list] + 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: # Do the download saveloc = download_user(self, context, self.username) @@ -479,7 +479,7 @@ def execute(self, context): bpy.ops.mcprep.reload_skins() return {'FINISHED'} else: - conf.log("Reusing downloaded skin") + env.log("Reusing downloaded skin") ind = skins.index(self.username.lower()) res = loadSkinFile(self, context, paths[ind][1], self.new_material) if res != 0: @@ -510,17 +510,17 @@ class MCPREP_OT_add_skin(bpy.types.Operator, ImportHelper): bl_description = "Add a new skin to the active folder" # filename_ext = ".zip" # needs to be only tinder - filter_glob = bpy.props.StringProperty(default="*", options={'HIDDEN'}) + filter_glob: bpy.props.StringProperty(default="*", options={'HIDDEN'}) fileselectparams = "use_filter_blender" - files = bpy.props.CollectionProperty(type=bpy.types.PropertyGroup) + files: bpy.props.CollectionProperty(type=bpy.types.PropertyGroup) - convert_layout = bpy.props.BoolProperty( + convert_layout: bpy.props.BoolProperty( name="Convert pre 1.8 skins", description=( "If an older skin layout (pre Minecraft 1.8) is detected, convert " "to new format (with clothing layers)"), default=True) - skipUsage = bpy.props.BoolProperty(default=False, options={'HIDDEN'}) + skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) track_function = "add_skin" track_param = None @@ -571,7 +571,7 @@ def invoke(self, context, event): self, width=400 * util.ui_scale()) def draw(self, context): - skin_path = conf.skin_list[context.scene.mcprep_skins_list_index] + skin_path = env.skin_list[context.scene.mcprep_skins_list_index] col = self.layout.column() col.scale_y = 0.7 col.label(text="Warning, will delete file {} from".format( @@ -581,14 +581,14 @@ def draw(self, context): @tracking.report_error def execute(self, context): - if not conf.skin_list: + if not env.skin_list: self.report({"ERROR"}, "No skins loaded in memory, try reloading") return {'CANCELLED'} - if context.scene.mcprep_skins_list_index >= len(conf.skin_list): + if context.scene.mcprep_skins_list_index >= len(env.skin_list): self.report({"ERROR"}, "Indexing error") return {'CANCELLED'} - file = conf.skin_list[context.scene.mcprep_skins_list_index][-1] + file = env.skin_list[context.scene.mcprep_skins_list_index][-1] if os.path.isfile(file) is False: self.report({"ERROR"}, "Skin not found to delete") @@ -597,8 +597,8 @@ def execute(self, context): # refresh the folder bpy.ops.mcprep.reload_skins() - if context.scene.mcprep_skins_list_index >= len(conf.skin_list): - context.scene.mcprep_skins_list_index = len(conf.skin_list) - 1 + if context.scene.mcprep_skins_list_index >= len(env.skin_list): + context.scene.mcprep_skins_list_index = len(env.skin_list) - 1 # in future, select multiple self.report({"INFO"}, "Removed " + bpy.path.basename(file)) @@ -639,7 +639,7 @@ class MCPREP_OT_spawn_mob_with_skin(bpy.types.Operator): bl_label = "Spawn with skin" bl_description = "Spawn rig and apply selected skin" - relocation = bpy.props.EnumProperty( + relocation: bpy.props.EnumProperty( items=[ ('Cursor', 'Cursor', 'No relocation'), ('Clear', 'Origin', 'Move the rig to the origin'), @@ -647,22 +647,22 @@ class MCPREP_OT_spawn_mob_with_skin(bpy.types.Operator): 'Offset the root bone to curse while moving the rest pose to ' 'the origin'))], name="Relocation") - toLink = bpy.props.BoolProperty( + toLink: bpy.props.BoolProperty( name="Library Link", description="Library link instead of append the group", default=False) - clearPose = bpy.props.BoolProperty( + clearPose: bpy.props.BoolProperty( name="Clear Pose", description="Clear the pose to rest position", default=True) - skipUsage = bpy.props.BoolProperty(default=False, options={'HIDDEN'}) + skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) track_function = "spawn_with_skin" track_param = None @tracking.report_error def execute(self, context): scn_props = context.scene.mcprep_props - if not conf.skin_list: + if not env.skin_list: self.report({'ERROR'}, "No skins found") return {'CANCELLED'} @@ -678,7 +678,7 @@ def execute(self, context): # bpy.ops.mcprep.spawn_with_skin() spawn based on active mob ind = context.scene.mcprep_skins_list_index - _ = loadSkinFile(self, context, conf.skin_list[ind][1]) + _ = loadSkinFile(self, context, env.skin_list[ind][1]) return {'FINISHED'} @@ -690,24 +690,24 @@ class MCPREP_OT_download_username_list(bpy.types.Operator): bl_description = "Download a list of skins from comma-separated usernames" bl_options = {'REGISTER', 'UNDO'} - username_list = bpy.props.StringProperty( + username_list: bpy.props.StringProperty( name="Username list", description="Comma-separated list of usernames to download.", default="" ) - skip_redownload = bpy.props.BoolProperty( + skip_redownload: bpy.props.BoolProperty( name="Skip download if skin already local", description="Avoid re-downloading skin and apply local file instead", default=True ) - convert_layout = bpy.props.BoolProperty( + convert_layout: bpy.props.BoolProperty( name="Convert pre 1.8 skins", description=( "If an older skin layout (pre Minecraft 1.8) is detected, convert " "to new format (with clothing layers)"), default=True ) - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'} ) @@ -735,7 +735,7 @@ def execute(self, context): user_list = list(set(user_list)) # Make list unique. # Currently loaded - skins = [str(skin[0]).lower() for skin in conf.skin_list] + skins = [str(skin[0]).lower() for skin in env.skin_list] issue_skins = [] for username in user_list: if username.lower() not in skins or not self.skip_redownload: @@ -782,7 +782,6 @@ def execute(self, context): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) bpy.types.Scene.mcprep_skins_list = bpy.props.CollectionProperty( @@ -790,7 +789,7 @@ def register(): bpy.types.Scene.mcprep_skins_list_index = bpy.props.IntProperty(default=0) # to auto-load the skins - conf.log("Adding reload skin handler to scene", vv_only=True) + env.log("Adding reload skin handler to scene", vv_only=True) try: bpy.app.handlers.scene_update_pre.append(handler_skins_enablehack) except: diff --git a/MCprep_addon/materials/sync.py b/MCprep_addon/materials/sync.py index 86169b22..23151106 100644 --- a/MCprep_addon/materials/sync.py +++ b/MCprep_addon/materials/sync.py @@ -23,10 +23,11 @@ from bpy.app.handlers import persistent from . import generate -from .. import conf +from ..conf import env from .. import tracking from .. import util +from ..conf import env # ----------------------------------------------------------------------------- # Utilities @@ -34,8 +35,8 @@ @persistent def clear_sync_cache(scene): - conf.log("Resetting sync mat cache", vv_only=True) - conf.material_sync_cache = None + env.log("Resetting sync mat cache", vv_only=True) + env.material_sync_cache = None def get_sync_blend(context): @@ -48,21 +49,21 @@ def reload_material_sync_library(context): """Reloads the library and cache""" sync_file = get_sync_blend(context) if not os.path.isfile(sync_file): - conf.material_sync_cache = [] + env.material_sync_cache = [] return with bpy.data.libraries.load(sync_file) as (data_from, _): - conf.material_sync_cache = list(data_from.materials) - conf.log("Updated sync cache", vv_only=True) + env.material_sync_cache = list(data_from.materials) + env.log("Updated sync cache", vv_only=True) def material_in_sync_library(mat_name, context): """Returns true if the material is in the sync mat library blend file""" - if conf.material_sync_cache is None: + if env.material_sync_cache is None: reload_material_sync_library(context) - if util.nameGeneralize(mat_name) in conf.material_sync_cache: + if util.nameGeneralize(mat_name) in env.material_sync_cache: return True - elif mat_name in conf.material_sync_cache: + elif mat_name in env.material_sync_cache: return True return False @@ -81,9 +82,9 @@ def sync_material(context, source_mat, sync_mat_name, link, replace): 0 if nothing modified, 1 if modified None if no error or string if error """ - if sync_mat_name in conf.material_sync_cache: + if sync_mat_name in env.material_sync_cache: import_name = sync_mat_name - elif util.nameGeneralize(sync_mat_name) in conf.material_sync_cache: + elif util.nameGeneralize(sync_mat_name) in env.material_sync_cache: import_name = util.nameGeneralize(sync_mat_name) # if link is true, check library material not already linked @@ -118,21 +119,21 @@ class MCPREP_OT_sync_materials(bpy.types.Operator): bl_label = "Sync Materials" bl_options = {'REGISTER', 'UNDO'} - selected = bpy.props.BoolProperty( + selected: bpy.props.BoolProperty( name="Only selected", description=( "Affect only the materials on selected objects, otherwise " "sync all materials in blend file"), default=True) - link = bpy.props.BoolProperty( + link: bpy.props.BoolProperty( name="Link", description="Link instead of appending material", default=False) - replace_materials = bpy.props.BoolProperty( + replace_materials: bpy.props.BoolProperty( name="Replace", description="Delete the local materials being synced, where matched", default=False) - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -197,7 +198,7 @@ def execute(self, context): last_err = err if last_err: - conf.log("Most recent error during sync:" + str(last_err)) + env.log("Most recent error during sync:" + str(last_err)) # Re-establish initial state, as append material clears selections for obj in inital_selection: @@ -265,7 +266,6 @@ def execute(self, context): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) bpy.app.handlers.load_post.append(clear_sync_cache) diff --git a/MCprep_addon/materials/uv_tools.py b/MCprep_addon/materials/uv_tools.py index 1a634cbc..308f4521 100644 --- a/MCprep_addon/materials/uv_tools.py +++ b/MCprep_addon/materials/uv_tools.py @@ -21,7 +21,7 @@ import time -from .. import conf +from ..conf import env from . import generate from .. import tracking from .. import util @@ -98,7 +98,7 @@ def detect_invalid_uvs_from_objs(obj_list): # rightfully not up against bounds of image. But generally, for combined # images the area covered is closer to 0.05 thresh = 0.25 - conf.log("Doing check for invalid UV faces", vv_only=True) + env.log("Doing check for invalid UV faces", vv_only=True) t0 = time.time() for obj in obj_list: @@ -122,7 +122,7 @@ def detect_invalid_uvs_from_objs(obj_list): invalid_objects.append(obj) t1 = time.time() t_diff = t1 - t0 # round to .1s - conf.log("UV check took {}s".format(t_diff), vv_only=True) + env.log("UV check took {}s".format(t_diff), vv_only=True) return invalid, invalid_objects @@ -137,9 +137,9 @@ class MCPREP_OT_scale_uv(bpy.types.Operator): bl_description = "Scale all selected UV faces. See F6 or redo-last panel to adjust factor" bl_options = {'REGISTER', 'UNDO'} - scale = bpy.props.FloatProperty(default=0.75, name="Scale") - selected_only = bpy.props.BoolProperty(default=True, name="Seleced only") - skipUsage = bpy.props.BoolProperty(default=False, options={'HIDDEN'}) + scale: bpy.props.FloatProperty(default=0.75, name="Scale") + selected_only: bpy.props.BoolProperty(default=True, name="Seleced only") + skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) @classmethod def poll(cls, context): @@ -187,7 +187,7 @@ def execute(self, context): if ret is not None: self.report({'ERROR'}, ret) - conf.log("Error, " + ret) + env.log("Error, " + ret) return {'CANCELLED'} return {'FINISHED'} @@ -237,17 +237,17 @@ class MCPREP_OT_select_alpha_faces(bpy.types.Operator): bl_description = "Select or delete transparent UV faces of a mesh" bl_options = {'REGISTER', 'UNDO'} - delete = bpy.props.BoolProperty( + delete: bpy.props.BoolProperty( name="Delete faces", description="Delete detected transparent mesh faces", default=False) - threshold = bpy.props.FloatProperty( + threshold: bpy.props.FloatProperty( name="Threshold", description="How transparent pixels need to be to select", default=0.2, min=0.0, max=1.0) - skipUsage = bpy.props.BoolProperty(default=False, options={'HIDDEN'}) + skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) @classmethod def poll(cls, context): @@ -285,7 +285,7 @@ def execute(self, context): return {"CANCELLED"} if not ret and self.delete: - conf.log("Delet faces") + env.log("Delet faces") bpy.ops.mesh.delete(type='FACE') return {"FINISHED"} @@ -293,7 +293,7 @@ def execute(self, context): def select_alpha(self, ob, threshold): """Core function to select alpha faces based on active material/image.""" if not ob.material_slots: - conf.log("No materials, skipping.") + env.log("No materials, skipping.") return "No materials" # pre-cache the materials and their respective images for comparing @@ -309,7 +309,7 @@ def select_alpha(self, ob, threshold): continue elif image.channels != 4: textures.append(None) # no alpha channel anyways - conf.log("No alpha channel for: " + image.name) + env.log("No alpha channel for: " + image.name) continue textures.append(image) data = [None for tex in textures] @@ -321,7 +321,7 @@ def select_alpha(self, ob, threshold): fnd = f.material_index image = textures[fnd] if not image: - conf.log("Could not get image from face's material") + env.log("Could not get image from face's material") return "Could not get image from face's material" # lazy load alpha part of image to memory, hold for whole operator @@ -348,7 +348,7 @@ def select_alpha(self, ob, threshold): xmax = round(max(xlist) * image.size[0]) - 0.5 ymin = round(min(ylist) * image.size[1]) - 0.5 ymax = round(max(ylist) * image.size[1]) - 0.5 - conf.log(["\tSet size:", xmin, xmax, ymin, ymax], vv_only=True) + env.log(["\tSet size:", xmin, xmax, ymin, ymax], vv_only=True) # assuming faces are roughly rectangular, sum pixels a face covers asum = 0 @@ -390,7 +390,6 @@ def select_alpha(self, ob, threshold): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) diff --git a/MCprep_addon/mcprep_ui.py b/MCprep_addon/mcprep_ui.py index 4b24b4a1..b29e5f76 100644 --- a/MCprep_addon/mcprep_ui.py +++ b/MCprep_addon/mcprep_ui.py @@ -37,12 +37,13 @@ from .spawner import meshswap from .spawner import mobs from .spawner import spawn_util +from .conf import env # from .import_bridge import bridge -# blender 2.7 vs 2.8 icon selections -LOAD_FACTORY = 'LOOP_BACK' if util.bv28() else 'LOAD_FACTORY' -HAND_ICON = 'FILE_REFRESH' if util.bv28() else 'HAND' -OPT_IN = 'URL' if util.bv28() else 'HAND' +# blender 2.8 icon selections +LOAD_FACTORY = 'LOOP_BACK' +HAND_ICON = 'FILE_REFRESH' +OPT_IN = 'URL' def addon_just_updated(): @@ -63,9 +64,9 @@ def addon_just_updated(): # hasn't been renamed yet to mcprep_data.json, which happens on init after # an install/update) check_interval = 5 # Time in seconds - if time.time() - check_interval > conf.last_check_for_updated: + if time.time() - check_interval > env.last_check_for_updated: check_for_updated_files() - conf.last_check_for_updated = time.time() + env.last_check_for_updated = time.time() return @@ -81,7 +82,7 @@ def check_for_updated_files(): This covers the scenario where someone used the native blender install addon *instead* of the auto updater route to update the addon. """ - if os.path.isfile(conf.json_path_update): + if os.path.isfile(env.json_path_update): addon_updater_ops.updater.json["just_updated"] = True @@ -126,12 +127,12 @@ def draw(self, context): # show icon if available mob = scn_props.mob_list_all[mobkey] icn = "mob-{}".format(mob.index) - if conf.use_icons and icn in conf.preview_collections["mobs"]: + if env.use_icons and icn in env.preview_collections["mobs"]: ops = layout.operator( "mcprep.mob_spawner", text=mob.name, - icon_value=conf.preview_collections["mobs"][icn].icon_id) - elif conf.use_icons: + icon_value=env.preview_collections["mobs"][icn].icon_id) + elif env.use_icons: ops = layout.operator( "mcprep.mob_spawner", text=mob.name, icon="BLANK1") else: @@ -139,7 +140,7 @@ def draw(self, context): ops.mcmob_type = mob.mcmob_type # Skip prep materials in case of unique shader. - if conf.json_data and mob.name in conf.json_data.get("mob_skip_prep", []): + if env.json_data and mob.name in env.json_data.get("mob_skip_prep", []): ops.prep_materials = False @@ -168,7 +169,7 @@ def draw(self, context): opr.location = util.get_cuser_location(context) # Ensure meshswap with rigs is made real, so the rigs can be used. - if conf.json_data and blockset[1] in conf.json_data.get("make_real", []): + if env.json_data and blockset[1] in env.json_data.get("make_real", []): opr.make_real = True @@ -183,11 +184,11 @@ def draw(self, context): layout.label(text="No items found!") for item in context.scene.mcprep_props.item_list: icn = "item-{}".format(item.index) - if conf.use_icons and icn in conf.preview_collections["items"]: + if env.use_icons and icn in env.preview_collections["items"]: ops = layout.operator( "mcprep.spawn_item", text=item.name, - icon_value=conf.preview_collections["items"][icn].icon_id) - elif conf.use_icons: + icon_value=env.preview_collections["items"][icn].icon_id) + elif env.use_icons: ops = layout.operator( "mcprep.spawn_item", text=item.name, icon="BLANK1") else: @@ -223,11 +224,11 @@ def draw(self, context): ops.frame = context.scene.frame_current elif effect.effect_type == effects.IMG_SEQ: icon = "effects-{}".format(effect.index) - if conf.use_icons and icon in conf.preview_collections["effects"]: + if env.use_icons and icon in env.preview_collections["effects"]: ops = col.operator( "mcprep.spawn_instant_effect", text=effect.name, - icon_value=conf.preview_collections["effects"][icon].icon_id) + icon_value=env.preview_collections["effects"][icon].icon_id) else: ops = col.operator( "mcprep.spawn_instant_effect", @@ -268,9 +269,6 @@ class MCPREP_MT_model_spawn(bpy.types.Menu): def draw(self, context): layout = self.layout - if not util.bv28(): - layout.label(text="Requires blender 2.8 or newer") - return if not context.scene.mcprep_props.model_list: layout.label(text="No models found!") for model in context.scene.mcprep_props.model_list: @@ -293,13 +291,13 @@ def draw(self, context): layout = self.layout props = context.scene.mcprep_props - if conf.preview_collections["main"] != "": - spawner_icon = conf.preview_collections["main"].get("spawner_icon") - meshswap_icon = conf.preview_collections["main"].get("meshswap_icon") - sword_icon = conf.preview_collections["main"].get("sword_icon") - effects_icon = conf.preview_collections["main"].get("effects_icon") - entity_icon = conf.preview_collections["main"].get("entity_icon") - model_icon = conf.preview_collections["main"].get("model_icon") + if env.preview_collections["main"] != "": + spawner_icon = env.preview_collections["main"].get("spawner_icon") + meshswap_icon = env.preview_collections["main"].get("meshswap_icon") + sword_icon = env.preview_collections["main"].get("sword_icon") + effects_icon = env.preview_collections["main"].get("effects_icon") + entity_icon = env.preview_collections["main"].get("entity_icon") + model_icon = env.preview_collections["main"].get("model_icon") else: spawner_icon = None meshswap_icon = None @@ -309,7 +307,7 @@ def draw(self, context): model_icon = None all_loaded = props.mob_list and props.meshswap_list and props.item_list - if not conf.loaded_all_spawners and not all_loaded: + if not env.loaded_all_spawners and not all_loaded: row = layout.row() row.operator( "mcprep.reload_spawners", text="Load spawners", icon=HAND_ICON) @@ -374,50 +372,50 @@ class McprepPreference(bpy.types.AddonPreferences): scriptdir = bpy.path.abspath(os.path.dirname(__file__)) def change_verbose(self, context): - conf.v = self.verbose + env.verbose = self.verbose - meshswap_path = bpy.props.StringProperty( + meshswap_path: bpy.props.StringProperty( name="Meshswap path", description=( "Default path to the meshswap asset file, for " "meshswapable objects and groups"), subtype='FILE_PATH', default=scriptdir + "/MCprep_resources/mcprep_meshSwap.blend") - entity_path = bpy.props.StringProperty( + entity_path: bpy.props.StringProperty( name="Entity path", description="Default path to the entity asset file, for entities", subtype='FILE_PATH', default=os.path.join(scriptdir, "MCprep_resources", "mcprep_entities.blend")) - mob_path = bpy.props.StringProperty( + mob_path: bpy.props.StringProperty( name="Mob path", description="Default folder for rig loads/spawns in new blender instances", subtype='DIR_PATH', default=scriptdir + "/MCprep_resources/rigs/") - custom_texturepack_path = bpy.props.StringProperty( + custom_texturepack_path: bpy.props.StringProperty( name="Texture pack path", description=( "Path to a folder containing resources and textures to use " "with material prepping"), subtype='DIR_PATH', default=scriptdir + "/MCprep_resources/resourcepacks/mcprep_default/") - skin_path = bpy.props.StringProperty( + skin_path: bpy.props.StringProperty( name="Skin path", description="Folder for skin textures, used in skin swapping", subtype='DIR_PATH', default=scriptdir + "/MCprep_resources/skins/") - effects_path = bpy.props.StringProperty( + effects_path: bpy.props.StringProperty( name="Effects path", description="Folder for effects blend files and assets", subtype='DIR_PATH', default=scriptdir + "/MCprep_resources/effects/") - world_obj_path = bpy.props.StringProperty( + world_obj_path: bpy.props.StringProperty( name="World Folder", description=( "Default folder for opening world objs from programs " "like jmc2obj or Mineways"), subtype='DIR_PATH', default="//") - MCprep_groupAppendLayer = bpy.props.IntProperty( + MCprep_groupAppendLayer: bpy.props.IntProperty( name="Group Append Layer", description=( "When groups are appended instead of linked, " @@ -426,43 +424,43 @@ def change_verbose(self, context): min=0, max=20, default=20) - MCprep_exporter_type = bpy.props.EnumProperty( + MCprep_exporter_type: bpy.props.EnumProperty( items=[ ('(choose)', '(choose)', 'Select your exporter'), ('jmc2obj', 'jmc2obj', 'Select if exporter used was jmc2obj'), ('Mineways', 'Mineways', 'Select if exporter used was Mineways')], name="Exporter") - preferences_tab = bpy.props.EnumProperty( + preferences_tab: bpy.props.EnumProperty( items=[ ('settings', 'Settings', 'Change MCprep settings'), ('tutorials', 'Tutorials', 'View MCprep tutorials & other help'), ('tracker_updater', 'Tracking/Updater', 'Change tracking and updating settings')], name="Exporter") - verbose = bpy.props.BoolProperty( + verbose: bpy.props.BoolProperty( name="Verbose logging", description="Print out more information in the console", default=False, update=change_verbose) - open_jmc2obj_path = bpy.props.StringProperty( + open_jmc2obj_path: bpy.props.StringProperty( name="jmc2obj path", description="Path to the jmc2obj executable", subtype='FILE_PATH', default="jmc2obj.jar") - open_mineways_path = bpy.props.StringProperty( + open_mineways_path: bpy.props.StringProperty( name="Mineways path", description="Path to the Mineways executable", subtype='FILE_PATH', update=mineways_update, default="Mineways") - save_folder = bpy.props.StringProperty( + save_folder: bpy.props.StringProperty( name="MC saves folder", description=( "Folder containing Minecraft world saves directories, " "for the direct import bridge"), subtype='FILE_PATH', default='') - feature_set = bpy.props.EnumProperty( + feature_set: bpy.props.EnumProperty( items=[ ('supported', 'Supported', 'Use only supported features'), ('experimental', 'Experimental', 'Enable experimental features')], @@ -471,31 +469,31 @@ def change_verbose(self, context): # addon updater preferences - auto_check_update = bpy.props.BoolProperty( + auto_check_update: bpy.props.BoolProperty( name="Auto-check for Update", description="If enabled, auto-check for updates using an interval", default=True, ) - updater_interval_months = bpy.props.IntProperty( + updater_interval_months: bpy.props.IntProperty( name='Months', description="Number of months between checking for updates", default=0, min=0 ) - updater_interval_days = bpy.props.IntProperty( + updater_interval_days: bpy.props.IntProperty( name='Days', description="Number of days between checking for updates", default=1, min=0, ) - updater_interval_hours = bpy.props.IntProperty( + updater_interval_hours: bpy.props.IntProperty( name='Hours', description="Number of hours between checking for updates", default=0, min=0, max=23 ) - updater_interval_minutes = bpy.props.IntProperty( + updater_interval_minutes: bpy.props.IntProperty( name='Minutes', description="Number of minutes between checking for updates", default=0, @@ -509,8 +507,6 @@ def draw(self, context): row.prop(self, "preferences_tab", expand=True) factor_width = 0.3 - if util.bv28(): - factor_width = 0.3 if self.preferences_tab == "settings": @@ -721,15 +717,13 @@ def draw(self, context): # updater draw function addon_updater_ops.update_settings_ui(self, context) - if not util.bv28(): - layout.label(text="Don't forget to save user preferences!") class MCPREP_PT_world_imports(bpy.types.Panel): """World importing related settings and tools""" bl_label = "World Imports" bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' if not util.bv28() else 'UI' + bl_region_type = 'UI' # bl_context = "objectmode" bl_category = "MCprep" @@ -769,14 +763,9 @@ def draw(self, context): row.operator("mcprep.open_jmc2obj") wpath = addon_prefs.world_obj_path - if util.bv28(): - # custom operator for splitting via mats after importing - col.operator( - "mcprep.import_world_split", - text="OBJ world import").filepath = wpath - else: - col.operator( - "import_scene.obj", text="OBJ world import").filepath = wpath + col.operator( + "mcprep.import_world_split", + text="OBJ world import").filepath = wpath split = layout.split() col = split.column(align=True) @@ -803,22 +792,13 @@ def draw(self, context): col = split.column(align=True) # Indicate whether UI can be improved or not. - view27 = ['TEXTURED', 'MATEIRAL', 'RENDERED'] view28 = ['SOLID', 'MATERIAL', 'RENDERED'] + + improved_28 = util.viewport_textured(context) is True + improved_28 &= context.scene.display.shading.type in view28 + improved_28 &= context.scene.display.shading.background_type == "WORLD" - improved_27 = not util.bv28() - if improved_27: - improved_27 &= util.viewport_textured(context) is True - improved_27 &= context.space_data.viewport_shade in view27 - improved_27 &= util.get_preferences(context).system.use_mipmaps is False - - improved_28 = util.bv28() if improved_28: - improved_28 &= util.viewport_textured(context) is True - improved_28 &= context.scene.display.shading.type in view28 - improved_28 &= context.scene.display.shading.background_type == "WORLD" - - if improved_27 or improved_28: row = col.row(align=True) row.enabled = False row.operator( @@ -829,15 +809,14 @@ def draw(self, context): "mcprep.improve_ui", text="Improve UI", icon='SETTINGS') # Optimizer Panel (only for blender 2.80+) - if util.min_bv((2, 80)): + row = col.row(align=True) + icon = "TRIA_DOWN" if scn_props.show_settings_optimizer else "TRIA_RIGHT" + row.prop( + scn_props, "show_settings_optimizer", + text="Cycles Optimizer", icon=icon) + if scn_props.show_settings_optimizer: row = col.row(align=True) - icon = "TRIA_DOWN" if scn_props.show_settings_optimizer else "TRIA_RIGHT" - row.prop( - scn_props, "show_settings_optimizer", - text="Cycles Optimizer", icon=icon) - if scn_props.show_settings_optimizer: - row = col.row(align=True) - optimize_scene.panel_draw(context, row) + optimize_scene.panel_draw(context, row) # Advanced settings. row = col.row(align=True) @@ -897,7 +876,7 @@ class MCPREP_PT_bridge(bpy.types.Panel): """MCprep panel for directly importing and reloading minecraft saves""" bl_label = "World Bridge" bl_space_type = "VIEW_3D" - bl_region_type = 'TOOLS' if not util.bv28() else 'UI' + bl_region_type = 'UI' bl_context = "objectmode" bl_category = "MCprep" @@ -914,7 +893,7 @@ class MCPREP_PT_world_tools(bpy.types.Panel): """World settings and tools""" bl_label = "World Tools" bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' if not util.bv28() else 'UI' + bl_region_type = 'UI' bl_category = "MCprep" def draw(self, context): @@ -969,7 +948,7 @@ class MCPREP_PT_skins(bpy.types.Panel): """MCprep panel for skin swapping""" bl_label = "Skin Swapper" bl_space_type = 'VIEW_3D' - bl_region_type = 'TOOLS' if not util.bv28() else 'UI' + bl_region_type = 'UI' bl_category = "MCprep" def draw(self, context): @@ -993,18 +972,18 @@ def draw(self, context): row = layout.row() col = row.column() - is_sortable = len(conf.skin_list) > 1 + is_sortable = len(env.skin_list) > 1 rows = 1 if (is_sortable): rows = 4 # any other conditions for needing reloading? - if not conf.skin_list: + 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 conf.skin_list and len(conf.skin_list) <= sind: + elif env.skin_list and len(env.skin_list) <= sind: col = layout.column() col.label(text="Reload skins") p = col.operator( @@ -1020,10 +999,10 @@ def draw(self, context): row = col.row(align=True) row.scale_y = 1.5 - if conf.skin_list: - skinname = bpy.path.basename(conf.skin_list[sind][0]) + if env.skin_list: + skinname = bpy.path.basename(env.skin_list[sind][0]) p = row.operator("mcprep.applyskin", text="Apply " + skinname) - p.filepath = conf.skin_list[sind][1] + p.filepath = env.skin_list[sind][1] else: row.enabled = False p = row.operator("mcprep.skin_swapper", text="No skins found") @@ -1066,7 +1045,7 @@ def draw(self, context): row.enabled = False row.operator( "mcprep.spawn_with_skin", text="Reload mobs below") - elif not conf.skin_list: + elif not env.skin_list: row.enabled = False row.operator( "mcprep.spawn_with_skin", text="Reload skins above") @@ -1215,7 +1194,7 @@ def mob_spawner(self, context): p.mcmob_type = mcmob_type # Skip prep materials in case of unique shader. - if conf.json_data and name in conf.json_data.get("mob_skip_prep", []): + if env.json_data and name in env.json_data.get("mob_skip_prep", []): p.prep_materials = False p = col.operator("mcprep.mob_install_menu") @@ -1255,7 +1234,7 @@ def mob_spawner(self, context): b_col.operator("mcprep.mob_install_icon") else: icon_index = scn_props.mob_list[scn_props.mob_list_index].index - if "mob-{}".format(icon_index) in conf.preview_collections["mobs"]: + if "mob-{}".format(icon_index) in env.preview_collections["mobs"]: b_col.operator( "mcprep.mob_install_icon", text="Change mob icon") else: @@ -1321,7 +1300,7 @@ def meshswap_spawner(self, context): p.method = method p.location = util.get_cuser_location(context) # Ensure meshswap with rigs is made real, so the rigs can be used. - if conf.json_data and block in conf.json_data.get("make_real", []): + if env.json_data and block in env.json_data.get("make_real", []): p.make_real = True else: @@ -1361,7 +1340,7 @@ def meshswap_spawner(self, context): def item_spawner(self, context): """Code for drawing the item spawner""" scn_props = context.scene.mcprep_props - + layout = self.layout layout.label(text="Generate items from textures") split = layout.split() @@ -1513,9 +1492,6 @@ def model_spawner(self, context): layout = self.layout layout.label(text="Generate models from .json files") - if not util.bv28(): - layout.label(text="Requires blender 2.8 or newer") - return split = layout.split() col = split.column(align=True) @@ -1692,7 +1668,7 @@ class MCPREP_PT_spawn(bpy.types.Panel): """MCprep panel for mob spawning""" bl_label = "Spawner" bl_space_type = "VIEW_3D" - bl_region_type = 'TOOLS' if not util.bv28() else 'UI' + bl_region_type = 'UI' bl_category = "MCprep" def draw(self, context): @@ -1712,7 +1688,7 @@ class MCPREP_PT_mob_spawner(bpy.types.Panel): bl_label = "Mob spawner" bl_parent_id = "MCPREP_PT_spawn" bl_space_type = "VIEW_3D" - bl_region_type = 'TOOLS' if not util.bv28() else 'UI' + bl_region_type = 'UI' bl_category = "MCprep" bl_options = {'DEFAULT_CLOSED'} @@ -1727,9 +1703,9 @@ def draw(self, context): mob_spawner(self, context) def draw_header(self, context): - if not conf.use_icons or conf.preview_collections["main"] == "": + if not env.use_icons or env.preview_collections["main"] == "": return - icon = conf.preview_collections["main"].get("spawner_icon") + icon = env.preview_collections["main"].get("spawner_icon") if not icon: return self.layout.label(text="", icon_value=icon.icon_id) @@ -1740,7 +1716,7 @@ class MCPREP_PT_model_spawner(bpy.types.Panel): bl_label = "Block (model) spawner" bl_parent_id = "MCPREP_PT_spawn" bl_space_type = "VIEW_3D" - bl_region_type = 'TOOLS' if not util.bv28() else 'UI' + bl_region_type = 'UI' bl_category = "MCprep" bl_options = {'DEFAULT_CLOSED'} @@ -1755,9 +1731,9 @@ def draw(self, context): model_spawner(self, context) def draw_header(self, context): - if not conf.use_icons or conf.preview_collections["main"] == "": + if not env.use_icons or env.preview_collections["main"] == "": return - icon = conf.preview_collections["main"].get("model_icon") + icon = env.preview_collections["main"].get("model_icon") if not icon: return self.layout.label(text="", icon_value=icon.icon_id) @@ -1768,7 +1744,7 @@ class MCPREP_PT_item_spawner(bpy.types.Panel): bl_label = "Item spawner" bl_parent_id = "MCPREP_PT_spawn" bl_space_type = "VIEW_3D" - bl_region_type = 'TOOLS' if not util.bv28() else 'UI' + bl_region_type = 'UI' bl_category = "MCprep" bl_options = {'DEFAULT_CLOSED'} @@ -1784,9 +1760,9 @@ def draw(self, context): item_spawner(self, context) def draw_header(self, context): - if not conf.use_icons or conf.preview_collections["main"] == "": + if not env.use_icons or env.preview_collections["main"] == "": return - icon = conf.preview_collections["main"].get("sword_icon") + icon = env.preview_collections["main"].get("sword_icon") if not icon: return self.layout.label(text="", icon_value=icon.icon_id) @@ -1797,7 +1773,7 @@ class MCPREP_PT_effects_spawner(bpy.types.Panel): bl_label = "Effects + weather" bl_parent_id = "MCPREP_PT_spawn" bl_space_type = "VIEW_3D" - bl_region_type = 'TOOLS' if not util.bv28() else 'UI' + bl_region_type = 'UI' bl_category = "MCprep" bl_options = {'DEFAULT_CLOSED'} @@ -1812,9 +1788,9 @@ def draw(self, context): effects_spawner(self, context) def draw_header(self, context): - if not conf.use_icons or conf.preview_collections["main"] == "": + if not env.use_icons or env.preview_collections["main"] == "": return - icon = conf.preview_collections["main"].get("effects_icon") + icon = env.preview_collections["main"].get("effects_icon") if not icon: return self.layout.label(text="", icon_value=icon.icon_id) @@ -1825,7 +1801,7 @@ class MCPREP_PT_entity_spawner(bpy.types.Panel): bl_label = "Entity spawner" bl_parent_id = "MCPREP_PT_spawn" bl_space_type = "VIEW_3D" - bl_region_type = 'TOOLS' if not util.bv28() else 'UI' + bl_region_type = 'UI' bl_category = "MCprep" bl_options = {'DEFAULT_CLOSED'} @@ -1840,9 +1816,9 @@ def draw(self, context): entity_spawner(self, context) def draw_header(self, context): - if not conf.use_icons or conf.preview_collections["main"] == "": + if not env.use_icons or env.preview_collections["main"] == "": return - icon = conf.preview_collections["main"].get("entity_icon") + icon = env.preview_collections["main"].get("entity_icon") if not icon: return self.layout.label(text="", icon_value=icon.icon_id) @@ -1853,7 +1829,7 @@ class MCPREP_PT_meshswap_spawner(bpy.types.Panel): bl_label = "Meshswap spawner" bl_parent_id = "MCPREP_PT_spawn" bl_space_type = "VIEW_3D" - bl_region_type = 'TOOLS' if not util.bv28() else 'UI' + bl_region_type = 'UI' bl_category = "MCprep" bl_options = {'DEFAULT_CLOSED'} @@ -1868,9 +1844,9 @@ def draw(self, context): meshswap_spawner(self, context) def draw_header(self, context): - if not conf.use_icons or conf.preview_collections["main"] == "": + if not env.use_icons or env.preview_collections["main"] == "": return - icon = conf.preview_collections["main"].get("meshswap_icon") + icon = env.preview_collections["main"].get("meshswap_icon") if not icon: return self.layout.label(text="", icon_value=icon.icon_id) @@ -1885,7 +1861,7 @@ def draw_header(self, context): def draw_mcprepadd(self, context): """Append to Shift+A, icon for top-level MCprep section.""" layout = self.layout - pcoll = conf.preview_collections["main"] + pcoll = env.preview_collections["main"] if pcoll != "": my_icon = pcoll["crafting_icon"] layout.menu(MCPREP_MT_3dview_add.bl_idname, icon_value=my_icon.icon_id) @@ -1918,8 +1894,8 @@ def mcprep_image_tools(self, context): txt = "Spawn as item" if not img: row.enabled = False - if conf.preview_collections["main"] != "": - sword_icon = conf.preview_collections["main"]["sword_icon"] + if env.preview_collections["main"] != "": + sword_icon = env.preview_collections["main"]["sword_icon"] else: sword_icon = None @@ -1940,38 +1916,38 @@ class McprepProps(bpy.types.PropertyGroup): """Properties saved to an individual scene""" # not available here - addon_prefs = util.get_user_preferences() + # addon_prefs = util.get_user_preferences() # depreciated, keeping to prevent re-registration errors - show_settings_material = bpy.props.BoolProperty( + show_settings_material: bpy.props.BoolProperty( name="show material settings", description="Show extra MCprep panel settings", default=False) - show_settings_skin = bpy.props.BoolProperty( + show_settings_skin: bpy.props.BoolProperty( name="show skin settings", description="Show extra MCprep panel settings", default=False) - show_settings_optimizer = bpy.props.BoolProperty( + show_settings_optimizer: bpy.props.BoolProperty( name="show optimizer settings", description="Show extra MCprep panel settings", default=False) - show_settings_spawner = bpy.props.BoolProperty( + show_settings_spawner: bpy.props.BoolProperty( name="show spawner settings", description="Show extra MCprep panel settings", default=False) - show_settings_effect = bpy.props.BoolProperty( + show_settings_effect: bpy.props.BoolProperty( name="show effect settings", description="Show extra MCprep panel settings", default=False) # Rig settings - spawn_rig_category = bpy.props.EnumProperty( + spawn_rig_category: bpy.props.EnumProperty( name="Mob category", description="Category of mobs & character rigs to spawn", update=mobs.spawn_rigs_category_load, items=mobs.spawn_rigs_categories ) - spawn_mode = bpy.props.EnumProperty( + spawn_mode: bpy.props.EnumProperty( name="Spawn Mode", description="Set mode for rig/object spawner", items=[ @@ -1983,28 +1959,28 @@ class McprepProps(bpy.types.PropertyGroup): ) # spawn lists - mob_list = bpy.props.CollectionProperty(type=spawn_util.ListMobAssets) - mob_list_index = bpy.props.IntProperty(default=0) - mob_list_all = bpy.props.CollectionProperty( + mob_list: bpy.props.CollectionProperty(type=spawn_util.ListMobAssets) + mob_list_index: bpy.props.IntProperty(default=0) + mob_list_all: bpy.props.CollectionProperty( type=spawn_util.ListMobAssetsAll) - meshswap_list = bpy.props.CollectionProperty( + meshswap_list: bpy.props.CollectionProperty( type=spawn_util.ListMeshswapAssets) - meshswap_list_index = bpy.props.IntProperty(default=0) - item_list = bpy.props.CollectionProperty(type=spawn_util.ListItemAssets) - item_list_index = bpy.props.IntProperty(default=0) - material_list = bpy.props.CollectionProperty( + meshswap_list_index: bpy.props.IntProperty(default=0) + item_list: bpy.props.CollectionProperty(type=spawn_util.ListItemAssets) + item_list_index: bpy.props.IntProperty(default=0) + material_list: bpy.props.CollectionProperty( type=material_manager.ListMaterials) - material_list_index = bpy.props.IntProperty(default=0) - entity_list = bpy.props.CollectionProperty(type=spawn_util.ListEntityAssets) - entity_list_index = bpy.props.IntProperty(default=0) - model_list = bpy.props.CollectionProperty(type=spawn_util.ListModelAssets) - model_list_index = bpy.props.IntProperty(default=0) + material_list_index: bpy.props.IntProperty(default=0) + entity_list: bpy.props.CollectionProperty(type=spawn_util.ListEntityAssets) + entity_list_index: bpy.props.IntProperty(default=0) + model_list: bpy.props.CollectionProperty(type=spawn_util.ListModelAssets) + model_list_index: bpy.props.IntProperty(default=0) # Effects are uniqune in that they are loaded into a list structure, # but the UI list itself is not directly displayed. Rather, dropdowns # will iterate over this to populate based on type. - effects_list = bpy.props.CollectionProperty(type=spawn_util.ListEffectsAssets) - effects_list_index = bpy.props.IntProperty(default=0) + effects_list: bpy.props.CollectionProperty(type=spawn_util.ListEffectsAssets) + effects_list_index: bpy.props.IntProperty(default=0) # ----------------------------------------------------------------------------- @@ -2040,7 +2016,6 @@ class McprepProps(bpy.types.PropertyGroup): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) bpy.types.Scene.mcprep_props = bpy.props.PointerProperty(type=McprepProps) @@ -2092,16 +2067,13 @@ def register(): update=update_mcprep_texturepack_path, default=addon_prefs.custom_texturepack_path) - conf.v = addon_prefs.verbose - if hasattr(bpy.types, "INFO_MT_add"): # 2.7 - bpy.types.INFO_MT_add.append(draw_mcprepadd) - elif hasattr(bpy.types, "VIEW3D_MT_add"): # 2.8 + env.verbose = addon_prefs.verbose + if hasattr(bpy.types, "VIEW3D_MT_add"): # 2.8 bpy.types.VIEW3D_MT_add.append(draw_mcprepadd) - if hasattr(bpy.types, "IMAGE_PT_tools_transform_uvs"): # 2.7 only - bpy.types.IMAGE_PT_tools_transform_uvs.append(mcprep_uv_tools) if hasattr(bpy.types, "IMAGE_MT_uvs"): # 2.8 *and* 2.7 # this is a dropdown menu for UVs, not a panel + env.log("IMAGE_MT_uvs registration!") bpy.types.IMAGE_MT_uvs.append(mcprep_uv_tools) # bpy.types.IMAGE_MT_image.append(mcprep_image_tools) # crashes, re-do ops @@ -2110,13 +2082,9 @@ def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) - if hasattr(bpy.types, "INFO_MT_add"): # 2.7 - bpy.types.INFO_MT_add.remove(draw_mcprepadd) elif hasattr(bpy.types, "VIEW3D_MT_add"): # 2.8 bpy.types.VIEW3D_MT_add.remove(draw_mcprepadd) - if hasattr(bpy.types, "IMAGE_PT_tools_transform_uvs"): # 2.7 - bpy.types.IMAGE_PT_tools_transform_uvs.remove(mcprep_uv_tools) if hasattr(bpy.types, "IMAGE_MT_uvs"): # 2.8 *and* 2.7 bpy.types.IMAGE_MT_uvs.remove(mcprep_uv_tools) # bpy.types.IMAGE_MT_image.remove(mcprep_image_tools) diff --git a/MCprep_addon/optimize_scene.py b/MCprep_addon/optimize_scene.py index e09f11d7..b46b3d12 100644 --- a/MCprep_addon/optimize_scene.py +++ b/MCprep_addon/optimize_scene.py @@ -46,34 +46,34 @@ def scene_brightness(self, context): ] return itms - caustics_bool = bpy.props.BoolProperty( + caustics_bool: bpy.props.BoolProperty( name="Caustics (slower)", default=False, description="If checked allows cautics to be enabled" ) - motion_blur_bool = bpy.props.BoolProperty( + motion_blur_bool: bpy.props.BoolProperty( name="Motion Blur (slower)", default=False, description="If checked allows motion blur to be enabled" ) - scene_brightness = bpy.props.EnumProperty( + scene_brightness: bpy.props.EnumProperty( name="", description="Brightness of the scene: Affects how the optimizer adjusts sampling", items=scene_brightness ) - quality_vs_speed = bpy.props.BoolProperty( + quality_vs_speed: bpy.props.BoolProperty( name="Optimize scene for quality: Makes the optimizer adjust settings in a less \"destructive\" way", default=True ) - simplify = bpy.props.BoolProperty( + simplify: bpy.props.BoolProperty( name="Simplify the viewport: Reduces subdivisions to 0. Only disable if any assets will break when using this", default=True ) - scrambling_unsafe = bpy.props.BoolProperty( + scrambling_unsafe: bpy.props.BoolProperty( name="Automatic Scrambling Distance: Can cause artifacts when rendering", default=False ) - preview_scrambling = bpy.props.BoolProperty( + preview_scrambling: bpy.props.BoolProperty( name="Preview Scrambling in the viewport", default=True ) @@ -404,7 +404,6 @@ def execute(self, context): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) bpy.types.Scene.optimizer_props = bpy.props.PointerProperty( diff --git a/MCprep_addon/spawner/effects.py b/MCprep_addon/spawner/effects.py index 6acf45d3..bc0023be 100644 --- a/MCprep_addon/spawner/effects.py +++ b/MCprep_addon/spawner/effects.py @@ -31,6 +31,8 @@ from . import spawn_util +from ..conf import env + # ----------------------------------------------------------------------------- # Global enum values # ----------------------------------------------------------------------------- @@ -414,7 +416,7 @@ def geo_update_params(context, effect, geo_mod): if os.path.isfile(jpath): geo_fields = geo_fields_from_json(effect, jpath) else: - conf.log("No json params path for geonode effects", vv_only=True) + env.log("No json params path for geonode effects", vv_only=True) return # Determine if these special keywords exist. @@ -422,7 +424,7 @@ def geo_update_params(context, effect, geo_mod): for input_name in geo_fields.keys(): if geo_fields[input_name] == "FOLLOW_OBJ": has_followkey = True - conf.log("geonode has_followkey field? " + str(has_followkey), vv_only=True) + env.log("geonode has_followkey field? " + str(has_followkey), vv_only=True) # Create follow empty if required by the group. camera = context.scene.camera @@ -449,16 +451,16 @@ def geo_update_params(context, effect, geo_mod): if inp.name in list(geo_fields): value = geo_fields[inp.name] if value == "CAMERA_OBJ": - conf.log("Set cam for geonode input", vv_only=True) + env.log("Set cam for geonode input", vv_only=True) geo_mod[geo_inp_id[inp.name]] = camera elif value == "FOLLOW_OBJ": if not center_empty: print(">> Center empty missing, not in preset!") else: - conf.log("Set follow for geonode input", vv_only=True) + env.log("Set follow for geonode input", vv_only=True) geo_mod[geo_inp_id[inp.name]] = center_empty else: - conf.log("Set {} for geonode input".format(inp.name), vv_only=True) + env.log("Set {} for geonode input".format(inp.name), vv_only=True) geo_mod[geo_inp_id[inp.name]] = value # TODO: check if any socket name in json specified not found in node. @@ -476,7 +478,7 @@ def geo_fields_from_json(effect, jpath): CAMERA_OBJ: Tells MCprep to assign the active camera object to slot. FOLLOW_OBJ: Tells MCprep to assign a generated empty to this slot. """ - conf.log("Loading geo fields form json: " + jpath) + env.log("Loading geo fields form json: " + jpath) with open(jpath) as fopen: jdata = json.load(fopen) @@ -658,9 +660,9 @@ def import_animated_coll(context, effect, keyname): new_colls = list(set(final_colls) - set(init_colls)) if not new_colls: if any_imported: - conf.log("New collection loaded, but not picked up") + env.log("New collection loaded, but not picked up") else: - conf.log("No colleections imported or recognized") + env.log("No colleections imported or recognized") raise Exception("No collections imported") elif len(new_colls) > 1: # Pick the closest fitting one. At worst, will pick a random one. @@ -754,7 +756,7 @@ def offset_animation_to_frame(collection, frame): def update_effects_path(self, context): """List for UI effects callback .""" - conf.log("Updating effects path", vv_only=True) + env.log("Updating effects path", vv_only=True) update_effects_list(context) @@ -763,12 +765,12 @@ def update_effects_list(context): mcprep_props = context.scene.mcprep_props mcprep_props.effects_list.clear() - if conf.use_icons and conf.preview_collections["effects"]: + if env.use_icons and env.preview_collections["effects"]: try: - bpy.utils.previews.remove(conf.preview_collections["effects"]) + bpy.utils.previews.remove(env.preview_collections["effects"]) except Exception as e: print(e) - conf.log("MCPREP: Failed to remove icon set, effects") + env.log("MCPREP: Failed to remove icon set, effects") load_geonode_effect_list(context) load_area_particle_effects(context) @@ -803,15 +805,15 @@ def load_geonode_effect_list(context): and jsf.lower().endswith(".json") ] - conf.log("json pairs of blend files", vv_only=True) - conf.log(json_files, vv_only=True) + env.log("json pairs of blend files", vv_only=True) + env.log(json_files, vv_only=True) for bfile in blends: row_items = [] using_json = False js_equiv = os.path.splitext(bfile)[0] + ".json" if js_equiv in json_files: - conf.log("Loading json preset for geonode for " + bfile) + env.log("Loading json preset for geonode for " + bfile) # Read nodegroups to include from json presets file. jpath = os.path.splitext(bfile)[0] + ".json" with open(jpath) as jopen: @@ -819,7 +821,7 @@ def load_geonode_effect_list(context): row_items = jdata.keys() using_json = True else: - conf.log("Loading nodegroups from blend for geonode effects: " + bfile) + env.log("Loading nodegroups from blend for geonode effects: " + bfile) # Read nodegroup names from blend file directly. with bpy.data.libraries.load(bfile) as (data_from, _): row_items = list(data_from.node_groups) @@ -832,7 +834,7 @@ def load_geonode_effect_list(context): # First key in index is nodegroup, save to subpath, but then # the present name (list of keys within) are the actual names. for preset in jdata[itm]: - conf.log("\tgeonode preset: " + preset, vv_only=True) + env.log("\tgeonode preset: " + preset, vv_only=True) effect = mcprep_props.effects_list.add() effect.name = preset # This is the assign json preset name. effect.subpath = itm # This is the node group name. @@ -931,7 +933,7 @@ def load_image_sequence_effects(context): lvl_3 = os.path.join(resource_folder, "assets", "minecraft", "textures", "particle") if not os.path.isdir(resource_folder): - conf.log( + env.log( "The particle resource directory is missing! Assign another resource pack") return elif os.path.isdir(lvl_0): @@ -968,7 +970,7 @@ def load_image_sequence_effects(context): effect.index = len(mcprep_props.effects_list) - 1 # For icon index. # Try to load a middle frame as the icon. - if not conf.use_icons or conf.preview_collections["effects"] == "": + if not env.use_icons or env.preview_collections["effects"] == "": continue effect_files = [ os.path.join(resource_folder, fname) @@ -980,7 +982,7 @@ def load_image_sequence_effects(context): if effect_files: # 0 if 1 item, otherwise greater side of median. e_index = int(len(effect_files) / 2) - conf.preview_collections["effects"].load( + env.preview_collections["effects"].load( "effects-{}".format(effect.index), effect_files[e_index], 'IMAGE') @@ -1043,8 +1045,8 @@ def effects_enum(self, context): )) return elist - effect_id = bpy.props.EnumProperty(items=effects_enum, name="Effect") - skipUsage = bpy.props.BoolProperty(default=False, options={'HIDDEN'}) + effect_id: bpy.props.EnumProperty(items=effects_enum, name="Effect") + skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) @classmethod def poll(cls, context): @@ -1114,22 +1116,22 @@ def effects_enum(self, context): )) return elist - effect_id = bpy.props.EnumProperty(items=effects_enum, name="Effect") - location = bpy.props.FloatVectorProperty(default=(0, 0, 0), name="Location") - frame = bpy.props.IntProperty( + effect_id: bpy.props.EnumProperty(items=effects_enum, name="Effect") + location: bpy.props.FloatVectorProperty(default=(0, 0, 0), name="Location") + frame: bpy.props.IntProperty( default=0, name="Frame", description="Start frame for animation") - speed = bpy.props.FloatProperty( + speed: bpy.props.FloatProperty( default=1.0, min=0.1, name="Speed", description="Make the effect run faster (skip frames) or slower (hold frames)") - show_image = bpy.props.BoolProperty( + show_image: bpy.props.BoolProperty( default=False, name="Show image preview", description="Show a middle animation frame as a viewport preview") - skipUsage = bpy.props.BoolProperty(default=False, options={'HIDDEN'}) + skipUsage: bpy.props.BoolProperty(default=False, options={'HIDDEN'}) @classmethod def poll(cls, context): @@ -1171,18 +1173,18 @@ class MCPREP_OT_spawn_particle_planes(bpy.types.Operator, ImportHelper): bl_label = "Spawn Particle Planes" bl_options = {'REGISTER', 'UNDO'} - location = bpy.props.FloatVectorProperty( + location: bpy.props.FloatVectorProperty( default=(0, 0, 0), name="Location") - frame = bpy.props.IntProperty(default=0, name="Frame") + frame: bpy.props.IntProperty(default=0, name="Frame") # Importer helper exts = ";".join(["*" + ext for ext in EXTENSIONS]) - filter_glob = bpy.props.StringProperty( + filter_glob: bpy.props.StringProperty( default=exts, options={'HIDDEN'}) fileselectparams = "use_filter_blender" - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -1240,7 +1242,6 @@ def execute(self, context): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) diff --git a/MCprep_addon/spawner/entities.py b/MCprep_addon/spawner/entities.py index 04d5ebf2..4dbf4df2 100644 --- a/MCprep_addon/spawner/entities.py +++ b/MCprep_addon/spawner/entities.py @@ -21,6 +21,7 @@ import bpy from .. import conf +from ..conf import env from .. import util from .. import tracking @@ -53,10 +54,10 @@ def get_entity_cache(context, clear=False): # Note: Only using groups, not objects, for entities. entity_cache = {"groups": [], "objects": []} if not os.path.isfile(entity_path): - conf.log("Entity path not found") + env.log("Entity path not found") return entity_cache if not entity_path.lower().endswith('.blend'): - conf.log("Entity path must be a .blend file") + env.log("Entity path must be a .blend file") return entity_cache with bpy.data.libraries.load(entity_path) as (data_from, _): @@ -78,7 +79,7 @@ def getEntityList(context): def update_entity_path(self, context): """for UI list path callback""" - conf.log("Updating entity path", vv_only=True) + env.log("Updating entity path", vv_only=True) if not context.scene.entity_path.lower().endswith('.blend'): print("Entity file is not a .blend, and should be") if not os.path.isfile(bpy.path.abspath(context.scene.entity_path)): @@ -162,14 +163,14 @@ class MCPREP_OT_entity_spawner(bpy.types.Operator): def swap_enum(self, context): return getEntityList(context) - entity = bpy.props.EnumProperty(items=swap_enum, name="Entity") - append_layer = bpy.props.IntProperty( + entity: bpy.props.EnumProperty(items=swap_enum, name="Entity") + append_layer: bpy.props.IntProperty( name="Append layer", default=20, min=0, max=20, description="Set the layer for appending groups, 0 means same as active layers") - relocation = bpy.props.EnumProperty( + relocation: bpy.props.EnumProperty( items=[ ('Cursor', 'Cursor', 'Move the rig to the cursor'), ('Clear', 'Origin', 'Move the rig to the origin'), @@ -177,18 +178,18 @@ def swap_enum(self, context): 'Offset the root bone to cursor while leaving the rest pose ' 'at the origin'))], name="Relocation") - clearPose = bpy.props.BoolProperty( + clearPose: bpy.props.BoolProperty( name="Clear Pose", description="Clear the pose to rest position", default=True) - prep_materials = bpy.props.BoolProperty( + prep_materials: bpy.props.BoolProperty( name="Prep materials (will reset nodes)", description=( "Prep materials of the added rig, will replace cycles node groups " "with default"), default=True) - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -256,7 +257,6 @@ def execute(self, context): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) global entity_cache diff --git a/MCprep_addon/spawner/item.py b/MCprep_addon/spawner/item.py index 05ba9429..ebf843c7 100644 --- a/MCprep_addon/spawner/item.py +++ b/MCprep_addon/spawner/item.py @@ -22,9 +22,9 @@ from bpy_extras.io_utils import ImportHelper import mathutils -from .. import conf from .. import util from .. import tracking +from ..conf import env from ..materials import generate try: import bpy.utils.previews @@ -44,12 +44,12 @@ def reload_items(context): extensions = [".png", ".jpg", ".jpeg"] mcprep_props.item_list.clear() - if conf.use_icons and conf.preview_collections["items"]: + if env.use_icons and env.preview_collections["items"]: try: - bpy.utils.previews.remove(conf.preview_collections["items"]) + bpy.utils.previews.remove(env.preview_collections["items"]) except Exception as e: print(e) - conf.log("MCPREP: Failed to remove icon set, items") + env.log("MCPREP: Failed to remove icon set, items") # Check levels lvl_1 = os.path.join(resource_folder, "textures") @@ -57,7 +57,7 @@ def reload_items(context): lvl_3 = os.path.join(resource_folder, "assets", "minecraft", "textures") if not os.path.isdir(resource_folder): - conf.log("Error, resource folder does not exist") + env.log("Error, resource folder does not exist") return elif os.path.isdir(lvl_1): resource_folder = lvl_1 @@ -89,9 +89,9 @@ def reload_items(context): asset.index = i # if available, load the custom icon too - if not conf.use_icons or conf.preview_collections["items"] == "": + if not env.use_icons or env.preview_collections["items"] == "": continue - conf.preview_collections["items"].load( + env.preview_collections["items"].load( "item-{}".format(i), item_file, 'IMAGE') if mcprep_props.item_list_index >= len(mcprep_props.item_list): @@ -297,44 +297,44 @@ class ItemSpawnBase(): """Class to inheret reused MCprep item spawning settings and functions.""" # TODO: add options like spawning attached to rig hand or other, - size = bpy.props.FloatProperty( + size: bpy.props.FloatProperty( name="Size", default=1.0, min=0.001, description="Size in blender units of the item") - thickness = bpy.props.FloatProperty( + thickness: bpy.props.FloatProperty( name="Thickness", default=1.0, min=0.0, description=( "The thickness of the item (this can later be changed in " "modifiers)")) - transparency = bpy.props.BoolProperty( + transparency: bpy.props.BoolProperty( name="Remove transparent faces", description="Transparent pixels will be transparent once rendered", default=True) - threshold = bpy.props.FloatProperty( + threshold: bpy.props.FloatProperty( name="Transparent threshold", description="1.0 = zero tolerance, no transparent pixels will be generated", default=0.5, min=0.0, max=1.0) - max_pixels = bpy.props.IntProperty( + max_pixels: bpy.props.IntProperty( name="Max pixels", default=50000, min=1, description=( "If needed, scale down image to generate less than this maximum " "pixel count")) - scale_uvs = bpy.props.FloatProperty( + scale_uvs: bpy.props.FloatProperty( name="Scale UVs", default=0.75, description="Scale individual UV faces of the generated item") - filepath = bpy.props.StringProperty( + filepath: bpy.props.StringProperty( default="", subtype="FILE_PATH", options={'HIDDEN', 'SKIP_SAVE'}) - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -421,14 +421,14 @@ class MCPREP_OT_spawn_item_from_file(bpy.types.Operator, ImportHelper, ItemSpawn bl_idname = "mcprep.spawn_item_file" bl_label = "Item from file" - filter_glob = bpy.props.StringProperty( + filter_glob: bpy.props.StringProperty( default="", options={'HIDDEN'}) fileselectparams = "use_filter_blender" - files = bpy.props.CollectionProperty( + files: bpy.props.CollectionProperty( type=bpy.types.PropertyGroup, options={'HIDDEN', 'SKIP_SAVE'}) - filter_image = bpy.props.BoolProperty( + filter_image: bpy.props.BoolProperty( default=True, options={'HIDDEN', 'SKIP_SAVE'}) @@ -466,9 +466,7 @@ def execute(self, context): def register(): - util.make_annotations(ItemSpawnBase) # Don't register, only annotate. for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) diff --git a/MCprep_addon/spawner/mcmodel.py b/MCprep_addon/spawner/mcmodel.py index 6c895d9a..c6fccec2 100644 --- a/MCprep_addon/spawner/mcmodel.py +++ b/MCprep_addon/spawner/mcmodel.py @@ -25,7 +25,7 @@ import bmesh from bpy_extras.io_utils import ImportHelper -from .. import conf +from ..conf import env from .. import util from .. import tracking from ..materials import generate @@ -106,12 +106,12 @@ def add_material(name="material", path=""): cur_mats = list(bpy.data.materials) res = bpy.ops.mcprep.load_material(filepath=path, skipUsage=True) if res != {'FINISHED'}: - conf.log("Failed to generate material as specified") + env.log("Failed to generate material as specified") post_mats = list(bpy.data.materials) new_mats = list(set(post_mats) - set(cur_mats)) if not new_mats: - conf.log("Failed to fetch any generated material") + env.log("Failed to fetch any generated material") return None mat = new_mats[0] @@ -208,7 +208,7 @@ def read_model(context, model_filepath): elif os.path.isfile(base_path): elements, textures = read_model(context, base_path) else: - conf.log("Failed to find mcmodel file " + parent_filepath) + env.log("Failed to find mcmodel file " + parent_filepath) current_elements = obj_data.get("elements") if current_elements is not None: @@ -222,10 +222,10 @@ def read_model(context, model_filepath): for img in current_textures: textures[img] = current_textures[img] - conf.log("\nfile:" + str(model_filepath), vv_only=True) - # conf.log("parent:" + str(parent)) - # conf.log("elements:" + str(elements)) - # conf.log("textures:" + str(textures)) + env.log("\nfile:" + str(model_filepath), vv_only=True) + # env.log("parent:" + str(parent)) + # env.log("elements:" + str(elements)) + # env.log("textures:" + str(textures)) return elements, textures @@ -359,7 +359,7 @@ def update_model_list(context): if not os.path.isdir(active_pack): scn_props.model_list.clear() scn_props.model_list_index = 0 - conf.log("No models found for active path " + active_pack) + env.log("No models found for active path " + active_pack) return base_has_models = os.path.isdir(base_pack) @@ -374,7 +374,7 @@ def update_model_list(context): if os.path.isfile(os.path.join(active_pack, model)) and model.lower().endswith(".json")] else: - conf.log("Base resource pack has no models folder: " + base_pack) + env.log("Base resource pack has no models folder: " + base_pack) base_models = [] sorted_models = [ @@ -413,23 +413,23 @@ def draw_import_mcmodel(self, context): class ModelSpawnBase(): """Class to inheret reused MCprep item spawning settings and functions.""" - location = bpy.props.FloatVectorProperty( + location: bpy.props.FloatVectorProperty( default=(0, 0, 0), name="Location") - snapping = bpy.props.EnumProperty( + snapping: bpy.props.EnumProperty( name="Snapping", items=[ ("none", "No snap", "Keep exact location"), ("center", "Snap center", "Snap to block center"), ("offset", "Snap offset", "Snap to block center with 0.5 offset")], description="Automatically snap to whole block locations") - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @classmethod def poll(cls, context): - return context.mode == 'OBJECT' and util.bv28() + return context.mode == 'OBJECT' def place_model(self, obj): if self.snapping == "center": @@ -456,7 +456,7 @@ class MCPREP_OT_spawn_minecraft_model(bpy.types.Operator, ModelSpawnBase): bl_label = "Place model" bl_options = {'REGISTER', 'UNDO'} - filepath = bpy.props.StringProperty( + filepath: bpy.props.StringProperty( default="", subtype="FILE_PATH", options={'HIDDEN', 'SKIP_SAVE'}) @@ -494,7 +494,7 @@ class MCPREP_OT_import_minecraft_model_file( bl_options = {'REGISTER', 'UNDO'} filename_ext = ".json" - filter_glob = bpy.props.StringProperty( + filter_glob: bpy.props.StringProperty( default="*.json", options={'HIDDEN'}, maxlen=255 # Max internal buffer length, longer would be clamped. @@ -543,17 +543,13 @@ def execute(self, context): def register(): - util.make_annotations(ModelSpawnBase) # Don't register, only annotate. for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) - if util.bv28(): - bpy.types.TOPBAR_MT_file_import.append(draw_import_mcmodel) + bpy.types.TOPBAR_MT_file_import.append(draw_import_mcmodel) def unregister(): - if util.bv28(): - bpy.types.TOPBAR_MT_file_import.remove(draw_import_mcmodel) + bpy.types.TOPBAR_MT_file_import.remove(draw_import_mcmodel) for cls in reversed(classes): bpy.utils.unregister_class(cls) diff --git a/MCprep_addon/spawner/meshswap.py b/MCprep_addon/spawner/meshswap.py index 589bd664..bc35fedf 100755 --- a/MCprep_addon/spawner/meshswap.py +++ b/MCprep_addon/spawner/meshswap.py @@ -27,6 +27,7 @@ from . import spawn_util from .. import conf +from ..conf import env from ..materials import generate from .. import util from .. import tracking @@ -57,10 +58,10 @@ def get_meshswap_cache(context, clear=False): meshswap_cache = {"groups": [], "objects": []} if not os.path.isfile(meshswap_path): - conf.log("Meshswap path not found") + env.log("Meshswap path not found") return meshswap_cache if not meshswap_path.lower().endswith('.blend'): - conf.log("Meshswap path must be a .blend file") + env.log("Meshswap path must be a .blend file") return meshswap_cache with bpy.data.libraries.load(meshswap_path) as (data_from, _): @@ -69,7 +70,7 @@ def get_meshswap_cache(context, clear=False): meshswap_cache["groups"] = grp_list for obj in list(data_from.objects): if obj in meshswap_cache["groups"]: - # conf.log("Skipping meshwap obj already in cache: "+str(obj)) + # env.log("Skipping meshwap obj already in cache: "+str(obj)) continue # ignore list? e.g. Point.001, meshswap_cache["objects"].append(obj) @@ -107,7 +108,7 @@ def move_assets_to_excluded_layer(context, collections): def update_meshswap_path(self, context): """for UI list path callback""" - conf.log("Updating meshswap path", vv_only=True) + env.log("Updating meshswap path", vv_only=True) if not context.scene.meshswap_path.lower().endswith('.blend'): print("Meshswap file is not a .blend, and should be") if not os.path.isfile(bpy.path.abspath(context.scene.meshswap_path)): @@ -194,8 +195,8 @@ class MCPREP_OT_meshswap_spawner(bpy.types.Operator): def swap_enum(self, context): return getMeshswapList(context) - block = bpy.props.EnumProperty(items=swap_enum, name="Meshswap block") - method = bpy.props.EnumProperty( + block: bpy.props.EnumProperty(items=swap_enum, name="Meshswap block") + method: bpy.props.EnumProperty( name="Import method", items=[ # Making collection first to be effective default. @@ -203,32 +204,32 @@ def swap_enum(self, context): ("object", "Object asset", "Object asset"), ], options={'HIDDEN'}) - location = bpy.props.FloatVectorProperty( + location: bpy.props.FloatVectorProperty( default=(0, 0, 0), name="Location") - append_layer = bpy.props.IntProperty( + append_layer: bpy.props.IntProperty( name="Append layer", default=20, min=0, max=20, description="Set the layer for appending groups, 0 means same as active layers") - prep_materials = bpy.props.BoolProperty( + prep_materials: bpy.props.BoolProperty( name="Prep materials", default=True, description="Run prep materials on objects after appending") - snapping = bpy.props.EnumProperty( + snapping: bpy.props.EnumProperty( name="Snapping", items=[ ("none", "No snap", "Keep exact location"), ("center", "Snap center", "Snap to block center"), ("offset", "Snap offset", "Snap to block center with 0.5 offset")], description="Automatically snap to whole block locations") - make_real = bpy.props.BoolProperty( + make_real: bpy.props.BoolProperty( name="Make real", default=False, # TODO: make True once able to retain animations like fire description="Automatically make groups real after placement") - # toLink = bpy.props.BoolProperty( + # toLink: bpy.props.BoolProperty( # name = "Library Link mob", # description = "Library link instead of append the group", # default = False @@ -295,7 +296,7 @@ def execute(self, context): self, context, block, pre_groups) if not group: - conf.log("No group identified, could not retrieve imported group") + env.log("No group identified, could not retrieve imported group") self.report({"ERROR"}, "Could not retrieve imported group") return {'CANCELLED'} @@ -460,27 +461,27 @@ class MCPREP_OT_meshswap(bpy.types.Operator): runcount = 0 # current counter status of swapped meshes # properties for draw - meshswap_join = bpy.props.BoolProperty( + meshswap_join: bpy.props.BoolProperty( name="Join same blocks", default=True, description=( "Join together swapped blocks of the same type " "(unless swapped with a group)")) - use_dupliverts = bpy.props.BoolProperty( + use_dupliverts: bpy.props.BoolProperty( name="Use dupliverts (faster)", default=True, description="Use dupliverts to add meshes") - link_groups = bpy.props.BoolProperty( + link_groups: bpy.props.BoolProperty( name="Link groups", default=False, description="Link groups instead of appending") - prep_materials = bpy.props.BoolProperty( + prep_materials: bpy.props.BoolProperty( name="Prep materials", default=False, description=( "Automatically apply prep materials (with default settings) " "to blocks added in")) - append_layer = bpy.props.IntProperty( + append_layer: bpy.props.IntProperty( name="Append layer", default=20, min=0, @@ -573,7 +574,7 @@ def execute(self, context): # setup the progress bar denom = len(objList) - conf.log("Meshswap to check over {} objects".format(denom)) + env.log("Meshswap to check over {} objects".format(denom)) bpy.context.window_manager.progress_begin(0, 100) tprep = time.time() - tprep @@ -591,7 +592,7 @@ def execute(self, context): bpy.context.window_manager.progress_update(iter_index / denom) swapGen = util.nameGeneralize(swap.name) # swapGen = generate.get_mc_canonical_name(swap.name) - conf.log("Simplified name: {x}".format(x=swapGen)) + env.log("Simplified name: {x}".format(x=swapGen)) # IMPORTS, gets lists properties, etc swapProps = self.checkExternal(context, swapGen) @@ -609,7 +610,7 @@ def execute(self, context): if not (swapProps['meshSwap'] or swapProps['groupSwap']): continue - conf.log( + env.log( "Swapping '{x}', simplified name '{y}".format( x=swap.name, y=swapGen)) @@ -667,7 +668,7 @@ def execute(self, context): if context.selected_objects: new_objects.append(context.selected_objects[0]) else: - conf.log("No selected objects after join") + env.log("No selected objects after join") else: # no joining, so just directly append to new_objects new_objects += dupedObj # a list @@ -735,13 +736,13 @@ def execute(self, context): t5 = time.time() # run timing calculations - if conf.vv: + if env.very_verbose: loop_prep = sum(t1s) - sum(t0s) face_process = sum(t2s) - sum(t1s) instancing = sum(t3s) - sum(t2s) cleanup = t5 - t4 total = tprep + loop_prep + face_process + instancing + cleanup - conf.log("Total time: {}s, init: {}, prep: {}, poly process: {}, instance:{}, cleanup: {}".format( + env.log("Total time: {}s, init: {}, prep: {}, poly process: {}, instance:{}, cleanup: {}".format( round(total, 1), round(tprep, 1), round(loop_prep, 1), round(face_process, 1), round(instancing, 1), round(cleanup, 1))) @@ -849,15 +850,15 @@ def checkExternal(self, context, name): # delete unnecessary ones first if name in rmable: removable = True - conf.log("Removable!") + env.log("Removable!") return {'removable': removable} # check the actual name against the library name = generate.get_mc_canonical_name(name)[0] cache = get_meshswap_cache(context) - if name in conf.json_data["blocks"]["canon_mapping_block"]: + if name in env.json_data["blocks"]["canon_mapping_block"]: # e.g. remaps entity/chest/normal back to chest - name_remap = conf.json_data["blocks"]["canon_mapping_block"][name] + name_remap = env.json_data["blocks"]["canon_mapping_block"][name] else: name_remap = None @@ -877,7 +878,7 @@ def checkExternal(self, context, name): return False # if not present, continue # now import - conf.log("about to link, group {} / mesh {}?".format( + env.log("about to link, group {} / mesh {}?".format( groupSwap, meshSwap)) toLink = self.link_groups for ob in context.selected_objects: @@ -934,7 +935,7 @@ def checkExternal(self, context, name): grouped = True # set properties for item in util.collections()[name].items(): - conf.log("GROUP PROPS:" + str(item)) + env.log("GROUP PROPS:" + str(item)) try: x = item[1].name # will NOT work if property UI except: @@ -959,7 +960,7 @@ def checkExternal(self, context, name): # ## BE IN FILE, EG NAME OF MESH TO SWAP CHANGED, INDEX ERROR IS THROWN HERE # ## >> MAKE a more graceful error indication. # filter out non-meshes in case of parent grouping or other pull-ins - # conf.log("DEBUG - post importing {}, selected objects: {}".format( + # env.log("DEBUG - post importing {}, selected objects: {}".format( # name, list(bpy.context.selected_objects)), vv_only=True) for ob in bpy.context.selected_objects: @@ -994,8 +995,8 @@ def checkExternal(self, context, name): for ob in context.selected_objects: util.select_set(ob, False) # #### HERE set the other properties, e.g. variance and edgefloat, now that the obj exists - conf.log("groupSwap: {}, meshSwap: {}".format(groupSwap, meshSwap)) - conf.log("edgeFloat: {}, variance: {}, torchlike: {}".format( + env.log("groupSwap: {}, meshSwap: {}".format(groupSwap, meshSwap)) + env.log("edgeFloat: {}, variance: {}, torchlike: {}".format( edgeFloat, variance, torchlike)) return { 'importName': name, 'object': importedObj, 'meshSwap': meshSwap, @@ -1045,10 +1046,10 @@ def proccess_poly_orientations(self, face, swapProps, swapGen, instance_configs) # if not swapProps['edgeFloat']: # #continue # print("do nothing, this is for jmc2obj") - conf.log( + env.log( "Instance: loc, face.local, face.nrm, hanging offset, if_edgeFloat:", vv_only=True) - conf.log( + env.log( str([loc, face.l, face.n, [a, b, c], outside_hanging]), vv_only=True) @@ -1091,7 +1092,7 @@ def proccess_poly_orientations(self, face, swapProps, swapGen, instance_configs) else: rot_type = 0 elif swapProps['edgeFloat']: - conf.log("Edge float!", vv_only=True) + env.log("Edge float!", vv_only=True) if (y - face.l[1] < 0): rot_type = 8 elif (x_diff > 0.3): @@ -1120,9 +1121,9 @@ def proccess_poly_orientations(self, face, swapProps, swapGen, instance_configs) else: rot_type = 0 elif self.track_exporter == "Mineways": - conf.log("checking: {} {}".format(x_diff, z_diff)) + env.log("checking: {} {}".format(x_diff, z_diff)) if swapProps['torchlike']: # needs fixing - conf.log("recognized it's a torchlike obj..") + env.log("recognized it's a torchlike obj..") if (x_diff > .1 and x_diff < 0.6): rot_type = 1 elif (z_diff > .1 and z_diff < 0.6): @@ -1201,7 +1202,7 @@ def add_instances_with_transforms(self, context, swap, swapProps, instance_confi if grouped: # definition for randimization, defined at top! randGroup = util.randomizeMeshSawp(swapProps['importName'], 3) - conf.log("Rand group: {}".format(randGroup)) + env.log("Rand group: {}".format(randGroup)) new_ob = util.addGroupInstance(randGroup, loc) if hasattr(new_ob, "empty_draw_size"): new_ob.empty_draw_size = 0.25 @@ -1289,7 +1290,7 @@ def add_instances_with_transforms(self, context, swap, swapProps, instance_confi def offsetByHalf(self, obj): if obj.type != 'MESH': return - conf.log("doing offset") + env.log("doing offset") active = bpy.context.object # preserve current active util.set_active_object(bpy.context, obj) bpy.ops.object.mode_set(mode='EDIT') @@ -1310,7 +1311,7 @@ class MCPREP_OT_fix_mineways_scale(bpy.types.Operator): @tracking.report_error def execute(self, context): - conf.log("Attempting to fix Mineways scaling for meshswap") + env.log("Attempting to fix Mineways scaling for meshswap") # get cursor loc first? shouldn't matter which mode/location though tmp_loc = util.get_cuser_location(context) @@ -1349,7 +1350,6 @@ def execute(self, context): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) global meshswap_cache diff --git a/MCprep_addon/spawner/mobs.py b/MCprep_addon/spawner/mobs.py index 0d7219ab..219cfdae 100644 --- a/MCprep_addon/spawner/mobs.py +++ b/MCprep_addon/spawner/mobs.py @@ -26,6 +26,7 @@ from bpy_extras.io_utils import ImportHelper from .. import conf +from ..conf import env from .. import util from .. import tracking @@ -51,8 +52,8 @@ def get_rig_list(context): def update_rig_path(self, context): """List for UI mobs callback of property spawn_rig_category.""" - conf.log("Updating rig path", vv_only=True) - conf.rig_categories = [] + env.log("Updating rig path", vv_only=True) + env.rig_categories = [] update_rig_list(context) spawn_rigs_categories(self, context) @@ -67,7 +68,7 @@ def _add_rigs_from_blend(path, blend_name, category): extensions = [".png", ".jpg", ".jpeg"] icon_folder = os.path.join(os.path.dirname(path), "icons") run_icons = os.path.isdir(icon_folder) - if not conf.use_icons or conf.preview_collections["mobs"] == "": + if not env.use_icons or env.preview_collections["mobs"] == "": run_icons = False mob_names = spawn_util.filter_collections(data_from) @@ -104,7 +105,7 @@ def _add_rigs_from_blend(path, blend_name, category): and os.path.splitext(f.lower())[-1] in extensions] if not icons: continue - conf.preview_collections["mobs"].load( + env.preview_collections["mobs"].load( "mob-{}".format(mob.index), os.path.join(icon_folder, icons[0]), 'IMAGE') @@ -113,15 +114,15 @@ def _add_rigs_from_blend(path, blend_name, category): context.scene.mcprep_props.mob_list.clear() context.scene.mcprep_props.mob_list_all.clear() - if conf.use_icons and conf.preview_collections["mobs"]: + if env.use_icons and env.preview_collections["mobs"]: print("Removing mobs preview collection") try: - bpy.utils.previews.remove(conf.preview_collections["mobs"]) + bpy.utils.previews.remove(env.preview_collections["mobs"]) except: - conf.log("MCPREP: Failed to remove icon set, mobs") + env.log("MCPREP: Failed to remove icon set, mobs") if os.path.isdir(rigpath) is False: - conf.log("Rigpath directory not found") + env.log("Rigpath directory not found") return categories = [ @@ -163,7 +164,7 @@ def update_rig_category(context): scn_props = context.scene.mcprep_props if not scn_props.mob_list_all: - conf.log("No rigs found, failed to update category") + env.log("No rigs found, failed to update category") scn_props.mob_list.clear() return @@ -202,7 +203,7 @@ class MCPREP_OT_reload_mobs(bpy.types.Operator): @tracking.report_error def execute(self, context): - conf.rig_categories = [] + env.rig_categories = [] update_rig_list(context) return {'FINISHED'} @@ -217,8 +218,8 @@ class MCPREP_OT_mob_spawner(bpy.types.Operator): def riglist_enum(self, context): return get_rig_list(context) - mcmob_type = bpy.props.EnumProperty(items=riglist_enum, name="Mob Type") - relocation = bpy.props.EnumProperty( + mcmob_type: bpy.props.EnumProperty(items=riglist_enum, name="Mob Type") + relocation: bpy.props.EnumProperty( items=[ ('Cursor', 'Cursor', 'Move the rig to the cursor'), ('Clear', 'Origin', 'Move the rig to the origin'), @@ -226,22 +227,22 @@ def riglist_enum(self, context): 'Offset the root bone to cursor while leaving the rest pose ' 'at the origin'))], name="Relocation") - toLink = bpy.props.BoolProperty( + toLink: bpy.props.BoolProperty( name="Library Link", description="Library link instead of append the group", default=False) - clearPose = bpy.props.BoolProperty( + clearPose: bpy.props.BoolProperty( name="Clear Pose", description="Clear the pose to rest position", default=True) - prep_materials = bpy.props.BoolProperty( + prep_materials: bpy.props.BoolProperty( name="Prep materials (will reset nodes)", description=( "Prep materials of the added rig, will replace cycles node groups " "with default"), default=True) - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -267,11 +268,11 @@ def execute(self, context): try: [path, name] = self.mcmob_type.split(':/:') except Exception as err: - conf.log("Error: Failed to parse mcmob_type") + env.log("Error: Failed to parse mcmob_type") self.report({'ERROR'}, "Failed to parse mcmob_type, try reloading mobs") return {'CANCELLED'} path = os.path.join(context.scene.mcprep_mob_path, path) - conf.log("Path is now ", path) + env.log("Path is now ", path) try: # must be in object mode, this make similar behavior to other objs @@ -283,7 +284,7 @@ def execute(self, context): if self.toLink: if path == '//': - conf.log("This is the local file. Cancelling...") + env.log("This is the local file. Cancelling...") return {'CANCELLED'} _ = spawn_util.load_linked(self, context, path, name) else: @@ -325,7 +326,7 @@ def set_fake_users(self, context, new_objs): for obj in mod_objs: if obj not in list(context.scene.collection.all_objects): obj.use_fake_user = True - conf.log("Set {} as fake user".format(obj.name)) + env.log("Set {} as fake user".format(obj.name)) class MCPREP_OT_install_mob(bpy.types.Operator, ImportHelper): @@ -337,7 +338,7 @@ class MCPREP_OT_install_mob(bpy.types.Operator, ImportHelper): "in selected blend file will become individually spawnable") filename_ext = ".blend" - filter_glob = bpy.props.StringProperty( + filter_glob: bpy.props.StringProperty( default="*.blend", options={'HIDDEN'}, ) @@ -357,7 +358,7 @@ def getCategories(self, context): ret.append(("no_category", "No Category", "Uncategorized mob")) # last entry return ret - mob_category = bpy.props.EnumProperty( + mob_category: bpy.props.EnumProperty( items=getCategories, name="Mob Category") @@ -367,19 +368,19 @@ def execute(self, context): newrig = bpy.path.abspath(self.filepath) if not os.path.isfile(newrig): - conf.log("Error: Rig blend file not found!") + env.log("Error: Rig blend file not found!") self.report({'ERROR'}, "Rig blend file not found!") return {'CANCELLED'} if not newrig.lower().endswith('.blend'): - conf.log("Error: Not a blend file! Select a .blend file with a rig") + env.log("Error: Not a blend file! Select a .blend file with a rig") self.report({'ERROR'}, "Not a blend file! Select a .blend file with a rig") return {'CANCELLED'} # now check the rigs folder indeed exists drpath = bpy.path.abspath(drpath) if not os.path.isdir(drpath): - conf.log("Error: Rig directory is not valid!") + env.log("Error: Rig directory is not valid!") self.report({'ERROR'}, "Rig directory is not valid!") return {'CANCELLED'} @@ -391,7 +392,7 @@ def execute(self, context): install_groups.pop(install_groups.index('Collection')) if not install_groups: - conf.log("Error: no groups found in blend file!") + env.log("Error: no groups found in blend file!") self.report({'ERROR'}, "No groups found in blend file!") return {'CANCELLED'} @@ -430,7 +431,7 @@ def execute(self, context): # copy all relevant icons, based on groups installed # ## matching same folde or subfolder icons to append - if conf.use_icons: + if env.use_icons: basedir = os.path.dirname(newrig) icon_files = self.identify_icons(install_groups, basedir) icondir = os.path.join(basedir, "icons") @@ -564,18 +565,18 @@ def execute(self, context): return {'CANCELLED'} if os.path.isfile(path) is False: - conf.log("Error: Source filepath not found, didn't delete: " + path) + env.log("Error: Source filepath not found, didn't delete: " + path) self.report({'ERROR'}, "Source filepath not found, didn't delete") return {'CANCELLED'} else: try: os.remove(path) except Exception as err: - conf.log("Error: could not delete file: " + str(err)) + env.log("Error: could not delete file: " + str(err)) self.report({'ERROR'}, "Could not delete file") return {'CANCELLED'} self.report({'INFO'}, "Removed: " + str(path)) - conf.log("Removed file: " + str(path)) + env.log("Removed file: " + str(path)) bpy.ops.mcprep.reload_mobs() return {'FINISHED'} @@ -585,11 +586,11 @@ class MCPREP_OT_install_mob_icon(bpy.types.Operator, ImportHelper): bl_idname = "mcprep.mob_install_icon" bl_label = "Install mob icon" - filter_glob = bpy.props.StringProperty( + filter_glob: bpy.props.StringProperty( default="", options={'HIDDEN'}) fileselectparams = "use_filter_blender" - filter_image = bpy.props.BoolProperty( + filter_image: bpy.props.BoolProperty( default=True, options={'HIDDEN', 'SKIP_SAVE'}) @@ -648,12 +649,12 @@ def execute(self, context): # if successful, load or reload icon id icon_id = "mob-{}".format(mob.index) - if icon_id in conf.preview_collections["mobs"]: + if icon_id in env.preview_collections["mobs"]: print("Deloading old icon for this mob") - print(dir(conf.preview_collections["mobs"][icon_id])) - conf.preview_collections["mobs"][icon_id].reload() + print(dir(env.preview_collections["mobs"][icon_id])) + env.preview_collections["mobs"][icon_id].reload() else: - conf.preview_collections["mobs"].load(icon_id, new_file, 'IMAGE') + env.preview_collections["mobs"].load(icon_id, new_file, 'IMAGE') print("Icon reloaded") return {'FINISHED'} @@ -668,13 +669,13 @@ def spawn_rigs_categories(self, context): items = [] items.append(("all", "All Mobs", "Show all mobs loaded")) - categories = conf.rig_categories - if not conf.rig_categories: + categories = env.rig_categories + if not env.rig_categories: it = context.scene.mcprep_mob_path try: categories = [ f for f in os.listdir(it) if os.path.isdir(os.path.join(it, f))] - conf.rig_categories = categories + env.rig_categories = categories except FileNotFoundError: pass # Directory has changed or is not found. for item in categories: @@ -709,7 +710,6 @@ def spawn_rigs_category_load(self, context): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) diff --git a/MCprep_addon/spawner/spawn_util.py b/MCprep_addon/spawner/spawn_util.py index fb1103da..38cbdeb8 100755 --- a/MCprep_addon/spawner/spawn_util.py +++ b/MCprep_addon/spawner/spawn_util.py @@ -21,7 +21,7 @@ import bpy -from .. import conf +from ..conf import env from .. import util from .. import tracking from . import mobs @@ -80,10 +80,10 @@ def filter_collections(data_from): short = name.replace(" ", "").replace("-", "").replace("_", "") if SKIP_COLL in short.lower(): - conf.log("Skipping collection: " + name) + env.log("Skipping collection: " + name) continue if SKIP_COLL_LEGACY in short.lower(): - conf.log("Skipping legacy collection: " + name) + env.log("Skipping legacy collection: " + name) continue elif INCLUDE_COLL in name.lower(): any_mcprep = True @@ -91,7 +91,7 @@ def filter_collections(data_from): all_names.append(name) if any_mcprep: - conf.log("Filtered from {} down to {} MCprep collections".format( + env.log("Filtered from {} down to {} MCprep collections".format( len(all_names), len(mcprep_names))) all_names = mcprep_names return all_names @@ -202,13 +202,13 @@ def attemptScriptLoad(path): path = path[:-5] + "py" if os.path.basename(path) in [txt.name for txt in bpy.data.texts]: - conf.log("Script {} already imported, not importing a new one".format( + env.log("Script {} already imported, not importing a new one".format( os.path.basename(path))) return if not os.path.isfile(path): return # no script found - conf.log("Script found, loading and running it") + env.log("Script found, loading and running it") text = bpy.data.texts.load(filepath=path, internal=True) try: ctx = bpy.context.copy() @@ -221,9 +221,9 @@ def attemptScriptLoad(path): ctx.use_fake_user = True ctx.use_module = True except: - conf.log("Failed to run the script, not registering") + env.log("Failed to run the script, not registering") return - conf.log("Ran the script") + env.log("Ran the script") text.use_module = True @@ -261,13 +261,13 @@ def fix_armature_target(self, context, new_objs, src_coll): if old_target.animation_data: new_target.animation_data_create() new_target.animation_data.action = old_target.animation_data.action - conf.log( + env.log( "Updated animation of armature for instance of " + src_coll.name) if mod.object in new_objs: continue # Was already the new object target for modifier. mod.object = new_target - conf.log( + env.log( "Updated target of armature for instance of " + src_coll.name) @@ -340,7 +340,7 @@ def get_rig_from_objects(objects): def offset_root_bone(context, armature): """Used to offset bone to world location (cursor)""" - conf.log("Attempting offset root") + env.log("Attempting offset root") set_bone = False lower_bones = [bone.name.lower() for bone in armature.pose.bones] lower_name = None @@ -399,7 +399,7 @@ def load_linked(self, context, path, name): # act = context.selected_objects[0] # better for 2.8 # elif context.object: # act = context.object # assumption of object after linking - conf.log("Identified new obj as: {}".format( + env.log("Identified new obj as: {}".format( act), vv_only=True) if not act: @@ -488,7 +488,7 @@ def load_append(self, context, path, name): # Could try to recopy thsoe elements, or try re-appending with # renaming of the original group self.report({'ERROR'}, "Group name already exists in local file") - conf.log("Group already appended/is here") + env.log("Group already appended/is here") return path = bpy.path.abspath(path) @@ -506,7 +506,7 @@ def load_append(self, context, path, name): else: raise Exception("No Group or Collection bpy API endpoint") - conf.log(os.path.join(path, subpath) + ', ' + name) + env.log(os.path.join(path, subpath) + ', ' + name) pregroups = list(util.collections()) res = util.bAppendLink(os.path.join(path, subpath), name, False) postgroups = list(util.collections()) @@ -520,10 +520,10 @@ def load_append(self, context, path, name): return if not new_groups and name in util.collections(): # this is more likely to fail but serves as a fallback - conf.log("Mob spawn: Had to go to fallback group name grab") + env.log("Mob spawn: Had to go to fallback group name grab") grp_added = util.collections()[name] elif not new_groups: - conf.log("Warning, could not detect imported group") + env.log("Warning, could not detect imported group") self.report({'WARNING'}, "Could not detect imported group") return else: @@ -534,7 +534,7 @@ def load_append(self, context, path, name): # group is a subcollection of another name. grp_added = grp - conf.log("Identified collection/group {} as the primary imported".format( + env.log("Identified collection/group {} as the primary imported".format( grp_added), vv_only=True) # if rig not centered in original file, assume its group is @@ -543,7 +543,7 @@ def load_append(self, context, path, name): elif hasattr(grp_added, "instance_offset"): # 2.8 gl = grp_added.instance_offset else: - conf.log("Warning, could not set offset for group; null type?") + env.log("Warning, could not set offset for group; null type?") gl = (0, 0, 0) cl = util.get_cuser_location(context) @@ -552,7 +552,7 @@ def load_append(self, context, path, name): addedObjs = [ob for ob in grp_added.objects] for ob in all_objects: if ob not in addedObjs: - conf.log("This obj not in group {}: {}".format( + env.log("This obj not in group {}: {}".format( grp_added.name, ob.name)) # removes things like random bone shapes pulled in, # without deleting them, just unlinking them from the scene @@ -577,14 +577,14 @@ def load_append(self, context, path, name): # pass rig_obj = get_rig_from_objects(addedObjs) if not rig_obj: - conf.log("Could not get rig object") + env.log("Could not get rig object") self.report({'WARNING'}, "No armatures found!") else: - conf.log("Using object as primary rig: " + rig_obj.name) + env.log("Using object as primary rig: " + rig_obj.name) try: util.set_active_object(context, rig_obj) except RuntimeError: - conf.log("Failed to set {} as active".format(rig_obj)) + env.log("Failed to set {} as active".format(rig_obj)) rig_obj = None if rig_obj and self.clearPose or rig_obj and self.relocation == "Offset": @@ -618,7 +618,7 @@ def load_append(self, context, path, name): if self.relocation == "Offset" and posemode: set_bone = offset_root_bone(context, rig_obj) if not set_bone: - conf.log( + env.log( "This addon works better when the root bone's name is 'MAIN'") self.report( {'INFO'}, @@ -661,7 +661,7 @@ def execute(self, context): # to prevent re-drawing "load spawners!" if any one of the above # loaded nothing for any reason. - conf.loaded_all_spawners = True + env.loaded_all_spawners = True return {'FINISHED'} @@ -716,21 +716,21 @@ class MCPREP_UL_mob(bpy.types.UIList): def draw_item(self, context, layout, data, set, icon, active_data, active_propname, index): icon = "mob-{}".format(set.index) if self.layout_type in {'DEFAULT', 'COMPACT'}: - if not conf.use_icons: + if not env.use_icons: layout.label(text=set.name) - elif conf.use_icons and icon in conf.preview_collections["mobs"]: + elif env.use_icons and icon in env.preview_collections["mobs"]: layout.label( text=set.name, - icon_value=conf.preview_collections["mobs"][icon].icon_id) + icon_value=env.preview_collections["mobs"][icon].icon_id) else: layout.label(text=set.name, icon="BLANK1") elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' - if conf.use_icons and icon in conf.preview_collections["mobs"]: + if env.use_icons and icon in env.preview_collections["mobs"]: layout.label( text="", - icon_value=conf.preview_collections["mobs"][icon].icon_id) + icon_value=env.preview_collections["mobs"][icon].icon_id) else: layout.label(text="", icon='QUESTION') @@ -770,21 +770,21 @@ class MCPREP_UL_item(bpy.types.UIList): def draw_item(self, context, layout, data, set, icon, active_data, active_propname, index): icon = "item-{}".format(set.index) if self.layout_type in {'DEFAULT', 'COMPACT'}: - if not conf.use_icons: + if not env.use_icons: layout.label(text=set.name) - elif conf.use_icons and icon in conf.preview_collections["items"]: + elif env.use_icons and icon in env.preview_collections["items"]: layout.label( text=set.name, - icon_value=conf.preview_collections["items"][icon].icon_id) + icon_value=env.preview_collections["items"][icon].icon_id) else: layout.label(text=set.name, icon="BLANK1") elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' - if conf.use_icons and icon in conf.preview_collections["items"]: + if env.use_icons and icon in env.preview_collections["items"]: layout.label( text="", - icon_value=conf.preview_collections["items"][icon].icon_id) + icon_value=env.preview_collections["items"][icon].icon_id) else: layout.label(text="", icon='QUESTION') @@ -803,10 +803,10 @@ def draw_item(self, context, layout, data, set, icon, active_data, active_propna elif set.effect_type == effects.COLLECTION: layout.label(text=set.name, icon=COLL_ICON) elif set.effect_type == effects.IMG_SEQ: - if conf.use_icons and icon in conf.preview_collections["effects"]: + if env.use_icons and icon in env.preview_collections["effects"]: layout.label( text=set.name, - icon_value=conf.preview_collections["effects"][icon].icon_id) + icon_value=env.preview_collections["effects"][icon].icon_id) else: layout.label(text=set.name, icon="RENDER_RESULT") else: @@ -814,10 +814,10 @@ def draw_item(self, context, layout, data, set, icon, active_data, active_propna elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' - if conf.use_icons and icon in conf.preview_collections["effects"]: + if env.use_icons and icon in env.preview_collections["effects"]: layout.label( text="", - icon_value=conf.preview_collections["effects"][icon].icon_id) + icon_value=env.preview_collections["effects"][icon].icon_id) else: layout.label(text="", icon='QUESTION') @@ -827,45 +827,45 @@ class MCPREP_UL_material(bpy.types.UIList): def draw_item(self, context, layout, data, set, icon, active_data, active_propname, index): icon = "material-{}".format(set.index) if self.layout_type in {'DEFAULT', 'COMPACT'}: - if not conf.use_icons: + if not env.use_icons: layout.label(text=set.name) - elif conf.use_icons and icon in conf.preview_collections["materials"]: + elif env.use_icons and icon in env.preview_collections["materials"]: layout.label( text=set.name, - icon_value=conf.preview_collections["materials"][icon].icon_id) + icon_value=env.preview_collections["materials"][icon].icon_id) else: layout.label(text=set.name, icon="BLANK1") elif self.layout_type in {'GRID'}: layout.alignment = 'CENTER' - if conf.use_icons and icon in conf.preview_collections["materials"]: + if env.use_icons and icon in env.preview_collections["materials"]: layout.label( text="", - icon_value=conf.preview_collections["materials"][icon].icon_id) + icon_value=env.preview_collections["materials"][icon].icon_id) else: layout.label(text="", icon='QUESTION') class ListMobAssetsAll(bpy.types.PropertyGroup): """For listing hidden group of all mobs, regardless of category""" - description = bpy.props.StringProperty() - category = bpy.props.StringProperty() - mcmob_type = bpy.props.StringProperty() - index = bpy.props.IntProperty(min=0, default=0) # for icon drawing + description: bpy.props.StringProperty() + category: bpy.props.StringProperty() + mcmob_type: bpy.props.StringProperty() + index: bpy.props.IntProperty(min=0, default=0) # for icon drawing class ListMobAssets(bpy.types.PropertyGroup): """For UI drawing of mob assets and holding data""" - description = bpy.props.StringProperty() - category = bpy.props.StringProperty() # category it belongs to - mcmob_type = bpy.props.StringProperty() - index = bpy.props.IntProperty(min=0, default=0) # for icon drawing + description: bpy.props.StringProperty() + category: bpy.props.StringProperty() # category it belongs to + mcmob_type: bpy.props.StringProperty() + index: bpy.props.IntProperty(min=0, default=0) # for icon drawing class ListMeshswapAssets(bpy.types.PropertyGroup): """For UI drawing of meshswap assets and holding data""" - block = bpy.props.StringProperty() # block name only like "fire" - method = bpy.props.EnumProperty( + block: bpy.props.StringProperty() # block name only like "fire" + method: bpy.props.EnumProperty( name="Import method", # Collection intentionally first to be default for operator calls. items=[ @@ -873,39 +873,39 @@ class ListMeshswapAssets(bpy.types.PropertyGroup): ("object", "Object asset", "Object asset"), ] ) - description = bpy.props.StringProperty() + description: bpy.props.StringProperty() class ListEntityAssets(bpy.types.PropertyGroup): """For UI drawing of meshswap assets and holding data""" - entity = bpy.props.StringProperty() # virtual enum, Group/name - description = bpy.props.StringProperty() + entity: bpy.props.StringProperty() # virtual enum, Group/name + description: bpy.props.StringProperty() class ListItemAssets(bpy.types.PropertyGroup): """For UI drawing of item assets and holding data""" # inherited: name - description = bpy.props.StringProperty() - path = bpy.props.StringProperty(subtype='FILE_PATH') - index = bpy.props.IntProperty(min=0, default=0) # for icon drawing + description: bpy.props.StringProperty() + path: bpy.props.StringProperty(subtype='FILE_PATH') + index: bpy.props.IntProperty(min=0, default=0) # for icon drawing class ListModelAssets(bpy.types.PropertyGroup): """For UI drawing of mc model assets and holding data""" - filepath = bpy.props.StringProperty(subtype="FILE_PATH") - description = bpy.props.StringProperty() - # index = bpy.props.IntProperty(min=0, default=0) # icon pulled by name. + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + description: bpy.props.StringProperty() + # index: bpy.props.IntProperty(min=0, default=0) # icon pulled by name. class ListEffectsAssets(bpy.types.PropertyGroup): """For UI drawing for different kinds of effects""" # inherited: name - filepath = bpy.props.StringProperty(subtype="FILE_PATH") - subpath = bpy.props.StringProperty( + filepath: bpy.props.StringProperty(subtype="FILE_PATH") + subpath: bpy.props.StringProperty( description="Collection/particle/nodegroup within this file", default="") - description = bpy.props.StringProperty() - effect_type = bpy.props.EnumProperty( + description: bpy.props.StringProperty() + effect_type: bpy.props.EnumProperty( name="Effect type", items=( (effects.GEO_AREA, 'Geonode area', 'Instance wide-area geonodes effect'), @@ -913,7 +913,7 @@ class ListEffectsAssets(bpy.types.PropertyGroup): (effects.COLLECTION, 'Collection effect', 'Instance pre-animated collection'), (effects.IMG_SEQ, 'Image sequence', 'Instance an animated image sequence effect'), )) - index = bpy.props.IntProperty(min=0, default=0) # for icon drawing + index: bpy.props.IntProperty(min=0, default=0) # for icon drawing # ----------------------------------------------------------------------------- @@ -944,7 +944,6 @@ class ListEffectsAssets(bpy.types.PropertyGroup): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) diff --git a/MCprep_addon/tracking.py b/MCprep_addon/tracking.py index ebd635fc..d3020d4c 100644 --- a/MCprep_addon/tracking.py +++ b/MCprep_addon/tracking.py @@ -43,6 +43,7 @@ import textwrap import time from datetime import datetime + from .conf import env except Exception as err: print("[MCPREP Error] Failed tracker module load, invalid import module:") print('\t'+str(err)) @@ -484,18 +485,19 @@ def string_trunc(self, value): class TRACK_OT_toggle_enable_tracking(bpy.types.Operator): """Enabled or disable usage tracking""" - bl_idname = IDNAME+".toggle_enable_tracking" + bl_idname = f"{IDNAME}.toggle_enable_tracking" bl_label = "Toggle opt-in for analytics tracking" - bl_description = "Toggle anonymous usage tracking to help the developers. "+\ - " The only data tracked is what MCprep functions are used, key "+\ - "blender/addon information, and the timestamp of the addon installation" + bl_description = ( + "Toggle anonymous usage tracking to help the developers. " + "The only data tracked is what MCprep functions are used, key blender" + "/addon information, and the timestamp of the addon installation") options = {'REGISTER', 'UNDO'} - tracking = bpy.props.EnumProperty( - items = [('toggle', 'Toggle', 'Toggle operator use tracking'), + tracking: bpy.props.EnumProperty( + items=[('toggle', 'Toggle', 'Toggle operator use tracking'), ('enable', 'Enable', 'Enable operator use tracking'), ('disable', 'Disable', 'Disable operator use tracking (if already on)')], - name = "tracking") + name="tracking") def execute(self, context): if not VALID_IMPORT: @@ -543,13 +545,13 @@ class TRACK_OT_popup_report_error(bpy.types.Operator): bl_label = "MCprep Error, press OK below to send this report to developers" bl_description = "Report error to database, add additional comments for context" - error_report = bpy.props.StringProperty(default="") - comment = bpy.props.StringProperty( + error_report: bpy.props.StringProperty(default="") + comment: bpy.props.StringProperty( default="", maxlen=USER_COMMENT_LENGTH, options={'SKIP_SAVE'}) - action = bpy.props.EnumProperty( + action: bpy.props.EnumProperty( items = [('report', 'Send', 'Send the error report to developers, fully anonymous'), ('ignore', "Don't send", "Ignore this error report")], ) @@ -611,9 +613,9 @@ def draw(self, context): def execute(self, context): # if in headless mode, skip if bpy.app.background: - conf.log("Skip Report logging, running headless") - conf.log("Would have reported:") - #conf.log(self.error_report) + env.log("Skip Report logging, running headless") + env.log("Would have reported:") + #env.log(self.error_report) raise Exception(self.error_report) return {'CANCELLED'} @@ -861,7 +863,7 @@ def wrapper(self, context): elif hasattr(self, "skipUsage") and self.skipUsage is True: return res # skip running usage elif VALID_IMPORT is False: - conf.log("Skipping usage, VALID_IMPORT is False") + env.log("Skipping usage, VALID_IMPORT is False") return try: @@ -884,12 +886,12 @@ def wrapper_safe_handler(self): and Tracker._last_request.get("function") == self.track_function and Tracker._last_request.get("time") + Tracker._debounce > time.time() ): - conf.log("Skipping usage due to debounce") + env.log("Skipping usage due to debounce") run_track = False # If successful completion, run analytics function if relevant if bpy.app.background and run_track: - conf.log("Background mode, would have tracked usage: " + self.track_function) + env.log("Background mode, would have tracked usage: " + self.track_function) elif run_track: param = None exporter = None @@ -926,27 +928,6 @@ def layout_split(layout, factor=0.0, align=False): return layout.split(factor=factor, align=align) -def make_annotations(cls): - """Add annotation attribute to class fields to avoid Blender 2.8 warnings""" - if not hasattr(bpy.app, "version") or bpy.app.version < (2, 80): - return cls - if bpy.app.version < (2, 93, 0): - bl_props = { - k: v for k, v in cls.__dict__.items() if isinstance(v, tuple)} - else: - bl_props = { - k: v for k, v in cls.__dict__.items() - if isinstance(v, bpy.props._PropertyDeferred)} - if bl_props: - if '__annotations__' not in cls.__dict__: - setattr(cls, '__annotations__', {}) - annotations = cls.__dict__['__annotations__'] - for k, v in bl_props.items(): - annotations[k] = v - delattr(cls, k) - return cls - - classes = ( TRACK_OT_toggle_enable_tracking, TRACK_OT_popup_feedback, @@ -999,7 +980,7 @@ def register(bl_info): # used to define which server source, not just if's below if VALID_IMPORT: - Tracker.dev = conf.dev # True or False + Tracker.dev = env.dev_build # True or False else: Tracker.dev = False @@ -1015,7 +996,6 @@ def register(bl_info): Tracker.tracking_enabled = True # User accepted on download for cls in classes: - make_annotations(cls) bpy.utils.register_class(cls) # register install diff --git a/MCprep_addon/util.py b/MCprep_addon/util.py index c218eab1..0d058510 100755 --- a/MCprep_addon/util.py +++ b/MCprep_addon/util.py @@ -28,6 +28,7 @@ import bpy from . import conf +from .conf import env # Commonly used name for an excluded collection in Blender 2.8+ @@ -47,7 +48,7 @@ def apply_colorspace(node, color_enum): noncolor_override = None if not node.image: - conf.log("Node has no image applied yet, cannot change colorspace") + env.log("Node has no image applied yet, cannot change colorspace") # For later 2.8, fix images color space user if hasattr(node, "color_space"): # 2.7 and earlier 2.8 versions @@ -126,7 +127,7 @@ def bAppendLink(directory, name, toLink, active_layer=True): Returns: true if successful, false if not. """ - conf.log("Appending " + directory + " : " + name, vv_only=True) + env.log("Appending " + directory + " : " + name, vv_only=True) # for compatibility, add ending character if directory[-1] != "/" and directory[-1] != os.path.sep: @@ -134,7 +135,7 @@ def bAppendLink(directory, name, toLink, active_layer=True): if "link_append" in dir(bpy.ops.wm): # OLD method of importing, e.g. in blender 2.70 - conf.log("Using old method of append/link, 2.72 <=", vv_only=True) + env.log("Using old method of append/link, 2.72 <=", vv_only=True) try: bpy.ops.wm.link_append(directory=directory, filename=name, link=toLink) return True @@ -142,7 +143,7 @@ def bAppendLink(directory, name, toLink, active_layer=True): print("bAppendLink", e) return False elif "link" in dir(bpy.ops.wm) and "append" in dir(bpy.ops.wm): - conf.log("Using post-2.72 method of append/link", vv_only=True) + env.log("Using post-2.72 method of append/link", vv_only=True) if toLink: bpy.ops.wm.link(directory=directory, filename=name) elif bv28(): @@ -155,7 +156,7 @@ def bAppendLink(directory, name, toLink, active_layer=True): print("bAppendLink", e) return False else: - conf.log("{} {} {}".format(directory, name, active_layer)) + env.log("{} {} {}".format(directory, name, active_layer)) try: bpy.ops.wm.append( directory=directory, @@ -212,6 +213,7 @@ def min_bv(version, *, inclusive=True): def bv28(): """Check if blender 2.8, for layouts, UI, and properties. """ + env.deprecation_warning() return min_bv((2, 80)) @@ -297,13 +299,13 @@ def loadTexture(texture): if base_filepath == bpy.path.abspath(texture): data_img = bpy.data.images[base] data_img.reload() - conf.log("Using already loaded texture", vv_only=True) + env.log("Using already loaded texture", vv_only=True) else: data_img = bpy.data.images.load(texture, check_existing=True) - conf.log("Loading new texture image", vv_only=True) + env.log("Loading new texture image", vv_only=True) else: data_img = bpy.data.images.load(texture, check_existing=True) - conf.log("Loading new texture image", vv_only=True) + env.log("Loading new texture image", vv_only=True) return data_img @@ -337,11 +339,11 @@ def link_selected_objects_to_scene(): def open_program(executable): # Open an external program from filepath/executbale executable = bpy.path.abspath(executable) - conf.log("Open program request: " + executable) + env.log("Open program request: " + executable) # input could be .app file, which appears as if a folder if not os.path.isfile(executable): - conf.log("File not executable") + env.log("File not executable") if not os.path.isdir(executable): return -1 elif not executable.lower().endswith(".app"): @@ -351,7 +353,7 @@ def open_program(executable): osx_or_linux = platform.system() == "Darwin" osx_or_linux = osx_or_linux or 'linux' in platform.system().lower() if executable.lower().endswith('.exe') and osx_or_linux: - conf.log("Opening program via wine") + env.log("Opening program via wine") p = Popen(['which', 'wine'], stdin=PIPE, stdout=PIPE, stderr=PIPE) stdout, err = p.communicate(b"") has_wine = stdout and not err @@ -359,7 +361,7 @@ def open_program(executable): if has_wine: # wine is installed; this will hang blender until mineways closes. p = Popen(['wine', executable], stdin=PIPE, stdout=PIPE, stderr=PIPE) - conf.log( + env.log( "Opening via wine + direct executable, will hang blender till closed") # communicating with process makes it hang, so trust it works @@ -372,18 +374,18 @@ def open_program(executable): try: # attempt to use blender's built-in method res = bpy.ops.wm.path_open(filepath=executable) if res == {"FINISHED"}: - conf.log("Opened using built in path opener") + env.log("Opened using built in path opener") return 0 else: - conf.log("Did not get finished response: ", str(res)) + env.log("Did not get finished response: ", str(res)) except: - conf.log("failed to open using builtin mehtod") + env.log("failed to open using builtin mehtod") pass if platform.system() == "Darwin" and executable.lower().endswith(".app"): # for mac, if folder, check that it has .app otherwise throw -1 # (right now says will open even if just folder!!) - conf.log("Attempting to open .app via system Open") + env.log("Attempting to open .app via system Open") p = Popen(['open', executable], stdin=PIPE, stdout=PIPE, stderr=PIPE) stdout, err = p.communicate(b"") if err != b"": @@ -447,7 +449,7 @@ def addGroupInstance(group_name, loc, select=True): def load_mcprep_json(): """Load in the json file, defered so not at addon enable time.""" - path = conf.json_path + path = env.json_path default = { "blocks": { "reflective": [], @@ -465,18 +467,18 @@ def load_mcprep_json(): "make_real": [] } if not os.path.isfile(path): - conf.log("Error, json file does not exist: " + path) - conf.json_data = default + env.log("Error, json file does not exist: " + path) + env.json_data = default return False with open(path) as data_file: try: - conf.json_data = json.load(data_file) - conf.log("Successfully read the JSON file") + env.json_data = json.load(data_file) + env.log("Successfully read the JSON file") return True except Exception as err: print("Failed to load json file:") print('\t', err) - conf.json_data = default + env.json_data = default def ui_scale(): @@ -557,7 +559,6 @@ def alphanum_key(key): return sorted(elements, key=alphanum_key) - # ----------------------------------------------------------------------------- # Cross blender 2.7 and 2.8 functions # ----------------------------------------------------------------------------- @@ -565,6 +566,7 @@ def alphanum_key(key): def make_annotations(cls): """Add annotation attribute to class fields to avoid Blender 2.8 warnings""" + env.deprecation_warning() if not hasattr(bpy.app, "version") or bpy.app.version < (2, 80): return cls if bpy.app.version < (2, 93, 0): diff --git a/MCprep_addon/util_operators.py b/MCprep_addon/util_operators.py index 335d6526..04433309 100644 --- a/MCprep_addon/util_operators.py +++ b/MCprep_addon/util_operators.py @@ -94,7 +94,7 @@ class MCPREP_OT_show_preferences(bpy.types.Operator): bl_idname = "mcprep.open_preferences" bl_label = "Show MCprep preferences" - tab = bpy.props.EnumProperty( + tab: bpy.props.EnumProperty( items=[ ('settings', 'Open settings', 'Open MCprep preferences settings'), ('tutorials', 'Open tutorials', 'View MCprep tutorials'), @@ -142,7 +142,7 @@ class MCPREP_OT_open_folder(bpy.types.Operator): bl_idname = "mcprep.openfolder" bl_label = "Open folder" - folder = bpy.props.StringProperty( + folder: bpy.props.StringProperty( name="Folderpath", default="//") @@ -171,7 +171,7 @@ class MCPREP_OT_open_help(bpy.types.Operator): bl_label = "Open help page" bl_description = "Need help? Click to open a reference page" - url = bpy.props.StringProperty( + url: bpy.props.StringProperty( name="Url", default="") @@ -190,11 +190,11 @@ class MCPREP_OT_prep_material_legacy(bpy.types.Operator): bl_label = "MCprep Materials" bl_options = {'REGISTER', 'UNDO'} - useReflections = bpy.props.BoolProperty( + useReflections: bpy.props.BoolProperty( name="Use reflections", description="Allow appropriate materials to be rendered reflective", default=True) - combineMaterials = bpy.props.BoolProperty( + combineMaterials: bpy.props.BoolProperty( name="Combine materials", description="Consolidate duplciate materials & textures", default=False) @@ -235,7 +235,6 @@ def execute(self, context): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) diff --git a/MCprep_addon/world_tools.py b/MCprep_addon/world_tools.py index 5dec4ba2..2e94b044 100644 --- a/MCprep_addon/world_tools.py +++ b/MCprep_addon/world_tools.py @@ -25,6 +25,7 @@ from bpy_extras.io_utils import ExportHelper, ImportHelper from . import conf +from .conf import env from . import util from . import tracking from .materials import generate @@ -255,7 +256,7 @@ class MCPREP_OT_open_jmc2obj(bpy.types.Operator): bl_description = "Open the jmc2obj executbale" # poll, and prompt to download if not present w/ tutorial link - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -333,7 +334,7 @@ class MCPREP_OT_open_mineways(bpy.types.Operator): bl_description = "Open the Mineways executbale" # poll, and prompt to download if not present w/ tutorial link - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -416,11 +417,11 @@ class MCPREP_OT_import_world_split(bpy.types.Operator, ImportHelper): bl_label = "Import World" bl_options = {'REGISTER', 'UNDO'} - filter_glob = bpy.props.StringProperty( + filter_glob: bpy.props.StringProperty( default="*.obj;*.mtl", options={'HIDDEN'}) fileselectparams = "use_filter_blender" - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -618,7 +619,7 @@ class MCPREP_OT_prep_world(bpy.types.Operator): bl_description = "Prep world render settings to something generally useful" bl_options = {'REGISTER', 'UNDO'} - skipUsage = bpy.props.BoolProperty( + skipUsage: bpy.props.BoolProperty( default=False, options={'HIDDEN'}) @@ -745,11 +746,11 @@ def prep_world_internal(self, context): sky_used = True break if sky_used: - conf.log("MCprep sky being used with atmosphere") + env.log("MCprep sky being used with atmosphere") context.scene.world.use_sky_blend = False context.scene.world.horizon_color = (0.00938029, 0.0125943, 0.0140572) else: - conf.log("No MCprep sky with atmosphere") + env.log("No MCprep sky with atmosphere") context.scene.world.use_sky_blend = True context.scene.world.horizon_color = (0.647705, 0.859927, 0.940392) context.scene.world.zenith_color = (0.0954261, 0.546859, 1) @@ -802,13 +803,13 @@ def enum_options(self, context): "Create static sky, with no sun or moon")) return enums - world_type = bpy.props.EnumProperty( + world_type: bpy.props.EnumProperty( name="Sky type", description=( "Decide to improt dynamic (time/hour-controlled) vs static sky " "(daytime only), and the type of sun/moon (if any) to use"), items=enum_options) - initial_time = bpy.props.EnumProperty( + initial_time: bpy.props.EnumProperty( name="Set time (dynamic only)", description="Set initial time of day, only supported for dynamic sky types", items=( @@ -818,11 +819,11 @@ def enum_options(self, context): ("0", "Midnight", "Set initial time to 12am"), ("6", "Sunrise", "Set initial time to 6am")) ) - add_clouds = bpy.props.BoolProperty( + add_clouds: bpy.props.BoolProperty( name="Add clouds", description="Add in a cloud mesh", default=True) - remove_existing_suns = bpy.props.BoolProperty( + remove_existing_suns: bpy.props.BoolProperty( name="Remove initial suns", description="Remove any existing sunlamps", default=True) @@ -891,7 +892,7 @@ def execute(self, context): self.report( {'ERROR'}, "Source MCprep world blend file does not exist: " + blendfile) - conf.log( + env.log( "Source MCprep world blend file does not exist: " + blendfile) return {'CANCELLED'} if wname in bpy.data.worlds: @@ -924,7 +925,7 @@ def execute(self, context): time_obj = get_time_object() if not time_obj: - conf.log( + env.log( "TODO: implement create time_obj, parent sun to it & driver setup") if self.world_type in ("world_static_mesh", "world_mesh"): @@ -932,7 +933,7 @@ def execute(self, context): self.report( {'ERROR'}, "Source MCprep world blend file does not exist: " + blendfile) - conf.log( + env.log( "Source MCprep world blend file does not exist: " + blendfile) return {'CANCELLED'} resource = blendfile + "/Object" @@ -1035,7 +1036,7 @@ def create_dynamic_world(self, context, blendfile, wname): context.scene.world["mcprep_world"] = True else: self.report({'ERROR'}, "Failed to import new world") - conf.log("Failed to import new world") + env.log("Failed to import new world") # assign sun/moon shader accordingly use_shader = 1 if self.world_type == "world_shader" else 0 @@ -1057,23 +1058,22 @@ def create_dynamic_world(self, context, blendfile, wname): # if needed: create time object and setup drivers # if not time_obj: - # conf.log("Creating time_obj") - # time_obj = bpy.data.objects.new('MCprep Time Control', None) - # util.obj_link_scene(time_obj, context) - # global time_obj_cache - # time_obj_cache = time_obj - # if hasattr(time_obj, "empty_draw_type"): # 2.7 - # time_obj.empty_draw_type = 'SPHERE' - # else: # 2.8 - # time_obj.empty_display_type = 'SPHERE' - + # env.log("Creating time_obj") + # time_obj = bpy.data.objects.new('MCprep Time Control', None) + # util.obj_link_scene(time_obj, context) + # global time_obj_cache + # time_obj_cache = time_obj + # if hasattr(time_obj, "empty_draw_type"): # 2.7 + # time_obj.empty_draw_type = 'SPHERE' + # else: # 2.8 + # time_obj.empty_display_type = 'SPHERE' # first, get the driver # if (not world.node_tree.animation_data - # or not world.node_tree.animation_data.drivers - # or not world.node_tree.animation_data.drivers[0].driver): - # conf.log("Could not get driver from imported dynamic world") - # self.report({'WARNING'}, "Could not update driver for dynamic world") - # driver = None + # or not world.node_tree.animation_data.drivers + # or not world.node_tree.animation_data.drivers[0].driver): + # env.log("Could not get driver from imported dynamic world") + # self.report({'WARNING'}, "Could not update driver for dynamic world") + # driver = None # else: # driver = world.node_tree.animation_data.drivers[0].driver # if driver and driver.variables[0].targets[0].id_type == 'OBJECT': @@ -1090,7 +1090,7 @@ class MCPREP_OT_time_set(bpy.types.Operator): bl_options = {'REGISTER', 'UNDO'} # subject center to place lighting around - time_enum = bpy.props.EnumProperty( + time_enum: bpy.props.EnumProperty( name="Time selection", description="Select between the different reflections", items=[ @@ -1104,7 +1104,7 @@ class MCPREP_OT_time_set(bpy.types.Operator): ("18000", "Midnight", "Time=18,000, moon at zenish"), ("23000", "Sunrise", "Time set day=23,000, sun first visible") ]) - day_offset = bpy.props.IntProperty( + day_offset: bpy.props.IntProperty( name="Day offset", description="Offset by number of days (ie +/- 24000*n)", default=0) @@ -1155,7 +1155,7 @@ class MCPREP_OT_render_helper(): def cleanup_scene(self): # Clean up - conf.log("Cleanup pano rendering") + env.log("Cleanup pano rendering") for i in range(len(self.render_queue_cleanup)): util.obj_unlink_remove(self.render_queue_cleanup[i]["camera"], True) @@ -1192,7 +1192,7 @@ def create_panorama_cam(self, name, camera_data, rot, loc): return camera def cancel_render(self, scene): - conf.log("Cancelling pano render queue") + env.log("Cancelling pano render queue") self.render_queue = [] self.cleanup_scene() @@ -1218,7 +1218,7 @@ def display_current(self, use_rendered=False): else: header_text = "Pano render finished" - conf.log(header_text) + env.log(header_text) area.header_text_set(header_text) area.show_menus = False @@ -1248,7 +1248,7 @@ def render_next_in_queue(self, scene, dummy): self.prior_frame = self.current_render if not self.render_queue: - conf.log("Finished pano render queue") + env.log("Finished pano render queue") self.cleanup_scene() return @@ -1259,7 +1259,7 @@ def render_next_in_queue(self, scene, dummy): bpy.context.scene.render.filepath = os.path.join( self.filepath, self.current_render["filename"]) - conf.log("Starting pano render {}".format(self.current_render["filename"])) + env.log("Starting pano render {}".format(self.current_render["filename"])) self.display_current() bpy.app.timers.register( @@ -1271,7 +1271,7 @@ def render_next_in_queue(self, scene, dummy): def init_render_timer(): """Helper for pano renders to offset the start of the queue from op run.""" - conf.log("Initial render timer started pano queue") + env.log("Initial render timer started pano queue") render_helper.render_next_in_queue(None, None) @@ -1288,17 +1288,17 @@ class MCPREP_OT_render_panorama(bpy.types.Operator, ExportHelper): bl_description = "Render Panorama for texture Pack" bl_options = {'REGISTER', 'UNDO'} - panorama_resolution = bpy.props.IntProperty( + panorama_resolution: bpy.props.IntProperty( name="Render resolution", description="The resolution of the output images", default=1024 ) - open_folder = bpy.props.BoolProperty( + open_folder: bpy.props.BoolProperty( name="Open folder when done", description="Open the output folder when render completes", default=False) - filepath = bpy.props.StringProperty(subtype='DIR_PATH') + filepath: bpy.props.StringProperty(subtype='DIR_PATH') filename_ext = "" # Not used, but required by ExportHelper. def draw(self, context): @@ -1391,7 +1391,6 @@ def execute(self, context): def register(): for cls in classes: - util.make_annotations(cls) bpy.utils.register_class(cls) diff --git a/compile.sh b/compile.sh index ed9b9088..e2910393 100755 --- a/compile.sh +++ b/compile.sh @@ -15,10 +15,22 @@ else FAST_RELOAD=true fi + +while getopts 'f:d' flag +do + case "${flag}" in + f) echo "Running a fast compile..." + FAST_RELOAD=true + ;; + d) DEV_BUILD=true;; + *) echo "Invalid flag!" && exit;; + esac + +done + NAME=MCprep_addon BLENDER_INSTALLS=blender_installs.txt - # Remove left over files in the build folder, but leaves the zip. function clean(){ echo "Cleaning build folder" @@ -51,6 +63,13 @@ function build() { cp -r $NAME/materials build/$NAME/ cp -r $NAME/spawner build/$NAME/ + if [ "$DEV_BUILD" = true ] + then + echo "Creating dev build..." + touch build/$NAME/mcprep_dev.txt + else + rm -f build/$NAME/mcprep_dev.txt # Make sure this is removed + fi if [ "$FAST_RELOAD" = false ] then @@ -59,12 +78,11 @@ function build() { cp -r $NAME/MCprep_resources build/$NAME/ # Making the zip with all the sub files is also slow. - cd build + cd build || exit rm $NAME.zip # Compeltely remove old version (else it's append/replace) zip $NAME.zip -rq $NAME cd ../ fi - } @@ -81,9 +99,9 @@ function detect_installs() { then # Add all ls -rd -- /Users/*/Library/Application\ Support/Blender/*/scripts/addons/ > $BLENDER_INSTALLS - elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ] + elif [ "$(uname -s | cut -c 1-5)" == "Linux" ] then - echo "TODO support platform, manually populate" + ls -rd -- ~/.config/blender/*/scripts/addons > $BLENDER_INSTALLS exit else echo "Unsupported platform, manually populate" @@ -102,7 +120,7 @@ function install_path(){ then # echo "Remove prior: $i/$NAME/" # ls "$i/$NAME/" - rm -r "$i/$NAME/" + rm -rf "${i/$NAME:?}/" fi mkdir -p "$i/$NAME" diff --git a/mcprep_data_refresh.py b/mcprep_data_refresh.py index 09a86a36..8a3248b2 100755 --- a/mcprep_data_refresh.py +++ b/mcprep_data_refresh.py @@ -1,4 +1,3 @@ -#!/opt/homebrew/bin/python3 # Tool to pull down material names from jmc2obj and Mineways import json diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..8d90efb6 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,18 @@ +# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. + +[[package]] +name = "fake-bpy-module-2-80" +version = "20230117" +description = "Collection of the fake Blender Python API module for the code completion." +category = "main" +optional = false +python-versions = ">=3.7" +files = [ + {file = "fake-bpy-module-2.80-20230117.tar.gz", hash = "sha256:eee7e6edb0b27c723bed3eb7b9e818cf2cbb535909c340a9e3ba950190458c74"}, + {file = "fake_bpy_module_2.80-20230117-py3-none-any.whl", hash = "sha256:911b38d2581d4298a244ba654f7eb2c105146038b963bea378669492b5155c1f"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.7" +content-hash = "0dcfbebc366679b4eb077fbd04fcbc554e02c03d7af69ab0ed55275fc0931959" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..41e82f11 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "MCprep" +version = "3.5" +description = "Blender python addon to increase workflow for creating minecraft renders and animations" +authors = ["TheDuckCow"] +license = "GPL-3.0-only" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.7" +fake-bpy-module-2-80 = "^20230117" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/run_tests.sh b/run_tests.sh index 28d6f196..238f6302 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -30,7 +30,7 @@ TEST_RUNNERS=( ) # Update the mappings. -./mcprep_data_refresh.py -auto +python3 ./mcprep_data_refresh.py -auto # First, do a soft reload of python files. echo "Soft py file reload" diff --git a/test_files/addon_tests.py b/test_files/addon_tests.py index 13c43dff..9be7c287 100644 --- a/test_files/addon_tests.py +++ b/test_files/addon_tests.py @@ -209,7 +209,7 @@ def setup_env_paths(self): spec.loader.exec_module(module) from MCprep import conf - conf.init() + conf.env = conf.MCprepEnv() def get_mcprep_path(self): """Returns the addon basepath installed in this blender instance""" @@ -956,7 +956,7 @@ def import_materials_util(self, mapping_set): from MCprep import conf util.load_mcprep_json() # force load json cache - mcprep_data = conf.json_data["blocks"][mapping_set] + mcprep_data = conf.env.json_data["blocks"][mapping_set] # first detect alignment to the raw underlining mappings, nothing to # do with canonical yet @@ -1117,7 +1117,7 @@ def canonical_name_no_none(self): if not did_raise: return "None input SHOULD raise error" - # TODO: patch conf.json_data["blocks"] used by addon if possible, + # TODO: patch conf.env.json_data["blocks"] used by addon if possible, # if this is transformed into a true py unit test. This will help # check against report (-MNGGQfGGTJRqoizVCer) @@ -2389,7 +2389,7 @@ class MCPTEST_OT_test_run(bpy.types.Operator): bl_idname = "mcpreptest.run_test" bl_description = "Run specified test index" - index = bpy.props.IntProperty(default=0) + index: bpy.props.IntProperty(default=0) def execute(self, context): # ind = context.window_manager.mcprep_test_index @@ -2488,7 +2488,6 @@ def register(): # context.window_manager.mcprep_test_index = -1 put into handler to reset? for cls in classes: - # util.make_annotations(cls) bpy.utils.register_class(cls)