diff --git a/MCprep_addon/materials/generate.py b/MCprep_addon/materials/generate.py index 3598f89a..02c492bd 100644 --- a/MCprep_addon/materials/generate.py +++ b/MCprep_addon/materials/generate.py @@ -39,7 +39,7 @@ def get_mc_canonical_name(name): """Convert a material name to standard MC name. Returns: - canonical name + canonical name, or fallback to generalized name (never returns None) form (mc, jmc, or mineways) """ general_name = util.nameGeneralize(name) @@ -50,8 +50,8 @@ def get_mc_canonical_name(name): # Special case to allow material names, e.g. in meshswap, to end in .emit # while still mapping to canonical names, to pick up features like animated - # textures. - if ".emit" in general_name: + # textures. Cross check that name isn't exactly .emit to avoid None return. + if ".emit" in general_name and general_name != ".emit": general_name = general_name.replace(".emit", "") if ("blocks" not in conf.json_data @@ -66,20 +66,24 @@ def get_mc_canonical_name(name): elif general_name in conf.json_data["blocks"]["block_mapping_jmc"]: canon = conf.json_data["blocks"]["block_mapping_jmc"][general_name] form = "jmc2obj" - elif general_name.lower() in conf.json_data["blocks"]["block_mapping_jmc"]: - canon = conf.json_data["blocks"]["block_mapping_jmc"][general_name.lower()] - form = "jmc2obj" elif general_name in conf.json_data["blocks"]["block_mapping_mineways"]: canon = conf.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"][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"][general_name.lower()] form = "mineways" else: - conf.log("Canonical name not matched: "+general_name, True) + conf.log("Canonical name not matched: " + general_name, True) canon = general_name form = None + if canon is None or canon == '': + conf.log("Error: Encountered None canon value with " + str(general_name)) + canon = general_name + return canon, form diff --git a/MCprep_addon/materials/material_manager.py b/MCprep_addon/materials/material_manager.py index 842c3b34..66dec8c5 100644 --- a/MCprep_addon/materials/material_manager.py +++ b/MCprep_addon/materials/material_manager.py @@ -37,7 +37,7 @@ def reload_materials(context): """Reload the material UI list""" mcprep_props = context.scene.mcprep_props resource_folder = bpy.path.abspath(context.scene.mcprep_texturepack_path) - extensions = [".png",".jpg",".jpeg"] + extensions = [".png", ".jpg", ".jpeg"] mcprep_props.material_list.clear() if conf.use_icons and conf.preview_collections["materials"]: @@ -58,8 +58,8 @@ def reload_materials(context): search_paths = [ resource_folder, - os.path.join(resource_folder,"blocks"), - os.path.join(resource_folder,"block")] + os.path.join(resource_folder, "blocks"), + os.path.join(resource_folder, "block")] files = [] for path in search_paths: @@ -434,7 +434,8 @@ def load_from_texturepack(self, mat): # even if images of same name already exist, load new block conf.log("Find missing images: Creating new image datablock for "+mat.name) - image = bpy.data.images.load(image_path, check_existing=False) + # do not use 'check_existing=False' to keep compatibility pre 2.79 + image = bpy.data.images.load(image_path) engine = bpy.context.scene.render.engine if engine == 'CYCLES' or engine == 'BLENDER_EEVEE': diff --git a/MCprep_addon/materials/prep.py b/MCprep_addon/materials/prep.py index 429bc640..785ac29f 100755 --- a/MCprep_addon/materials/prep.py +++ b/MCprep_addon/materials/prep.py @@ -474,7 +474,7 @@ def draw(self, context): track_function = "generate_mat" track_param = None @tracking.report_error - def execute(self,context): + def execute(self, context): scn_props = context.scene.mcprep_props mat_item = scn_props.material_list[scn_props.material_list_index] mat, err = self.generate_base_material( @@ -505,7 +505,7 @@ def execute(self,context): def generate_base_material(self, context, name, path): """Generate a base material from name and active resource pack""" - image = bpy.data.images.load(path, check_existing=False) + image = bpy.data.images.load(path) mat = bpy.data.materials.new(name=name) engine = context.scene.render.engine diff --git a/MCprep_addon/spawner/meshswap.py b/MCprep_addon/spawner/meshswap.py index 82cf3222..afb43d22 100755 --- a/MCprep_addon/spawner/meshswap.py +++ b/MCprep_addon/spawner/meshswap.py @@ -100,7 +100,7 @@ def move_assets_to_excluded_layer(context, collections): for grp in collections: if grp.name not in initial_view_coll.collection.children: - continue # not linked, likely a sub-group not added to scn + continue # not linked, likely a sub-group not added to scn initial_view_coll.collection.children.unlink(grp) if grp.name not in meshswap_exclude_vl.collection.children: meshswap_exclude_vl.collection.children.link(grp) @@ -217,7 +217,7 @@ def swap_enum(self, context): description="Automatically snap to whole block locations") make_real = bpy.props.BoolProperty( name="Make real", - default=True, + default=False, # TODO: make True once able to retain animations like fire description="Automatically make groups real after placement") # toLink = bpy.props.BoolProperty( @@ -299,12 +299,32 @@ def execute(self, context): # make real doesn't select resulting output now (true for # earlier versions of 2.8, not true in 2.82 at least where # selects everything BUT the source of original instance + pre_objs = list(bpy.data.objects) bpy.ops.object.duplicates_make_real() post_objs = list(bpy.data.objects) new_objs = list(set(post_objs)-set(pre_objs)) for obj in new_objs: util.select_set(obj, True) + + # Re-apply animation if any + # TODO: Causes an issue for any animation that depends + # on direct xyz placement (noting that parents are + # cleared on Make Real). Could try adding a parent to + # any given object's current location and give the + # equivalent xyz offset at some point in time. + """ + orig_objs = [obo for obo in group.objects + if util.nameGeneralize(obj.name) == util.nameGeneralize(obo.name)] + if not orig_objs: + continue + obo = orig_objs[0] + if not obo or not obo.animation_data: + continue + if not obj.animation_data: + obj.animation_data_create() + obj.animation_data.action = obo.animation_data.action + """ else: bpy.ops.object.duplicates_make_real() diff --git a/MCprep_addon/tracking.py b/MCprep_addon/tracking.py index 0b6e4986..159b6189 100644 --- a/MCprep_addon/tracking.py +++ b/MCprep_addon/tracking.py @@ -21,6 +21,9 @@ # fail to work because of this module) VALID_IMPORT = True +# Request timeout (total request time may still be longer) +TIMEOUT = 60 + # critical to at least load these import os import bpy @@ -288,7 +291,7 @@ def raw_request(self, method, path, payload, callback=None): resp = self._raw_request_mod_http(method, path, payload) self._httpclient_fallback = True elif self._httpclient_fallback is False: - resp = self._raw_request_mod_requests(method, path, payload) + resp = self._raw_request_mod_requests(method, path, payload) else: resp = self._raw_request_mod_http(method, path, payload) @@ -305,12 +308,12 @@ def _raw_request_mod_requests(self, method, path, payload): be used. This function works in blender 2.8, and at least 2.79. """ url = self._appurl + path - if method=="POST": - res = requests.post(url, payload) - elif method=="GET": - res = requests.get(url) - elif method=="PUT": - res = requests.put(url, payload) + if method == "POST": + res = requests.post(url, payload, timeout=TIMEOUT) + elif method == "GET": + res = requests.get(url, timeout=TIMEOUT) + elif method == "PUT": + res = requests.put(url, payload, timeout=TIMEOUT) else: raise ValueError("raw_request input must be GET, POST, or PUT") @@ -346,7 +349,7 @@ def _raw_request_mod_http(self, method, path, payload): print("Error: "+str(err)) return {'status':'NO_CONNECTION'} - if method=="POST" or method=="PUT": + if method in ("POST", "PUT"): connection.request(method, path, payload) elif method == "GET": connection.request(method, path) diff --git a/test_files/addon_tests.py b/test_files/addon_tests.py index 848ddac0..91644dbf 100644 --- a/test_files/addon_tests.py +++ b/test_files/addon_tests.py @@ -61,6 +61,7 @@ def __init__(self): self.import_mineways_combined, self.name_generalize, self.canonical_name_no_none, + self.canonical_test_mappings, self.meshswap_spawner, self.meshswap_jmc2obj, self.meshswap_mineways_separated, @@ -803,12 +804,49 @@ def canonical_name_no_none(self): mats = materialsFromObj(bpy.context.selected_objects) canons = [[get_mc_canonical_name(mat.name)][0] for mat in mats] - if None in canons: # detect None response to canon input + if None in canons: # detect None response to canon input return "Canon returned none value" + if '' in canons: + return "Canon returned empty str value" - # calling the reload materials operation, as this is where some error - # reports ran into this issue (but could not replicate) - bpy.ops.mcprep.reload_materials() + # Ensure it never returns None + in_str, _ = get_mc_canonical_name('') + if in_str != '': + return "Empty str should return empty string, not" + str(in_str) + + did_raise = False + try: + get_mc_canonical_name(None) + except: + did_raise = True + if not did_raise: + return "None input SHOULD raise error" + + # TODO: patch conf.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) + + def canonical_test_mappings(self): + """Test some specific mappings to ensure they return correctly.""" + from MCprep.materials.generate import get_mc_canonical_name + + misc = { + ".emit": ".emit", + } + jmc_to_canon = { + "grass": "grass", + "mushroom_red": "red_mushroom", + # "slime": "slime_block", # KNOWN jmc, need to address + } + mineways_to_canon = {} + + for map_type in [misc, jmc_to_canon, mineways_to_canon]: + for key, val in map_type.items(): + res, mapped = get_mc_canonical_name(key) + if res == val: + continue + return "Wrong mapping: {} mapped to {} ({}), not {}".format( + key, res, mapped, val) def meshswap_util(self, mat_name): """Run meshswap on the first object with found mat_name"""