123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- from .logger import *
- from .package_level import *
- import bpy
- from base64 import b64encode
- from mathutils import Color
- from os import path, remove
- from shutil import copy
- from sys import exc_info # for writing errors to log file
- # used in Texture constructor, defined in BABYLON.Texture
- CLAMP_ADDRESSMODE = 0
- WRAP_ADDRESSMODE = 1
- MIRROR_ADDRESSMODE = 2
- # used in Texture constructor, defined in BABYLON.Texture
- EXPLICIT_MODE = 0
- SPHERICAL_MODE = 1
- #PLANAR_MODE = 2
- CUBIC_MODE = 3
- #PROJECTION_MODE = 4
- #SKYBOX_MODE = 5
- DEFAULT_MATERIAL_NAMESPACE = 'Same as Filename'
- #===============================================================================
- class MultiMaterial:
- def __init__(self, material_slots, idx, nameSpace):
- self.name = nameSpace + '.' + 'Multimaterial#' + str(idx)
- Logger.log('processing begun of multimaterial: ' + self.name, 2)
- self.material_slots = material_slots
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def to_scene_file(self, file_handler):
- file_handler.write('{')
- write_string(file_handler, 'name', self.name, True)
- write_string(file_handler, 'id', self.name)
- file_handler.write(',"materials":[')
- first = True
- for material in self.material_slots:
- if first != True:
- file_handler.write(',')
- file_handler.write('"' + material.name +'"')
- first = False
- file_handler.write(']')
- file_handler.write('}')
- #===============================================================================
- class Texture:
- def __init__(self, slot, level, textureOrImage, mesh, exporter):
- wasBaked = not hasattr(textureOrImage, 'uv_layer')
- if wasBaked:
- image = textureOrImage
- texture = None
- repeat = False
- self.hasAlpha = False
- self.coordinatesIndex = 0
- else:
- texture = textureOrImage
- image = texture.texture.image
- repeat = texture.texture.extension == 'REPEAT'
- self.hasAlpha = texture.texture.use_alpha
- usingMap = texture.uv_layer
- if len(usingMap) == 0:
- usingMap = mesh.data.uv_textures[0].name
- Logger.log('Image texture found, type: ' + slot + ', mapped using: "' + usingMap + '"', 4)
- if mesh.data.uv_textures[0].name == usingMap:
- self.coordinatesIndex = 0
- elif mesh.data.uv_textures[1].name == usingMap:
- self.coordinatesIndex = 1
- else:
- Logger.warn('Texture is not mapped as UV or UV2, assigned 1', 5)
- self.coordinatesIndex = 0
- # always write the file out, since base64 encoding is easiest from a file
- try:
- imageFilepath = path.normpath(bpy.path.abspath(image.filepath))
- basename = path.basename(imageFilepath)
- internalImage = image.packed_file or wasBaked
- # when coming from either a packed image or a baked image, then save_render
- if internalImage:
- if exporter.scene.inlineTextures:
- textureFile = path.join(exporter.textureDir, basename + "temp")
- else:
- textureFile = path.join(exporter.textureDir, basename)
- image.save_render(textureFile)
- # when backed by an actual file, copy to target dir, unless inlining
- else:
- textureFile = bpy.path.abspath(image.filepath)
- if not exporter.scene.inlineTextures:
- copy(textureFile, exporter.textureDir)
- except:
- ex = exc_info()
- Logger.warn('Error encountered processing image file: ' + ', Error: '+ str(ex[1]))
- if exporter.scene.inlineTextures:
- # base64 is easiest from a file, so sometimes a temp file was made above; need to delete those
- with open(textureFile, "rb") as image_file:
- asString = b64encode(image_file.read()).decode()
- self.encoded_URI = 'data:image/' + image.file_format + ';base64,' + asString
- if internalImage:
- remove(textureFile)
- # capture texture attributes
- self.slot = slot
- self.name = basename
- self.level = level
- if (texture and texture.mapping == 'CUBE'):
- self.coordinatesMode = CUBIC_MODE
- if (texture and texture.mapping == 'SPHERE'):
- self.coordinatesMode = SPHERICAL_MODE
- else:
- self.coordinatesMode = EXPLICIT_MODE
- self.uOffset = texture.offset.x if texture else 0.0
- self.vOffset = texture.offset.y if texture else 0.0
- self.uScale = texture.scale.x if texture else 1.0
- self.vScale = texture.scale.y if texture else 1.0
- self.uAng = 0
- self.vAng = 0
- self.wAng = 0
- if (repeat):
- if (texture.texture.use_mirror_x):
- self.wrapU = MIRROR_ADDRESSMODE
- else:
- self.wrapU = WRAP_ADDRESSMODE
- if (texture.texture.use_mirror_y):
- self.wrapV = MIRROR_ADDRESSMODE
- else:
- self.wrapV = WRAP_ADDRESSMODE
- else:
- self.wrapU = CLAMP_ADDRESSMODE
- self.wrapV = CLAMP_ADDRESSMODE
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def to_scene_file(self, file_handler):
- file_handler.write(', \n"' + self.slot + '":{')
- write_string(file_handler, 'name', self.name, True)
- write_float(file_handler, 'level', self.level)
- write_float(file_handler, 'hasAlpha', self.hasAlpha)
- write_int(file_handler, 'coordinatesMode', self.coordinatesMode)
- write_float(file_handler, 'uOffset', self.uOffset)
- write_float(file_handler, 'vOffset', self.vOffset)
- write_float(file_handler, 'uScale', self.uScale)
- write_float(file_handler, 'vScale', self.vScale)
- write_float(file_handler, 'uAng', self.uAng)
- write_float(file_handler, 'vAng', self.vAng)
- write_float(file_handler, 'wAng', self.wAng)
- write_int(file_handler, 'wrapU', self.wrapU)
- write_int(file_handler, 'wrapV', self.wrapV)
- write_int(file_handler, 'coordinatesIndex', self.coordinatesIndex)
- if hasattr(self,'encoded_URI'):
- write_string(file_handler, 'base64String', self.encoded_URI)
- file_handler.write('}')
- #===============================================================================
- # need to evaluate the need to bake a mesh before even starting; class also stores specific types of bakes
- class BakingRecipe:
- def __init__(self, mesh):
- # transfer from Mesh custom properties
- self.bakeSize = mesh.data.bakeSize
- self.bakeQuality = mesh.data.bakeQuality # for lossy compression formats
- self.forceBaking = mesh.data.forceBaking # in mesh, but not currently exposed
- self.usePNG = mesh.data.usePNG # in mesh, but not currently exposed
- # initialize all members
- self.needsBaking = self.forceBaking
- self.diffuseBaking = self.forceBaking
- self.ambientBaking = False
- self.opacityBaking = False
- self.reflectionBaking = False
- self.emissiveBaking = False
- self.bumpBaking = False
- self.specularBaking = False
- # need to make sure a single render
- self.cyclesRender = False
- blenderRender = False
- # accumulators set by Blender Game
- self.backFaceCulling = True # used only when baking
- self.isBillboard = len(mesh.material_slots) == 1 and mesh.material_slots[0].material.game_settings.face_orientation == 'BILLBOARD'
- # Cycles specific, need to get the node trees of each material
- self.nodeTrees = []
- for material_slot in mesh.material_slots:
- # a material slot is not a reference to an actual material; need to look up
- material = material_slot.material
- self.backFaceCulling &= material.game_settings.use_backface_culling
- # testing for Cycles renderer has to be different
- if material.use_nodes == True:
- self.needsBaking = True
- self.cyclesRender = True
- self.nodeTrees.append(material.node_tree)
- for node in material.node_tree.nodes:
- id = node.bl_idname
- if id == 'ShaderNodeBsdfDiffuse':
- self.diffuseBaking = True
- if id == 'ShaderNodeAmbientOcclusion':
- self.ambientBaking = True
- # there is no opacity baking for Cycles AFAIK
- if id == '':
- self.opacityBaking = True
- if id == 'ShaderNodeEmission':
- self.emissiveBaking = True
- if id == 'ShaderNodeNormal' or id == 'ShaderNodeNormalMap':
- self.bumpBaking = True
- if id == '':
- self.specularBaking = True
- else:
- blenderRender = True
- nDiffuseImages = 0
- nReflectionImages = 0
- nAmbientImages = 0
- nOpacityImages = 0
- nEmissiveImages = 0
- nBumpImages = 0
- nSpecularImages = 0
- textures = [mtex for mtex in material.texture_slots if mtex and mtex.texture]
- for mtex in textures:
- # ignore empty slots
- if mtex.texture.type == 'NONE':
- continue
- # for images, just need to make sure there is only 1 per type
- if mtex.texture.type == 'IMAGE' and not self.forceBaking:
- if mtex.use_map_diffuse or mtex.use_map_color_diffuse:
- if mtex.texture_coords == 'REFLECTION':
- nReflectionImages += 1
- else:
- nDiffuseImages += 1
- if mtex.use_map_ambient:
- nAmbientImages += 1
- if mtex.use_map_alpha:
- nOpacityImages += 1
- if mtex.use_map_emit:
- nEmissiveImages += 1
- if mtex.use_map_normal:
- nBumpImages += 1
- if mtex.use_map_color_spec:
- nSpecularImages += 1
- else:
- self.needsBaking = True
- if mtex.use_map_diffuse or mtex.use_map_color_diffuse:
- if mtex.texture_coords == 'REFLECTION':
- self.reflectionBaking = True
- else:
- self.diffuseBaking = True
- if mtex.use_map_ambient:
- self.ambientBaking = True
- if mtex.use_map_alpha and material.alpha > 0:
- self.opacityBaking = True
- if mtex.use_map_emit:
- self.emissiveBaking = True
- if mtex.use_map_normal:
- self.bumpBaking = True
- if mtex.use_map_color_spec:
- self.specularBaking = True
- # 2nd pass 2 check for multiples of a given image type
- if nDiffuseImages > 1:
- self.needsBaking = self.diffuseBaking = True
- if nReflectionImages > 1:
- self.needsBaking = self.nReflectionImages = True
- if nAmbientImages > 1:
- self.needsBaking = self.ambientBaking = True
- if nOpacityImages > 1:
- self.needsBaking = self.opacityBaking = True
- if nEmissiveImages > 1:
- self.needsBaking = self.emissiveBaking = True
- if nBumpImages > 1:
- self.needsBaking = self.bumpBaking = True
- if nSpecularImages > 1:
- self.needsBaking = self.specularBaking = True
- self.multipleRenders = blenderRender and self.cyclesRender
- # check for really old .blend file, eg. 2.49, to ensure that everything requires exists
- if self.needsBaking and bpy.data.screens.find('UV Editing') == -1:
- Logger.warn('Contains material requiring baking, but resources not available. Probably .blend very old', 2)
- self.needsBaking = False
- #===============================================================================
- # Not intended to be instanced directly
- class Material:
- def __init__(self, checkReadyOnlyOnce, maxSimultaneousLights):
- self.checkReadyOnlyOnce = checkReadyOnlyOnce
- self.maxSimultaneousLights = maxSimultaneousLights
- # first pass of textures, either appending image type or recording types of bakes to do
- self.textures = []
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def to_scene_file(self, file_handler):
- file_handler.write('{')
- write_string(file_handler, 'name', self.name, True)
- write_string(file_handler, 'id', self.name)
- write_color(file_handler, 'ambient', self.ambient)
- write_color(file_handler, 'diffuse', self.diffuse)
- write_color(file_handler, 'specular', self.specular)
- write_color(file_handler, 'emissive', self.emissive)
- write_float(file_handler, 'specularPower', self.specularPower)
- write_float(file_handler, 'alpha', self.alpha)
- write_bool(file_handler, 'backFaceCulling', self.backFaceCulling)
- write_bool(file_handler, 'checkReadyOnlyOnce', self.checkReadyOnlyOnce)
- write_int(file_handler, 'maxSimultaneousLights', self.maxSimultaneousLights)
- for texSlot in self.textures:
- texSlot.to_scene_file(file_handler)
- file_handler.write('}')
- #===============================================================================
- class StdMaterial(Material):
- def __init__(self, material_slot, exporter, mesh):
- super().__init__(mesh.data.checkReadyOnlyOnce, mesh.data.maxSimultaneousLights)
- nameSpace = exporter.nameSpace if mesh.data.materialNameSpace == DEFAULT_MATERIAL_NAMESPACE or len(mesh.data.materialNameSpace) == 0 else mesh.data.materialNameSpace
- self.name = nameSpace + '.' + material_slot.name
- Logger.log('processing begun of Standard material: ' + material_slot.name, 2)
- # a material slot is not a reference to an actual material; need to look up
- material = material_slot.material
- self.ambient = material.ambient * material.diffuse_color
- self.diffuse = material.diffuse_intensity * material.diffuse_color
- self.specular = material.specular_intensity * material.specular_color
- self.emissive = material.emit * material.diffuse_color
- self.specularPower = material.specular_hardness
- self.alpha = material.alpha
- self.backFaceCulling = material.game_settings.use_backface_culling
- textures = [mtex for mtex in material.texture_slots if mtex and mtex.texture]
- for mtex in textures:
- # test should be un-neccessary, since should be a BakedMaterial; just for completeness
- if (mtex.texture.type != 'IMAGE'):
- continue
- elif not mtex.texture.image:
- Logger.warn('Material has un-assigned image texture: "' + mtex.name + '" ignored', 3)
- continue
- elif len(mesh.data.uv_textures) == 0:
- Logger.warn('Mesh has no UV maps, material: "' + mtex.name + '" ignored', 3)
- continue
- if mtex.use_map_diffuse or mtex.use_map_color_diffuse:
- if mtex.texture_coords == 'REFLECTION':
- Logger.log('Reflection texture found "' + mtex.name + '"', 3)
- self.textures.append(Texture('reflectionTexture', mtex.diffuse_color_factor, mtex, mesh, exporter))
- else:
- Logger.log('Diffuse texture found "' + mtex.name + '"', 3)
- self.textures.append(Texture('diffuseTexture', mtex.diffuse_color_factor, mtex, mesh, exporter))
- if mtex.use_map_ambient:
- Logger.log('Ambient texture found "' + mtex.name + '"', 3)
- self.textures.append(Texture('ambientTexture', mtex.ambient_factor, mtex, mesh, exporter))
- if mtex.use_map_alpha:
- if self.alpha > 0:
- Logger.log('Opacity texture found "' + mtex.name + '"', 3)
- self.textures.append(Texture('opacityTexture', mtex.alpha_factor, mtex, mesh, exporter))
- else:
- Logger.warn('Opacity non-std way to indicate opacity, use material alpha to also use Opacity texture', 4)
- self.alpha = 1
- if mtex.use_map_emit:
- Logger.log('Emissive texture found "' + mtex.name + '"', 3)
- self.textures.append(Texture('emissiveTexture', mtex.emit_factor, mtex, mesh, exporter))
- if mtex.use_map_normal:
- Logger.log('Bump texture found "' + mtex.name + '"', 3)
- self.textures.append(Texture('bumpTexture', 1.0 / mtex.normal_factor, mtex, mesh, exporter))
- if mtex.use_map_color_spec:
- Logger.log('Specular texture found "' + mtex.name + '"', 3)
- self.textures.append(Texture('specularTexture', mtex.specular_color_factor, mtex, mesh, exporter))
- #===============================================================================
- class BakedMaterial(Material):
- def __init__(self, exporter, mesh, recipe):
- super().__init__(mesh.data.checkReadyOnlyOnce, mesh.data.maxSimultaneousLights)
- nameSpace = exporter.nameSpace if mesh.data.materialNameSpace == DEFAULT_MATERIAL_NAMESPACE or len(mesh.data.materialNameSpace) == 0 else mesh.data.materialNameSpace
- self.name = nameSpace + '.' + mesh.name
- Logger.log('processing begun of baked material: ' + mesh.name, 2)
- # changes to cycles & smart_project occurred in 2.77; need to know what we are running
- bVersion = blenderMajorMinorVersion()
- # any baking already took in the values. Do not want to apply them again, but want shadows to show.
- # These are the default values from StandardMaterials
- self.ambient = Color((0, 0, 0))
- self.diffuse = Color((0.8, 0.8, 0.8)) # needed for shadows, but not change anything else
- self.specular = Color((1, 1, 1))
- self.emissive = Color((0, 0, 0))
- self.specularPower = 64
- self.alpha = 1.0
- self.backFaceCulling = recipe.backFaceCulling
- # texture is baked from selected mesh(es), need to insure this mesh is only one selected
- bpy.ops.object.select_all(action='DESELECT')
- mesh.select = True
- # store setting to restore
- engine = exporter.scene.render.engine
- # mode_set's only work when there is an active object
- exporter.scene.objects.active = mesh
- # UV unwrap operates on mesh in only edit mode, procedurals can also give error of 'no images to be found' when not done
- # select all verticies of mesh, since smart_project works only with selected verticies
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.mesh.select_all(action='SELECT')
- # you need UV on a mesh in order to bake image. This is not reqd for procedural textures, so may not exist
- # need to look if it might already be created, if so use the first one
- uv = mesh.data.uv_textures[0] if len(mesh.data.uv_textures) > 0 else None
- if uv == None or recipe.forceBaking:
- mesh.data.uv_textures.new('BakingUV')
- uv = mesh.data.uv_textures['BakingUV']
- uv.active = True
- uv.active_render = not recipe.forceBaking # want the other uv's for the source when combining
- if bVersion <= 2.76:
- bpy.ops.uv.smart_project(angle_limit = 66.0, island_margin = 0.0, user_area_weight = 1.0, use_aspect = True)
- else:
- bpy.ops.uv.smart_project(angle_limit = 66.0, island_margin = 0.0, user_area_weight = 1.0, use_aspect = True, stretch_to_bounds = True)
- # syntax for using unwrap enstead of smart project
- # bpy.ops.uv.unwrap(margin = 1.0) # defaulting on all
- uvName = 'BakingUV' # issues with cycles when not done this way
- else:
- uvName = uv.name
- format = 'PNG' if recipe.usePNG else 'JPEG'
- # create a temporary image & link it to the UV/Image Editor so bake_image works
- image = bpy.data.images.new(name = mesh.name + '_BJS_BAKE', width = recipe.bakeSize, height = recipe.bakeSize, alpha = recipe.usePNG, float_buffer = False)
- image.file_format = format
- image.mapping = 'UV' # default value
- image_settings = exporter.scene.render.image_settings
- image_settings.file_format = format
- image_settings.color_mode = 'RGBA' if recipe.usePNG else 'RGB'
- image_settings.quality = recipe.bakeQuality # for lossy compression formats
- image_settings.compression = recipe.bakeQuality # Amount of time to determine best compression: 0 = no compression with fast file output, 100 = maximum lossless compression with slow file output
- # now go thru all the textures that need to be baked
- if recipe.diffuseBaking:
- cycles_type = 'DIFFUSE_COLOR' if bVersion <= 2.76 else 'DIFFUSE'
- self.bake('diffuseTexture', cycles_type, 'TEXTURE', image, mesh, uvName, exporter, recipe)
- if recipe.ambientBaking:
- self.bake('ambientTexture', 'AO', 'AO', image, mesh, uvName, exporter, recipe)
- if recipe.opacityBaking: # no eqivalent found for cycles
- self.bake('opacityTexture', None, 'ALPHA', image, mesh, uvName, exporter, recipe)
- if recipe.reflectionBaking: # no eqivalent found for cycles
- self.bake('reflectionTexture', None, 'MIRROR_COLOR', image, mesh, uvName, exporter, recipe)
- if recipe.emissiveBaking:
- self.bake('emissiveTexture', 'EMIT', 'EMIT', image, mesh, uvName, exporter, recipe)
- if recipe.bumpBaking:
- self.bake('bumpTexture', 'NORMAL', 'NORMALS', image, mesh, uvName, exporter, recipe)
- if recipe.specularBaking:
- cycles_type = 'SPECULAR' if bVersion <= 2.76 else 'GLOSSY'
- self.bake('specularTexture', cycles_type, 'SPEC_COLOR', image, mesh, uvName, exporter, recipe)
- # Toggle vertex selection & mode, if setting changed their value
- bpy.ops.mesh.select_all(action='TOGGLE') # still in edit mode toggle select back to previous
- bpy.ops.object.mode_set(toggle=True) # change back to Object
- bpy.ops.object.select_all(action='TOGGLE') # change scene selection back, not seeming to work
- exporter.scene.render.engine = engine
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def bake(self, bjs_type, cycles_type, internal_type, image, mesh, uvName, exporter, recipe):
- extension = '.png' if recipe.usePNG else '.jpg'
- if recipe.cyclesRender:
- if cycles_type is None:
- return
- self.bakeCycles(cycles_type, image, uvName, recipe.nodeTrees, extension)
- else:
- self.bakeInternal(internal_type, image, uvName, extension)
- self.textures.append(Texture(bjs_type, 1.0, image, mesh, exporter))
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def bakeInternal(self, bake_type, image, uvName, extension):
- Logger.log('Internal baking texture, type: ' + bake_type + ', mapped using: ' + uvName, 3)
- # need to use the legal name, since this will become the file name, chars like ':' not legal
- legalName = legal_js_identifier(self.name)
- image.filepath = legalName + '_' + bake_type + extension
- scene = bpy.context.scene
- scene.render.engine = 'BLENDER_RENDER'
- scene.render.bake_type = bake_type
- # assign the image to the UV Editor, which does not have to shown
- bpy.data.screens['UV Editing'].areas[1].spaces[0].image = image
- renderer = scene.render
- renderer.use_bake_selected_to_active = False
- renderer.use_bake_to_vertex_color = False
- renderer.use_bake_clear = False
- renderer.bake_quad_split = 'AUTO'
- renderer.bake_margin = 5
- renderer.use_file_extension = True
- renderer.use_bake_normalize = True
- renderer.use_bake_antialiasing = True
- bpy.ops.object.bake_image()
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- def bakeCycles(self, bake_type, image, uvName, nodeTrees, extension):
- Logger.log('Cycles baking texture, type: ' + bake_type + ', mapped using: ' + uvName, 3)
- legalName = legal_js_identifier(self.name)
- image.filepath = legalName + '_' + bake_type + extension
- scene = bpy.context.scene
- scene.render.engine = 'CYCLES'
- # create an unlinked temporary node to bake to for each material
- for tree in nodeTrees:
- bakeNode = tree.nodes.new(type='ShaderNodeTexImage')
- bakeNode.image = image
- bakeNode.select = True
- tree.nodes.active = bakeNode
- bpy.ops.object.bake(type = bake_type, use_clear = True, margin = 5, use_selected_to_active = False)
- for tree in nodeTrees:
- tree.nodes.remove(tree.nodes.active)
- # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- @staticmethod
- def meshBakingClean(mesh):
- for uvMap in mesh.data.uv_textures:
- if uvMap.name == 'BakingUV':
- mesh.data.uv_textures.remove(uvMap)
- break
- # remove an image if it was baked
- for image in bpy.data.images:
- if image.name == mesh.name + '_BJS_BAKE':
- image.user_clear() # cannot remove image unless 0 references
- bpy.data.images.remove(image)
- break
|