From 344d307a14409218793b9362e47da438b05ecf1b Mon Sep 17 00:00:00 2001 From: TheDuckCow Date: Sun, 2 Oct 2016 17:18:11 -0400 Subject: [PATCH] several change sincluding now fully funcitonal skin swapping --- MCprep_addon/addon_updater.py | 85 +++-- MCprep_addon/addon_updater_ops.py | 62 +++- MCprep_addon/conf.py | 24 +- MCprep_addon/materials.py | 565 ++++++++++++++++++++++++++++-- MCprep_addon/mcprep_ui.py | 163 +++++++-- MCprep_addon/meshswap.py | 4 +- MCprep_addon/spawner.py | 43 ++- MCprep_addon/tracking.py | 6 +- MCprep_addon/util.py | 86 ++++- 9 files changed, 874 insertions(+), 164 deletions(-) diff --git a/MCprep_addon/addon_updater.py b/MCprep_addon/addon_updater.py index e419c5ea..b58161ac 100644 --- a/MCprep_addon/addon_updater.py +++ b/MCprep_addon/addon_updater.py @@ -19,22 +19,7 @@ """ See documentation for usage - - - -# Check for existing updates or any past versions -# option 1: backgorund, async check with callback function input -myUpdater.check_for_update_async(callback=None) # input callback function -# option 2: immediate, thread-blocking -(update_ready, version, link) = myUpdater.check_for_update() - -# run the update -run_update(revert_tag=None) -# or, select a verion to install -tags = myUpdater.get_tag_names() -latest_tag = tags[0] # equivalent to using revert_tag = None -myUpdater.run_update(revert_tag=latest_tag) # e.g. latest_tag = "v1.0.0" - +https://github.com/CGCookie/blender-addon-updater """ @@ -107,7 +92,6 @@ def __init__(self): self._fake_install = False self._async_checking = False # only true when async daemon started self._update_ready = None - self._connection_failed = False self._update_link = None self._update_version = None self._source_zip = None @@ -120,6 +104,8 @@ def __init__(self): self._addon+"_updater") self._addon_root = os.path.dirname(__file__) self._json = {} + self._error = None + self._error_msg = None # ------------------------------------------------------------------------- @@ -180,10 +166,6 @@ def json(self): self.set_updater_json() return self._json - @property - def connection_failed(self): - return self._connection_failed - @property def repo(self): return self._repo @@ -336,6 +318,14 @@ def check_interval(self): def check_interval(self, value): raise ValueError("Check frequency is read-only") + @property + def error(self): + return self._error + + @property + def error_msg(self): + return self._error_msg + # ------------------------------------------------------------------------- # Parameter validation related functions @@ -377,13 +367,20 @@ def form_repo_url(self): def get_tags(self): request = "/repos/"+self.user+"/"+self.repo+"/tags" # print("Request url: ",request) - if self.verbose:print("Grabbing tags from server") + if self.verbose:print("Getting tags from server") # do more error checking e.g. no connection here self._tags = self.get_api(request) - if len(self._tags) == 0: + if self._tags == None: + # some error occured self._tag_latest = None - if self.verbose:print("No tags found on this repository") + self._tags = [] + return + elif len(self._tags) == 0: + self._tag_latest = None + self._error = "No releases found" + self._error_msg = "No releases or tags found on this repository" + if self.verbose:print("No releases or tags found on this repository") else: self._tag_latest = self._tags[0] if self.verbose:print("Most recent tag found:",self._tags[0]) @@ -395,11 +392,14 @@ def get_api_raw(self, url): try: result = urllib.request.urlopen(request) except urllib.error.HTTPError as e: - raise ValueError("HTTPError, code: ",e.code) - # return or raise error? + self._error = "HTTP error" + self._error_msg = str(e.code) + self._update_ready = None except urllib.error.URLError as e: - raise ValueError("URLError, reason: ",e.reason) - # return or raise error? + self._error = "URL error, check internet connection" + self._error_msg = str(e.reason) + self._update_ready = None + return None else: result_string = result.read() result.close() @@ -412,7 +412,10 @@ def get_api(self, url): # return the json version get = None get = self.get_api_raw(url) # this can fail by self-created error raising - return json.JSONDecoder().decode( get ) + if get != None: + return json.JSONDecoder().decode( get ) + else: + return None # create a working directory and download the new files @@ -444,7 +447,6 @@ def stage_repository(self, url): if self._verbose: print("Error: Aborting update, "+error) raise ValueError("Aborting update, "+error) - if self._verbose:print("Todo: create backup zip of current addon now") if self._backup_current==True: self.create_backup() if self._verbose:print("Now retreiving the new source zip") @@ -589,7 +591,6 @@ def reload_addon(self): return - if self._verbose:print("Reloading addon...") addon_utils.modules(refresh=True) bpy.utils.refresh_script_paths() @@ -652,6 +653,10 @@ def check_for_update_async(self, callback=None): self.start_async_check_update(False, callback) def check_for_update_now(self, callback=None): + + self._error = None + self._error_msg = None + if self._verbose: print("Check update pressed, first getting current status") if self._async_checking == True: if self._verbose:print("Skipping async check, already started") @@ -669,6 +674,10 @@ def check_for_update_now(self, callback=None): def check_for_update(self, now=False): if self._verbose:print("Checking for update function") + # clear the errors if any + self._error = None + self._error_msg = None + # avoid running again in if already run once in BG, just return past result # but if force now check, then still do it if self._update_ready != None and now == False: @@ -706,7 +715,6 @@ def check_for_update(self, now=False): if len(self._tags) == 0: if self._verbose:print("No tag found on this repository") self._update_ready = False - self._connection_failed = True return (False, None, None) new_version = self.version_tuple_from_text(self.tag_latest) @@ -754,6 +762,10 @@ def run_update(self, force=False, revert_tag=None, clean=False, callback=None): self.set_tag(revert_tag) self._update_ready = True + # clear the errors if any + self._error = None + self._error_msg = None + if self.verbose:print("Running update") @@ -914,19 +926,16 @@ def async_check_update(self, now, callback=None): self._async_checking = False self._check_thread = None - def end_async_check_update(): - # could return popup if condition met - - return True - def stop_async_check_update(): + def stop_async_check_update(self): if self._check_thread != None: try: - print("need to end the thread here..") + print("Thread will end in normal course.") # however, "There is no direct kill method on a thread object." #self._check_thread.stop() except: pass + self._async_checking = False # ----------------------------------------------------------------------------- diff --git a/MCprep_addon/addon_updater_ops.py b/MCprep_addon/addon_updater_ops.py index 35920b27..5f261166 100644 --- a/MCprep_addon/addon_updater_ops.py +++ b/MCprep_addon/addon_updater_ops.py @@ -169,7 +169,7 @@ def target_version(self, context): @classmethod def poll(cls, context): - return updater.update_ready != None + return updater.update_ready != None and len(updater.tags)>0 def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) @@ -316,6 +316,24 @@ def execute(self, context): updater.ignore_update() return {'FINISHED'} + +class addon_updater_end_background(bpy.types.Operator): + """Stop checking for update in the background""" + bl_label = "End background check" + bl_idname = updater.addon+".end_background_check" + bl_description = "Stop checking for update in the background" + + # @classmethod + # def poll(cls, context): + # if updater.async_checking == True: + # return True + # else: + # return False + + def execute(self, context): + updater.stop_async_check_update() + return {'FINISHED'} + # ----------------------------------------------------------------------------- # Handler related, to create popups # ----------------------------------------------------------------------------- @@ -522,19 +540,40 @@ def update_settings_ui(self, context): row = box.row() col = row.column() movemosue = False - if updater.update_ready == None and updater.async_checking == False: + if updater.error != None: + subcol = col.row(align=True) + subcol.scale_y = 1 + split = subcol.split(align=True) + split.enabled = False + split.scale_y = 2 + split.operator(addon_updater_check_now.bl_idname, + updater.error) + split = subcol.split(align=True) + split.scale_y = 2 + split.operator(addon_updater_check_now.bl_idname, + text = "", icon="FILE_REFRESH") + + elif updater.update_ready == None and updater.async_checking == False: col.scale_y = 2 col.operator(addon_updater_check_now.bl_idname) elif updater.update_ready == None: # async is running - col.scale_y = 2 - col.enabled = False - col.operator(addon_updater_check_now.bl_idname, "Checking for update....") - movemosue = True # tell user to move mouse, trigger re-draw on background check - elif updater.update_ready == True and updater.update_version != updater.current_version: + subcol = col.row(align=True) + subcol.scale_y = 1 + split = subcol.split(align=True) + split.enabled = False + split.scale_y = 2 + split.operator(addon_updater_check_now.bl_idname, + "Checking...") + split = subcol.split(align=True) + split.scale_y = 2 + split.operator(addon_updater_end_background.bl_idname, + text = "", icon="X") + + elif updater.update_ready == True:# and updater.update_version != updater.current_version: col.scale_y = 2 col.operator(addon_updater_update_now.bl_idname, "Update now to "+str(updater.update_version)) - else: + else: # ie that updater.update_ready == False subcol = col.row(align=True) subcol.scale_y = 1 split = subcol.split(align=True) @@ -563,7 +602,9 @@ def update_settings_ui(self, context): row = box.row() lastcheck = updater.json["last_check"] - if movemosue == True: + if updater.error != None and updater.error_msg != None: + row.label(updater.error_msg) + elif movemosue == True: row.label("Move mouse if button doesn't update") elif lastcheck != "" and lastcheck != None: lastcheck = lastcheck[0: lastcheck.index(".") ] @@ -573,7 +614,6 @@ def update_settings_ui(self, context): - # ----------------------------------------------------------------------------- # Register, should be run in the register module itself # ----------------------------------------------------------------------------- @@ -605,7 +645,7 @@ def register(bl_info): # optional, consider turning off for production or allow as an option # This will print out additional debugging info to the console - updater.verbose = True + updater.verbose = False # optional, customize where the addon updater processing subfolder is, # needs to be within the same folder as the addon itself diff --git a/MCprep_addon/conf.py b/MCprep_addon/conf.py index d8aa411a..16b9b0f3 100644 --- a/MCprep_addon/conf.py +++ b/MCprep_addon/conf.py @@ -27,6 +27,7 @@ # ADDON GLOBAL VARIABLES AND INITIAL SETTINGS # ----------------------------------------------------------------------------- +import bpy def init(): @@ -72,7 +73,7 @@ def init(): global json_data # mcprep_data.json json_data = None # later will load addon information etc - global joson_user # mcprep_user.json + global json_user # mcprep_user.json json_user = None # later will load user saved information @@ -122,9 +123,9 @@ def init(): assets_installed = False - # - # Updates and stat tracking - # + # ----------------------------------------------- + # For installing assets + # ----------------------------------------------- global update_ready update_ready = False @@ -134,3 +135,18 @@ def init(): # or force users to decide after 5 uses, ie blocking the panels + # ----------------------------------------------- + # For cross-addon lists + # ----------------------------------------------- + global skin_list + global rig_list + skin_list = [] # each is: [ basename, path ] + rig_list = [] # each is: [ verify this ] + + + +def register(): + pass + +def unregister(): + pass diff --git a/MCprep_addon/materials.py b/MCprep_addon/materials.py index 4070c6ac..e2ae0b5e 100755 --- a/MCprep_addon/materials.py +++ b/MCprep_addon/materials.py @@ -27,6 +27,9 @@ import bpy import os import math +from bpy_extras.io_utils import ImportHelper +import shutil +import urllib.request # addon imports from . import conf @@ -35,14 +38,14 @@ # ----------------------------------------------------------------------------- -# Material utility functions +# Material class functions # ----------------------------------------------------------------------------- class MCPREP_materialChange(bpy.types.Operator): """Fixes materials and textures on selected objects for Minecraft rendering""" - bl_idname = "object.mcprep_mat_change" #"object.mc_mat_change" + bl_idname = "mcprep.mat_change" #"object.mc_mat_change" bl_label = "MCprep Materials" bl_options = {'REGISTER', 'UNDO'} @@ -51,32 +54,49 @@ class MCPREP_materialChange(bpy.types.Operator): description = "Allow appropriate materials to be rendered reflective", default = True ) + + consolidateMaterials = bpy.props.BoolProperty( + name = "Consolidate materials", + description = "Consolidate duplciate materials & textures", + default = True + ) + + # prop: consolidate textures consolidateTextures def getListDataMats(self): - reflective = [ 'glass', 'glass_pane_side','ice','ice_packed','iron_bars','door_iron_top','door_iron_bottom', - 'diamond_block','iron_block','gold_block','emerald_block','iron_trapdoor','glass_*', - 'Iron_Door','Glass_Pane','Glass','Stained_Glass_Pane','Iron_Trapdoor','Block_of_Iron', - 'Block_of_Diamond','Stained_Glass','Block_of_Gold','Block_of_Emerald','Packed_Ice', - 'Ice'] + reflective = [ 'glass', 'glass_pane_side','ice','ice_packed','iron_bars', + 'door_iron_top','door_iron_bottom','diamond_block','iron_block', + 'gold_block','emerald_block','iron_trapdoor','glass_*', + 'Iron_Door','Glass_Pane','Glass','Stained_Glass_Pane', + 'Iron_Trapdoor','Block_of_Iron','Block_of_Diamond', + 'Stained_Glass','Block_of_Gold','Block_of_Emerald', + 'Packed_Ice','Ice'] water = ['water','water_flowing','Stationary_Water'] - # things are transparent by default to be safe, but if something is solid it is much - # better to make it solid (faster render and build times) - solid = ['sand','dirt','dirt_grass_side','dirt_grass_top','dispenser_front','furnace_top', - 'redstone_block','gold_block','stone','iron_ore','coal_ore','wool_*','stained_clay_*', - 'stone_brick','cobblestone','plank_*','log_*','farmland_wet','farmland_dry', - 'cobblestone_mossy','nether_brick','gravel','*_ore','red_sand','dirt_podzol_top', - 'stone_granite_smooth','stone_granite','stone_diorite','stone_diorite_smooth','stone_andesite', - 'stone_andesite_smooth','brick','snow','hardened_clay','sandstone_side','sandstone_side_carved', - 'sandstone_side_smooth','sandstone_top','red_sandstone_top','red_sandstone_normal','bedrock', - 'dirt_mycelium_top','stone_brick_mossy','stone_brick_cracked','stone_brick_circle', - 'stone_slab_side','stone_slab_top','netherrack','soulsand','*_block','endstone', - 'Grass_Block','Dirt','Stone_Slab','Stone','Oak_Wood_Planks','Wooden_Slab','Sand','Carpet', - 'Wool','Stained_Clay','Gravel','Sandstone','*_Fence','Wood','Acacia/Dark_Oak_Wood','Farmland', - 'Brick','Snow','Bedrock','Netherrack','Soul_Sand','End_Stone'] - emit = ['redstone_block','redstone_lamp_on','glowstone','lava','lava_flowing','fire', - 'sea_lantern', - 'Glowstone','Redstone_Lamp_(on)','Stationary_Lava','Fire','Sea_Lantern','Block_of_Redstone'] + # things are transparent by default to be safe, but if something is solid + # it is better to make it solid (faster render and build times) + solid = ['sand','dirt','dirt_grass_side','dirt_grass_top', + 'dispenser_front','furnace_top','redstone_block','gold_block', + 'stone','iron_ore','coal_ore','wool_*','stained_clay_*', + 'stone_brick','cobblestone','plank_*','log_*','farmland_wet', + 'farmland_dry','cobblestone_mossy','nether_brick','gravel', + '*_ore','red_sand','dirt_podzol_top','stone_granite_smooth', + 'stone_granite','stone_diorite','stone_diorite_smooth', + 'stone_andesite','stone_andesite_smooth','brick','snow', + 'hardened_clay','sandstone_side','sandstone_side_carved', + 'sandstone_side_smooth','sandstone_top','red_sandstone_top', + 'red_sandstone_normal','bedrock','dirt_mycelium_top', + 'stone_brick_mossy','stone_brick_cracked','stone_brick_circle', + 'stone_slab_side','stone_slab_top','netherrack','soulsand', + '*_block','endstone','Grass_Block','Dirt','Stone_Slab','Stone', + 'Oak_Wood_Planks','Wooden_Slab','Sand','Carpet','Wool', + 'Stained_Clay','Gravel','Sandstone','*_Fence','Wood', + 'Acacia/Dark_Oak_Wood','Farmland','Brick','Snow','Bedrock', + 'Netherrack','Soul_Sand','End_Stone'] + emit = ['redstone_block','redstone_lamp_on','glowstone','lava', + 'lava_flowing','fire','sea_lantern','Glowstone', + 'Redstone_Lamp_(on)','Stationary_Lava','Fire','Sea_Lantern', + 'Block_of_Redstone'] ######## CHANGE TO MATERIALS LIBRARY # if v and not importedMats:print("Parsing library file for materials") @@ -97,7 +117,6 @@ def getListDataMats(self): # helper function for expanding wildcard naming for generalized materials # maximum 1 wildcard * def checklist(self,matName,alist): - print("WHHHAAATTT",matName,"##",alist) if matName in alist: return True else: @@ -125,18 +144,19 @@ def materialsInternal(self, mat): try: bpy.data.textures[texList[0].name].name = newName except: - if conf.v:print('\twarning: material '+mat.name+' has no texture slot. skipping...') + if conf.v:print('\twarning: material ' + +mat.name+' has no texture slot. skipping...') return # disable all but first slot, ensure first slot enabled mat.use_textures[0] = True for index in range(1,len(texList)): mat.use_textures[index] = False - - #generalizing the name, so that lava.001 material is recognized and given emit + + # strip out the .00# matGen = util.nameGeneralize(mat.name) mat.use_nodes = False - + mat.use_transparent_shadows = True #all materials receive trans mat.specular_intensity = 0 mat.texture_slots[0].texture.use_interpolation = False @@ -146,7 +166,7 @@ def materialsInternal(self, mat): mat.texture_slots[0].diffuse_color_factor = 1 mat.use_textures[1] = False - if not self.checklist(matGen,listData['solid']): #ie alpha is on unless turned off + if not self.checklist(matGen,listData['solid']): # alpha default on bpy.data.textures[newName].use_alpha = True mat.texture_slots[0].use_map_alpha = True mat.use_transparency = True @@ -238,7 +258,6 @@ def materialsCycles(self, mat): nodeGloss.inputs[1].default_value = 0.01 # copy of reflective for now if self.checklist(matGen,listData['solid']): #links.remove(nodeTrans.outputs["BSDF"],nodeMix1.inputs[1]) - ## ^ need to get right format to remove link, need link object, not endpoints nodeMix1.inputs[0].default_value = 0 # no transparency try: if nodeTex.image.source =='SEQUENCE': @@ -261,14 +280,14 @@ def execute(self, context): self.report({'ERROR'}, "No objects selected") return {'CANCELLED'} - # gets the list of materials (without repetition) in the selected object list + # gets the list of materials (without repetition) from selected matList = util.materialsFromObj(objList) if len(objList)==0: self.report({'ERROR'}, "No materials found on selected objects") return {'CANCELLED'} #check if linked material exists - render_engine = bpy.context.scene.render.engine + render_engine = context.scene.render.engine count = 0 for mat in matList: @@ -288,8 +307,484 @@ def execute(self, context): +class MCPREP_combineMaterials(bpy.types.Operator): + bl_idname = "mcprep.combine_materials" + bl_label = "Combine materials" + bl_description = "Consolidate the same materials together" + + # arg to auto-force remove old? versus just keep as 0-users + + def execute(self, context): + + # 2-level structure to hold base name and all + # materials blocks with the same base + nameCat = {} + data = bpy.data.materials + + # get and categorize all materials names + for mat in data: + base = util.nameGeneralize(mat.name) + if base not in nameCat: + nameCat[base] = [mat.name] + elif mat.name not in nameCat[base]: + nameCat[base].append(mat.name) + else: + print("Skipping, already added material") + + # perform the consolidation with one basename set at a time + for base in nameCat: + if len(base)<2: continue + + nameCat[base].sort() # in-place sorting + baseMat = data[ nameCat[base][0] ] + + for matname in nameCat[base][1:]: + + # skip if fake user set + if data[matname].use_fake_user == True: continue + # otherwise, remap + res = util.remap_users(data[matname],baseMat) + if res != 0: + self.report({'ERROR'}, str(res)) + return {'CANCELLED'} + old = data[matname] + if removeold==True and old.users==0: + data.remove( data[matname] ) + + # Final step.. rename to not have .001 if it does + genBase = util.nameGeneralize(baseMat.name) + if baseMat.name != genBase: + if genBase in data and data[genBase].users!=0: + pass + else: + baseMat.name = genBase + else: + baseMat.name = genBase + + return {'FINISHED'} + + + +# class MCPREP_scaleUV(bpy.types.Operator): +# bl_idname = "mcprep.scaleUV" +# bl_label = "Scale all faces in UV editor" +# bl_description = "Scale all faces in the UV editor" + +# def execute(self, context): + +# # WIP +# ob = context.active_object + +# # copied from elsewhere +# uvs = ob.data.uv_layers[0].data +# matchingVertIndex = list(chain.from_iterable(polyIndices)) + +# # example, matching list of uv coord and 3dVert coord: +# uvs_XY = [i.uv for i in Object.data.uv_layers[0].data] +# vertXYZ= [v.co for v in Object.data.vertices] + +# matchingVertIndex = list(chain.from_iterable([p.vertices for p in Object.data.polygons])) +# # and now, the coord to pair with uv coord: +# matchingVertsCoord = [vertsXYZ[i] for i in matchingVertIndex] + +# return {'FINISHED'} + + + +# ----------------------------------------------------------------------------- +# Skin swapping lists +# ----------------------------------------------------------------------------- + +# for asset listing UIList drawing +class MCPREP_skin_UIList(bpy.types.UIList): + def draw_item(self, context, layout, data, set, icon, active_data, active_propname, index): + layout.prop(set, "name", text="", emboss=False) + + +# for asset listing +class ListColl(bpy.types.PropertyGroup): + label = bpy.props.StringProperty() + description = bpy.props.StringProperty() + + +# reload the skins in the directory for UI list +def reloadSkinList(context): + + #addon_prefs = bpy.context.user_preferences.addons[__package__].preferences + + skinfolder = context.scene.mcskin_path + files = [ f for f in os.listdir(skinfolder) if\ + os.path.isfile(os.path.join(skinfolder,f)) ] + + skinlist = [] + for path in files: + if path.split(".")[-1].lower() not in ["png","jpg","jpeg","tiff"]: continue + skinlist.append( (path, "{x} skin".format(x=path)) ) + + # clear lists + context.scene.mcprep_skins_list.clear() + conf.skin_list = [] + + # recreate + for i, (skin, description) in enumerate(skinlist, 1): + print("#",i) + + item = context.scene.mcprep_skins_list.add() + conf.skin_list.append( (skin,os.path.join(skinfolder,skin)) ) + item.label = description + item.description = description + item.name = skin + + +# for UI list path callback +def update_skin_path(self, context): + if conf.vv:print("Updating skin path") + reloadSkinList(context) + + +def getMatsFromSelected(selected): + # pre-expand + obj_list = [] + + for ob in selected: + if ob.type == 'MESH': + obj_list.append(ob) + elif ob.type == 'ARMATURE': + for ch in ob.children: + if ch.type == 'MESH': obj_list.append(ch) + else: + continue + + mat_list = [] + for ob in obj_list: + # get all materials + + for slot in ob.material_slots: + if slot.material not in mat_list:mat_list.append(slot.material) + return mat_list + + +# called for any texture changing +# input a list of material & an already loaded image datablock +def changeTexture(image, materials): + render_engine = bpy.context.scene.render.engine + if (render_engine == 'BLENDER_RENDER'): + if conf.vv:print("Blender internal skin swapping") + status = swapInternal(image, materials) + elif (render_engine == 'CYCLES'): + if conf.vv:print("Cycles skin swapping") + status =swapCycles(image, materials) + return status + + +# scene update to auto load skins on load +def collhack_skins(scene): + bpy.app.handlers.scene_update_pre.remove(collhack_skins) + if conf.vv:print("Reloading skins") + + try: + reloadSkinList(bpy.context) + #bpy.ops.mcprep.reload_skins() + except: + print("Didin't run callback") + pass + + +# input is either UV image itself or filepath +def swapCycles(image, mats): + print("Skin swapping cycles") + #self.report({'ERROR'}, "Work in progress operator") + + +# input is either UV image itself or filepath +def swapInternal(image, mats): + if conf.vv:print("Texture swapping internal") + changed = 0 + for mat in mats: + for sl in mat.texture_slots: + if sl==None or sl.texture == None or sl.texture.type != 'IMAGE': continue + print("Found a good texture slot! orig image: "+str(sl.texture.image.name)) + sl.texture.image = image + changed += 1 + break + if changed == 0: + return False # nothing updated + else: + return True # updated at least one texture (the first) + + +def loadSkinFile(self, context): + + if os.path.isfile(self.filepath)==False: + self.report({'ERROR'}, "No materials found to update") + # special message for library linking? + + image = util.loadTexture(self.filepath) + mats = getMatsFromSelected(context.selected_objects) + if len(mats)==0: + self.report({'ERROR'}, "No materials found to update") + # special message for library linking? + return 1 + + # do th change, will update according to render engine + status = changeTexture(image, mats) + if status == False: + self.report({'ERROR'}, "No image textures found to update") + return 1 + else: + pass + + # and fix eyes if appropriate + if image.size[1]/image.size[0] != 1: + # self.report({'ERROR'}, "") + self.report({'INFO'}, "No image textures found to update") + return 0 + return 0 + + +# ----------------------------------------------------------------------------- +# Skin swapping classes +# ----------------------------------------------------------------------------- + + +class MCPREP_skinSwapper(bpy.types.Operator, ImportHelper): + """Swap the skin of a (character) with another file""" + bl_idname = "mcprep.skin_swapper" + bl_label = "Swap skin" + bl_description = "Swap the skin of a rig with another file" + bl_options = {'REGISTER', 'UNDO'} + + # filename_ext = ".zip" + filter_glob = bpy.props.StringProperty( + default="*", + options={'HIDDEN'}, + ) + fileselectparams = "use_filter_blender" + files = bpy.props.CollectionProperty(type=bpy.types.PropertyGroup) + + def execute(self,context): + + res = loadSkinFile(self, context) + if res!=0: + return {'CANCELLED'} + + return {'FINISHED'} + + + +class MCPREP_applySkin(bpy.types.Operator): + """Apply the active UIlist skin to select characters""" + bl_idname = "mcprep.applyskin" + bl_label = "Apply skin" + bl_description = "Apply the active UV image to selected character materials" + bl_options = {'REGISTER', 'UNDO'} + + filepath = bpy.props.StringProperty( + name="Skin", + description="selected") + + def execute(self,context): + + res = loadSkinFile(self, context) + if res!=0: + return {'CANCELLED'} + + return {'FINISHED'} + + +class MCPREP_applyUsernameSkin(bpy.types.Operator): + """Apply the active UIlist skin to select characters""" + bl_idname = "mcprep.applyusernameskin" + bl_label = "Skin from user" + bl_description = "Download and apply skin from specific username" + bl_options = {'REGISTER', 'UNDO'} + + username = bpy.props.StringProperty( + name="Username", + description="Exact name of user to get texture from", + default="") + + redownload = bpy.props.BoolProperty( + name="Re-download", + description="Re-download if already locally found", + default=False) + + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self) + + + def draw(self, context): + self.layout.label("Enter exact username below") + self.layout.prop(self,"username",text="") + self.layout.label( + "and then press OK; blender may pause briefly to download") + + + def execute(self,context): + if self.username == "": + 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 ] + if self.username.lower() not in skins or self.redownload==True: + self.downloadUser(context) + else: + if conf.v:print("Reusing downloaded skin") + ind = skins.index(self.username.lower()) + bpy.ops.mcprep.applyskin(filepath=paths[ind][1]) + + return {'FINISHED'} + + + def downloadUser(self, context): + + + #http://minotar.net/skin/theduckcow + src_link = "http://minotar.net/skin/" + saveloc = os.path.join(bpy.path.abspath(context.scene.mcskin_path), + self.username.lower()+".png") + + #urllib.request.urlretrieve(src_link+self.username.lower(), saveloc) + try: + urllib.request.urlretrieve(src_link+self.username.lower(), saveloc) + except urllib.error.HTTPError as e: + self.report({"ERROR"},"Could not find username") + return {'CANCELLED'} + except urllib.error.URLError as e: + self.report({"ERROR"},"URL error, check internet connection") + return {'CANCELLED'} + # else: + # self.report({"ERROR"},"Error occurred trying to download skin") + # return {'CANCELLED'} + + bpy.ops.mcprep.applyskin(filepath = saveloc) + bpy.ops.mcprep.reload_skins() + + + +class MCPREP_skinFixEyes(bpy.types.Operator): + """AFix the eyes of a rig to fit a rig""" + bl_idname = "mcprep.fix_skin_eyes" + bl_label = "Fix eyes" + bl_description = "Fix the eyes of a rig to fit a rig" + bl_options = {'REGISTER', 'UNDO'} + + # initial_eye_type: unknown (default), 2x2 square, 1x2 wide. + + def execute(self,context): + + # take the active texture input (based on selection) + print("fix eyes") + self.report({'ERROR'}, "Work in progress operator") + return {'CANCELLED'} + + + return {'FINISHED'} + + + +class MCPREP_addSkin(bpy.types.Operator, ImportHelper): + bl_idname = "mcprep.add_skin" + bl_label = "Add skin" + bl_description = "Add a new skin to the active folder" + bl_options = {'REGISTER', 'UNDO'} + + # filename_ext = ".zip" # needs to be only tinder + filter_glob = bpy.props.StringProperty( + default="*", + options={'HIDDEN'}, + ) + fileselectparams = "use_filter_blender" + files = bpy.props.CollectionProperty(type=bpy.types.PropertyGroup) + + def execute(self,context): + + # filepath editor dialog + # select file, apply change + + new_skin = bpy.path.abspath(self.filepath) + if os.path.isfile(new_skin) == False: + self.report({"ERROR"},"Not a image file path") + return {'CANCELLED'} + + base = bpy.path.basename(new_skin) + + # copy the skin file + shutil.copy2(new_skin, os.path.join(context.scene.mcskin_path,base)) + + # in future, select multiple + bpy.ops.mcprep.reload_skins() + self.report({"INFO"},"Added 1 skin") # more in the future + + return {'FINISHED'} + + +class MCPREP_removeSkin(bpy.types.Operator): + bl_idname = "mcprep.remove_skin" + bl_label = "Remove skin" + bl_description = "Remove a skin from the active folder" + bl_options = {'REGISTER', 'UNDO'} + + index = bpy.props.IntProperty( + default=0) + + def execute(self,context): + + if self.index >= len(conf.skin_list): + self.report({"ERROR"},"Indexing error") + return {'CANCELLED'} + + file = conf.skin_list[self.index][-1] + + if os.path.isfile(file) == False: + self.report({"ERROR"},"Skin not found to delete") + return {'CANCELLED'} + + os.remove(file) + + # 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 + + # in future, select multiple + self.report({"INFO"},"Removed "+bpy.path.basename(file)) + + + return {'FINISHED'} + + + +class MCPREP_reloadSkins(bpy.types.Operator): + bl_idname = "mcprep.reload_skins" + bl_label = "Reload skin" + bl_description = "Reload the skins folder" + + def execute(self, context): + reloadSkinList(context) + return {'FINISHED'} + + + +# ----------------------------------------------------------------------------- +# Above for UI +# Below for register +# ----------------------------------------------------------------------------- + + + def register(): - "register" + bpy.types.Scene.mcprep_skins_list = \ + bpy.props.CollectionProperty(type=ListColl) + bpy.types.Scene.mcprep_skins_list_index = bpy.props.IntProperty(default=0) + + # to auto-load the skins + bpy.app.handlers.scene_update_pre.append(collhack_skins) def unregister(): - "unregister" + del bpy.types.Scene.mcprep_skins_list + del bpy.types.Scene.mcprep_skins_list_index + diff --git a/MCprep_addon/mcprep_ui.py b/MCprep_addon/mcprep_ui.py index 641266c1..5c837495 100755 --- a/MCprep_addon/mcprep_ui.py +++ b/MCprep_addon/mcprep_ui.py @@ -33,6 +33,7 @@ from . import util from . import spawner # remove once UIlist for mobs implemented from . import addon_updater_ops +from . import materials # do this here?? @@ -68,8 +69,8 @@ def execute(self,context): -####### -# Panel for placing in the shift-A add object menu + +# Menu for placing in the shift-A add object menu class mobSpawnerMenu(bpy.types.Menu): bl_label = "Mob Spawner" bl_idname = "mcmob_spawn_menu" @@ -77,26 +78,30 @@ class mobSpawnerMenu(bpy.types.Menu): def draw(self, context): #self.layout.operator(AddBox.bl_idname, icon='MESH_CUBE') # not sure how to make it work for a menu layout = self.layout - rigitems = getRigList() - if conf.v:print("RIG ITEMS length:"+str(len(rigitems))) + rigitems = spawner.getRigList() for n in range(len(rigitems)): # do some kind of check for if no rigs found if False: layout.label(text="No rigs found", icon='ERROR') - layout.operator("object.mcprep_mobspawner", text=rigitems[n][1]).mcmob_type = rigitems[n][0] + layout.operator("mcprep.mobspawner", text=rigitems[n][1]).mcmob_type = rigitems[n][0] -####### -# Panel for all the meshswap objects + +# Menu for all the meshswap objects class meshswapPlaceMenu(bpy.types.Menu): bl_label = "Meshswap Objects" bl_idname = "mcprep_meshswapobjs" def draw(self, context): layout = self.layout - layout.label("Still in development") + rigitems = spawner.getRigList() + for n in range(len(rigitems)): + # do some kind of check for if no rigs found + if False: + layout.label(text="No objects found", icon='ERROR') + layout.operator("mcprep.mcprep_mobspawner", text=rigitems[n][1]).mcmob_type = rigitems[n][0] -####### -# Panel for root level of shift-A > MCprep + +# Menu for root level of shift-A > MCprep class mcprepQuickMenu(bpy.types.Menu): bl_label = "MCprep" bl_idname = "mcprep_objects" @@ -116,7 +121,7 @@ def draw(self, context): -####### + # pop-up declaring some button is WIP still =D class WIP(bpy.types.Menu): bl_label = "wip_warning" @@ -130,7 +135,7 @@ def draw(self, context): -####### + # preferences UI class MCprepPreference(bpy.types.AddonPreferences): bl_idname = __package__ @@ -146,6 +151,11 @@ class MCprepPreference(bpy.types.AddonPreferences): description = "Folder for rigs to spawn in, default for new blender instances", subtype = 'DIR_PATH', default = scriptdir + "/MCprep_resources/default_mcprep/rigs/") + mcskin_path = bpy.props.StringProperty( + name = "mcskin_path", + description = "Folder for skin textures, used in skin swapping", + subtype = 'DIR_PATH', + default = scriptdir + "/MCprep_resources/skins/") mcprep_use_lib = bpy.props.BoolProperty( name = "Link meshswapped groups & materials", description = "Use library linking when meshswapping or material matching", @@ -290,8 +300,10 @@ def draw(self, context): col = split.column(align=True) col.label("MCprep tools") - col.operator("object.mcprep_mat_change", text="Prep Materials", icon='MATERIAL') - col.operator("object.mcprep_meshswap", text="Mesh Swap", icon='LINK_BLEND') + col.operator("mcprep.mat_change", text="Prep Materials", icon='MATERIAL') + if (bpy.app.version[0]==2 and bpy.app.version[1] > 77) or (bpy.app.version[0]>2): + col.operator("mcprep.combine_materials", icon='PACKAGE') + col.operator("mcprep.meshswap", text="Mesh Swap", icon='LINK_BLEND') #the UV's pixels into actual 3D geometry (but same material, or modified to fit) #col.operator("object.solidify_pixels", text="Solidify Pixels", icon='MOD_SOLIDIFY') split = layout.split() @@ -310,13 +322,13 @@ def draw(self, context): col = split.column(align=True) row = col.row(align=True) - if not context.scene.mcprep_showsettings: - row.prop(context.scene,"mcprep_showsettings", text="settings", icon="TRIA_RIGHT") + if not context.scene.mcprep_props.mcprep_showsettings: + row.prop(context.scene.mcprep_props,"mcprep_showsettings", text="settings", icon="TRIA_RIGHT") #row.operator("object.mcprep_preferences",icon="PREFERENCES",text='') row.operator("screen.userpref_show",icon="PREFERENCES",text='') else: - row.prop(context.scene,"mcprep_showsettings", text="settings", icon="TRIA_DOWN") + row.prop(context.scene.mcprep_props,"mcprep_showsettings", text="settings", icon="TRIA_DOWN") row.operator("screen.userpref_show",icon="PREFERENCES",text='') layout = layout.box() @@ -347,11 +359,73 @@ def draw(self, context): "https://github.com/TheDuckCow/MCprep/releases" + ####### # MCprep panel for mob spawning -class MCpanelSpawn(bpy.types.Panel): +class MCpanelSkins(bpy.types.Panel): """MCprep addon panel""" - bl_label = "Mob Spawner" + bl_label = "Skin Swapper" + bl_space_type = 'VIEW_3D' + bl_region_type = 'TOOLS' + bl_context = "objectmode" + bl_category = "MCprep" + + def draw(self, context): + + layout = self.layout + # layout.label("Skin directory") + # split = layout.split() + # col = split.column(align=True) + # row = col.row(align=True) + # row.prop(context.scene,"mcskin_path",text="") + # row.operator("mcprep.reload_skins", icon='LOOP_BACK', text='') + # row = col.row(align=True) + # layout.operator("mcprep.fix_skin_eyes") + + + # set size of UIlist + row = layout.row() + col = row.column() + + is_sortable = len(conf.skin_list) > 1 + rows = 1 + if (is_sortable): + rows = 4 + + # row = col.row() + col.prop(context.scene,"mcskin_path",text="Source") + col.template_list("MCPREP_skin_UIList", "", + context.scene, "mcprep_skins_list", + context.scene, "mcprep_skins_list_index", + rows=rows) + + col = row.column(align=True) + col.operator("mcprep.add_skin", icon='ZOOMIN', text="") + col.operator("mcprep.remove_skin", icon='ZOOMOUT', text="").index = context.scene.mcprep_skins_list_index + col.operator("mcprep.reload_skins", icon='LOOP_BACK', text='') + + + col = layout.column(align=True) + + row = col.row(align=True) + if len (conf.skin_list)>0: + skinname = bpy.path.basename( conf.skin_list[context.scene.mcprep_skins_list_index][0] ) + p = row.operator("mcprep.applyskin","Apply: "+skinname) # context.scene.theory_asset_props_list_id + p.filepath = conf.skin_list[context.scene.mcprep_skins_list_index][1] + else: + # gray out + row.enabled = False + p = row.operator("mcprep.skin_swapper","No skins found") # context.scene.theory_asset_props_list_id + row = col.row(align=True) + row.operator("mcprep.skin_swapper","Skin from file") + row = col.row(align=True) + row.operator("mcprep.applyusernameskin","Skin from username") + + +# MCprep panel for mob spawning +class MCpanelSpawn(bpy.types.Panel): + """MCprep spawning panel""" + bl_label = "Spawner" bl_space_type = 'VIEW_3D' bl_region_type = 'TOOLS' bl_context = "objectmode" @@ -370,7 +444,7 @@ def draw(self, context): row.prop(context.scene,"mcrig_path",text="") row = col.row(align=True) row.operator("object.mc_openrigpath", text="Open folder", icon='FILE_FOLDER') - row.operator("object.mcprep_spawnpathreset", icon='LOAD_FACTORY', text='') + row.operator("mcprep.spawnpathreset", icon='LOAD_FACTORY', text='') row.operator("object.mc_reloadcache", icon='LOOP_BACK', text='') # here for the automated rest of them @@ -405,7 +479,7 @@ def draw(self, context): #credit = tmp[0].split(" - ")[-1].split(".")[0] # looks too messy to have in the button itself, # maybe place in the redo last menu #tx = "{x} (by {y})".format(x=rigitems[n][1],y=credit) - col.operator("object.mcprep_mobspawner", text=rigitems[n][1].title()).mcmob_type = rigitems[n][0] + col.operator("mcprep.mobspawner", text=rigitems[n][1].title()).mcmob_type = rigitems[n][0] #col.operator("object.mcmob_install_menu","Install {x}".format(x=catlabel),icon="PLUS").mob_category = catlabel # the last one if last: #there were uncategorized rigs @@ -429,6 +503,8 @@ def draw(self, context): "https://github.com/TheDuckCow/MCprep/releases" + + ####### # WIP OK button general purpose class dialogue(bpy.types.Operator): @@ -478,6 +554,19 @@ def draw_mcprepadd(self, context): layout.menu(mcprepQuickMenu.bl_idname) +# ----------------------------------------------- +# Addon wide properties (aside from user preferences) +# ----------------------------------------------- +class MCprep_props(bpy.types.PropertyGroup): + + # not available here + #addon_prefs = bpy.context.user_preferences.addons[__package__].preferences + + mcprep_showsettings = bpy.props.BoolProperty( + name = "mcprep_showsettings", + description = "Show extra settings in the MCprep panel", + default = False) + def register(): # start with custom icons @@ -495,39 +584,37 @@ def register(): if conf.v:print("Old verison of blender, no custom icons available") preview_collections["main"] = "" + bpy.types.Scene.mcprep_props = bpy.props.PointerProperty(type=MCprep_props) - bpy.types.Scene.mcprep_showsettings = bpy.props.BoolProperty( - name = "mcprep_showsettings", - description = "Show extra settings in the MCprep panel", - default = False) - - bpy.types.INFO_MT_add.append(draw_mcprepadd) - + # scene settings (later re-attempt to put into props group) addon_prefs = bpy.context.user_preferences.addons[__package__].preferences + bpy.types.Scene.mcrig_path = bpy.props.StringProperty( name = "mcrig_path", description = "Folder for rigs to spawn in, saved with this blend file data", subtype = 'DIR_PATH', default = addon_prefs.mcrig_path) + bpy.types.Scene.mcskin_path = bpy.props.StringProperty( + name = "mcskin_path", + description = "Folder for skin textures, used in skin swapping", + subtype = 'DIR_PATH', + update=materials.update_skin_path, + default = addon_prefs.mcskin_path) + + - #bpy.utils.register_manual_map(ops_manual_map) + bpy.types.INFO_MT_add.append(draw_mcprepadd) - # install() - if conf.v:print("MCprep register complete") def unregister(): bpy.types.INFO_MT_add.remove(draw_mcprepadd) - del bpy.types.Scene.mcprep_showsettings - #global custom_icons - #bpy.utils.previews.remove(custom_icons) - #print("unreg:") - #print(preview_collections) + + del bpy.types.Scene.mcprep_props + if preview_collections["main"] != "": for pcoll in preview_collections.values(): #print("clearing?",pcoll) bpy.utils.previews.remove(pcoll) preview_collections.clear() - #bpy.utils.unregister_manual_map(ops_manual_map) - diff --git a/MCprep_addon/meshswap.py b/MCprep_addon/meshswap.py index 749dc72e..5bc58c34 100755 --- a/MCprep_addon/meshswap.py +++ b/MCprep_addon/meshswap.py @@ -44,7 +44,7 @@ class meshSwap(bpy.types.Operator): """Swap minecraft objects from world imports for custom 3D models in the according meshSwap blend file""" - bl_idname = "object.mcprep_meshswap" + bl_idname = "mcprep.meshswap" bl_label = "MCprep meshSwap" bl_options = {'REGISTER', 'UNDO'} @@ -508,7 +508,7 @@ def execute(self, context): loc = swap.matrix_world*mathutils.Vector(set) #local to global if grouped: # definition for randimization, defined at top! - randGroup = randomizeMeshSawp(swapGen,3) + randGroup = util.randomizeMeshSawp(swapGen,3) # The built in method fails, bpy.ops.object.group_instance_add(...) #UPDATE: I reported the bug, and they fixed it nearly instantly =D diff --git a/MCprep_addon/spawner.py b/MCprep_addon/spawner.py index 60f4bf4c..8061c54f 100755 --- a/MCprep_addon/spawner.py +++ b/MCprep_addon/spawner.py @@ -50,12 +50,13 @@ def getRigList(): #consider passing in rigpath... but pathlist = [] riglist = [] - # check if cache file exists - rigCache = os.path.join(rigpath,"rigcache") - if not os.path.isfile(rigCache): - return updateRigList() # updates and returns the result + + if len(conf.rig_list)==0: # may redraw too many times, perhaps have flag + return updateRigList() else: + return conf.rig_list + # load the cache file f = open(rigCache,'r') ln = f.read() @@ -79,8 +80,8 @@ def getRigList(): #consider passing in rigpath... but rigListExample = [('blend/creeper.blend', '#Creeper', '#Description'), ('blend/steve.blend', '#Steve', '#Description'), ('blend/dawg.blend', '#Dawg', '#Description')] - #print(riglist) - return riglist # for debugging, return rigListExample + + return riglist def updateRigList(): @@ -90,12 +91,6 @@ def updateRigList(): pathlist = [] riglist = [] - # check if cache file exists - rigCache = os.path.join(rigpath,"rigcache") - if os.path.isdir(rigpath) == False: - os.mkdir(rigpath) - # or just print an error? - # iterate through all folders if len(os.listdir(rigpath)) < 1: #self.report({'ERROR'}, "Rig sub-folders not found") @@ -136,13 +131,15 @@ def updateRigList(): # save the file - f = open(rigCache,'w') - f.write("{x}\n".format(x=bpy.context.scene.mcrig_path)) - for tm in riglist: - #print("did it write?") - f.write("{x}\t{y}\t{z}\n".format(x=tm[0],y=tm[1],z=tm[2])) - #print( "{x}\t{y}\t{z}\n".format(x=tm[0],y=tm[1],z=tm[2]) ) - f.close() + + conf.rig_list = riglist + # f = open(rigCache,'w') + # f.write("{x}\n".format(x=bpy.context.scene.mcrig_path)) + # for tm in riglist: + # #print("did it write?") + # f.write("{x}\t{y}\t{z}\n".format(x=tm[0],y=tm[1],z=tm[2])) + # #print( "{x}\t{y}\t{z}\n".format(x=tm[0],y=tm[1],z=tm[2]) ) + # f.close() return riglist @@ -165,7 +162,7 @@ def execute(self,context): class mobSpawner(bpy.types.Operator): """Instantly spawn built-in or custom rigs into a scene""" - bl_idname = "object.mcprep_mobspawner" + bl_idname = "mcprep.mobspawner" bl_label = "Mob Spawner" bl_options = {'REGISTER', 'UNDO'} @@ -450,6 +447,8 @@ def execute(self, context): # if there is a script with this rig, attempt to run it self.attemptScriptLoad(path) + if context.scene.render.engine == 'CYCLES': + bpy.ops.mcprep.mat_change() # if cycles return {'FINISHED'} @@ -570,7 +569,7 @@ def execute(self,context): class spawnPathReset(bpy.types.Operator): """Reset the spawn path to the default specified in the addon preferences panel""" - bl_idname = "object.mcprep_spawnpathreset" + bl_idname = "mcprep.spawnpathreset" bl_label = "Reset spawn path" def execute(self,context): @@ -584,7 +583,7 @@ def execute(self,context): # ----------------------------------------------------------------------------- # Above for class functions/operators -# Below for UI +# Below for UI/register # ----------------------------------------------------------------------------- diff --git a/MCprep_addon/tracking.py b/MCprep_addon/tracking.py index 1c866595..4d837dc9 100644 --- a/MCprep_addon/tracking.py +++ b/MCprep_addon/tracking.py @@ -60,11 +60,9 @@ import os import json -import requests import http.client import platform import threading -from datetime import datetime import bpy @@ -365,7 +363,7 @@ def runInstall(background): location = "/1/track/install.json" payload = json.dumps({ - "timestamp":str(datetime.now()), + "timestamp": {".sv": "timestamp"}, "version":Tracker.version, "blender":"2.77", "status":"None", @@ -410,7 +408,7 @@ def runUsage(background): location = "/1/track/usage.json" payload = json.dumps({ - "timestamp":str(datetime.now()), + "timestamp":{".sv": "timestamp"}, "version":Tracker.version, "blender":"2.77", "platform":platform.system()+":"+platform.release(), diff --git a/MCprep_addon/util.py b/MCprep_addon/util.py index 0eebb5be..33ab2a56 100755 --- a/MCprep_addon/util.py +++ b/MCprep_addon/util.py @@ -53,15 +53,20 @@ # return None def nameGeneralize(name): - nameList = name.split(".") - #check last item in list, to see if numeric type e.g. from .001 - try: - x = int(nameList[-1]) - name = nameList[0] - for a in nameList[1:-1]: name+='.'+a - except: - pass - return name + if duplicatedDatablock(name) == True: + return name[:-4] + else: + return name + # old method + # nameList = name.split(".") + # #check last item in list, to see if numeric type e.g. from .001 + # try: + # x = int(nameList[-1]) + # name = nameList[0] + # for a in nameList[1:-1]: name+='.'+a + # except: + # pass + # return name #### # gets all materials on input list of objects @@ -116,7 +121,7 @@ def bAppendLink(directory,name, toLink): if (toLink): bpy.ops.wm.link(directory=directory, filename=name) else: - bpy.ops.wm.append(directory=directory, filename=name) + bpy.ops.wm.append(directory=directory, filename=name) #, activelayer=True else: # OLD method of importing bpy.ops.wm.link_append(directory=directory, filename=name, link=toLink) @@ -133,3 +138,64 @@ def onEdge(faceLoc): else: return False + +######## +# randomization for model imports, add extra statements for exta cases +def randomizeMeshSawp(swap,variations): + randi='' + if swap == 'torch': + randomized = random.randint(0,variations-1) + #print("## "+str(randomized)) + if randomized != 0: randi = ".{x}".format(x=randomized) + elif swap == 'Torch': + randomized = random.randint(0,variations-1) + #print("## "+str(randomized)) + if randomized != 0: randi = ".{x}".format(x=randomized) + return swap+randi + + +# --------- +# Check if datablock is a duplicate or not, e.g. ending in .00# +def duplicatedDatablock(name): + try: + if name[-4]!=".": return False + int(name[-3:]) + return True + except: + return False + + +# --------- +# Load texture, reusing existing texture if present +def loadTexture(texture): + + # load the image only once + base = bpy.path.basename(texture) + # HERE load the iamge and set + if base in bpy.data.images: + if bpy.path.abspath(bpy.data.images[base].filepath) == bpy.path.abspath(texture): + data_img = bpy.data.images[base] + data_img.reload() + if conf.v:print("Using already loaded texture") + else: + data_img = bpy.data.images.load(texture) + if conf.v:print("Loading new texture image") + else: + data_img = bpy.data.images.load(texture) + if conf.v:print("Loading new texture image") + + return data_img + + +# --------- +# Consistent, general way to remap datablock users +# todo: write equivalent function of user_remap for older blender versions +def remap_users(old, new): + if bpy.app.version[0]>=2 and bpy.app.version[1] >= 78: + #if hasattr(old, "user_remap"): # let it fail + old.user_remap( new ) + return 0 + else: + #raise ValueError("Error: not available prior to blender 2.78") + return "not available prior to blender 2.78" +