def generate_texture_unit(self, key, texture): """ Generates a texture_unit of a pass. key: key of the texture in the material shader (not used, for normal if needed) texture: the material texture """ src_dir = os.path.dirname(bpy.data.filepath) # For target path relative # dst_dir = os.path.dirname(self.target_path) dst_dir = src_dir filename = io_utils.path_reference(texture.image.filepath, src_dir, dst_dir, mode='RELATIVE', library=texture.image.library) # Do not use if target path relative # filename = repr(filepath)[1:-1] _, filename = split(filename) filename = self.change_ext(filename, texture.image) # special case of normal maps if key == "normalmap_texture": with self.w.iword('rtshader_system').embed(): self.w.iword('lighting_stage normal_map').word(filename).nl() return with self.w.iword('texture_unit').embed(): self.w.iword('texture').word(filename).nl() exmode = texture.extension if exmode in TEXTURE_ADDRESS_MODE: self.w.iword('tex_address_mode').word(TEXTURE_ADDRESS_MODE[exmode]).nl() if exmode == 'CLIP': r,g,b = texture.node_image.color_mapping.blend_color self.w.iword('tex_border_colour').round(r).round(g).round(b).nl() x,y = texture.scale[0:2] if x != 1 or y != 1: self.w.iword('scale').round(1.0 / x).round(1.0 / y).nl() if texture.texcoords == 'Reflection': if texture.projection == 'SPHERE': self.w.iline('env_map spherical') elif texture.projection == 'FLAT': self.w.iline('env_map planar') else: logger.warn('Texture: <%s> has a non-UV mapping type (%s) and not picked a proper projection type of: Sphere or Flat' % (texture.name, slot.mapping)) x,y = texture.translation[0:2] if x or y: self.w.iword('scroll').round(x).round(y).nl() if texture.rotation.z: # Radians to degrees self.w.iword('rotate').round(math.degrees(texture.rotation.z), 2).nl() btype = 'modulate' if key == "emission_color_texture": btype = "add" self.w.iword('colour_op').word(btype).nl()
def add_collada_image(self, img): #--- Add image to collada.images if img.name not in self.collada.images: #--- Copy image to a subdir in the export dir if self.copy_images: # Image source path and basename img_sourcepath = bpy.path.abspath(img.filepath) img_basename = bpy.path.basename(img_sourcepath) # Copy image to subdir in export dir export_filename = bpy.path.display_name_from_filepath( self.export_path) subdir = export_filename + "_textures" img_destpath = os.path.join(self.export_dir, subdir, img_basename) copy_set = {(img_sourcepath, img_destpath)} io_utils.path_reference_copy(copy_set) # Relative path to the copied image path = io_utils.path_reference(img_destpath, self.source_dir, self.export_dir, 'RELATIVE') #--- Reference image where it is else: if self.use_relative_path: path_mode = 'RELATIVE' else: path_mode = 'ABSOLUTE' path = io_utils.path_reference(img.filepath, self.source_dir, self.export_dir, path_mode) #--- Create and add CImage object c_img = material.CImage(img.name, path) self.collada.images.append(c_img) #--- Image already added to collada.images else: c_img = self.collada.images[img.name] return c_img
def add_collada_image(self, img): #--- Add image to collada.images if img.name not in self.collada.images: #--- Copy image to a subdir in the export dir if self.copy_images: # Image source path and basename img_sourcepath = bpy.path.abspath(img.filepath) img_basename = bpy.path.basename(img_sourcepath) # Copy image to subdir in export dir export_filename = bpy.path.display_name_from_filepath(self.export_path) #subdir = export_filename + "_textures" subdir = "textures" img_destpath = os.path.join(self.export_dir, subdir, img_basename) copy_set = {(img_sourcepath, img_destpath)} io_utils.path_reference_copy(copy_set) # Relative path to the copied image path = io_utils.path_reference(img_destpath, self.source_dir, self.export_dir, 'RELATIVE') #--- Reference image where it is else: if self.use_relative_path: path_mode = 'RELATIVE' else: path_mode = 'ABSOLUTE' path = io_utils.path_reference(img.filepath, self.source_dir, self.export_dir, path_mode) #--- Create and add CImage object c_img = material.CImage(img.name, path) self.collada.images.append(c_img) #--- Image already added to collada.images else: c_img = self.collada.images[img.name] return c_img
def write_mtl(scene, filepath, path_mode, copy_set, mtl_dict): world = scene.world world_amb = Color((0.8, 0.8, 0.8)) source_dir = os.path.dirname(bpy.data.filepath) dest_dir = os.path.dirname(filepath) with open(filepath, "w", encoding="utf8", newline="\n") as f: fw = f.write fw('# Blender MTL File: %r\n' % (os.path.basename(bpy.data.filepath) or "None")) fw('# Material Count: %i\n' % len(mtl_dict)) mtl_dict_values = list(mtl_dict.values()) mtl_dict_values.sort(key=lambda m: m[0]) # Write material/image combinations we have used. # Using mtl_dict.values() directly gives un-predictable order. for mtl_mat_name, mat in mtl_dict_values: # Get the Blender data for the material and the image. # Having an image named None will make a bug, dont do it :) fw('\nnewmtl %s\n' % mtl_mat_name) # Define a new material: matname_imgname mat_wrap = node_shader_utils.PrincipledBSDFWrapper(mat) if mat else None if mat_wrap: use_mirror = mat_wrap.metallic != 0.0 use_transparency = mat_wrap.alpha != 1.0 # XXX Totally empirical conversion, trying to adapt it # (from 1.0 - 0.0 Principled BSDF range to 0.0 - 900.0 OBJ specular exponent range)... spec = (1.0 - mat_wrap.roughness) * 30 spec *= spec fw('Ns %.6f\n' % spec) # Ambient if use_mirror: fw('Ka %.6f %.6f %.6f\n' % (mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic)) else: fw('Ka %.6f %.6f %.6f\n' % (1.0, 1.0, 1.0)) fw('Kd %.6f %.6f %.6f\n' % mat_wrap.base_color[:3]) # Diffuse # XXX TODO Find a way to handle tint and diffuse color, in a consistent way with import... fw('Ks %.6f %.6f %.6f\n' % (mat_wrap.specular, mat_wrap.specular, mat_wrap.specular)) # Specular # Emission, not in original MTL standard but seems pretty common, see T45766. fw('Ke %.6f %.6f %.6f\n' % mat_wrap.emission_color[:3]) fw('Ni %.6f\n' % mat_wrap.ior) # Refraction index fw('d %.6f\n' % mat_wrap.alpha) # Alpha (obj uses 'd' for dissolve) # See http://en.wikipedia.org/wiki/Wavefront_.obj_file for whole list of values... # Note that mapping is rather fuzzy sometimes, trying to do our best here. if mat_wrap.specular == 0: fw('illum 1\n') # no specular. elif use_mirror: if use_transparency: fw('illum 6\n') # Reflection, Transparency, Ray trace else: fw('illum 3\n') # Reflection and Ray trace elif use_transparency: fw('illum 9\n') # 'Glass' transparency and no Ray trace reflection... fuzzy matching, but... else: fw('illum 2\n') # light normally #### And now, the image textures... image_map = { "map_Kd": "base_color_texture", "map_Ka": None, # ambient... "map_Ks": "specular_texture", "map_Ns": "roughness_texture", "map_d": "alpha_texture", "map_Tr": None, # transmission roughness? "map_Bump": "normalmap_texture", "disp": None, # displacement... "refl": "metallic_texture", "map_Ke": "emission_color_texture", } for key, mat_wrap_key in sorted(image_map.items()): if mat_wrap_key is None: continue tex_wrap = getattr(mat_wrap, mat_wrap_key, None) if tex_wrap is None: continue image = tex_wrap.image if image is None: continue filepath = io_utils.path_reference(image.filepath, source_dir, dest_dir, path_mode, "", copy_set, image.library) options = [] if key == "map_Bump": if mat_wrap.normalmap_strength != 1.0: options.append('-bm %.6f' % mat_wrap.normalmap_strength) if tex_wrap.translation != Vector((0.0, 0.0, 0.0)): options.append('-o %.6f %.6f %.6f' % tex_wrap.translation[:]) if tex_wrap.scale != Vector((1.0, 1.0, 1.0)): options.append('-s %.6f %.6f %.6f' % tex_wrap.scale[:]) if options: fw('%s %s %s\n' % (key, " ".join(options), repr(filepath)[1:-1])) else: fw('%s %s\n' % (key, repr(filepath)[1:-1])) else: # Write a dummy material here? fw('Ns 500\n') fw('Ka 0.8 0.8 0.8\n') fw('Kd 0.8 0.8 0.8\n') fw('Ks 0.8 0.8 0.8\n') fw('d 1\n') # No alpha fw('illum 2\n') # light normally
def export_bm(context, bmx_filepath, prefs_fncg, opts_exportMode, opts_exportTarget): # ============================================ alloc a temp folder utils_tempFolderObj = tempfile.TemporaryDirectory() utils_tempFolder = utils_tempFolderObj.name utils_tempTextureFolder = os.path.join(utils_tempFolder, "Texture") os.makedirs(utils_tempTextureFolder) # ============================================ # find export target. # do not need check them validation in there. # just collect them. if opts_exportMode== "COLLECTION": objectList = opts_exportTarget.objects else: objectList = [opts_exportTarget, ] # try get fncg collection # fncg stands with forced non-component group try: object_fncgCollection = bpy.data.collections[prefs_fncg] except: object_fncgCollection = None # ============================================ export with open(os.path.join(utils_tempFolder, "index.bm"), "wb") as finfo: UTILS_file_io.write_uint32(finfo, UTILS_constants.bmfile_currentVersion) # ====================== export object meshSet = set() meshList = [] meshCount = 0 with open(os.path.join(utils_tempFolder, "object.bm"), "wb") as fobject: for obj in objectList: # only export mesh object if obj.type != 'MESH': continue # clean no mesh object object_blenderMesh = obj.data if object_blenderMesh is None: continue # check component if (object_fncgCollection is not None) and (obj.name in object_fncgCollection.objects): # it should be set as normal object forcely object_isComponent = False else: # check isComponent normally object_isComponent = UTILS_functions.is_component(obj.name) # triangle first and then group if not object_isComponent: if object_blenderMesh not in meshSet: _mesh_triangulate(object_blenderMesh) meshSet.add(object_blenderMesh) meshList.append(object_blenderMesh) object_meshIndex = meshCount meshCount += 1 else: object_meshIndex = meshList.index(object_blenderMesh) else: object_meshIndex = UTILS_functions.get_component_id(obj.name) # get visibility object_isHidden = not obj.visible_get() # try get grouping data object_groupList = UTILS_virtools_prop.get_virtools_group_data(obj) # ======================= # write to files # write finfo first UTILS_file_io.write_string(finfo, obj.name) UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.OBJECT) UTILS_file_io.write_uint64(finfo, fobject.tell()) # write fobject UTILS_file_io.write_bool(fobject, object_isComponent) UTILS_file_io.write_bool(fobject, object_isHidden) UTILS_file_io.write_world_matrix(fobject, obj.matrix_world) UTILS_file_io.write_uint32(fobject, len(object_groupList)) for item in object_groupList: UTILS_file_io.write_string(fobject, item) UTILS_file_io.write_uint32(fobject, object_meshIndex) # ====================== export mesh materialSet = set() materialList = [] with open(os.path.join(utils_tempFolder, "mesh.bm"), "wb") as fmesh: for mesh in meshList: # split normals mesh.calc_normals_split() # write finfo first UTILS_file_io.write_string(finfo, mesh.name) UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.MESH) UTILS_file_io.write_uint64(finfo, fmesh.tell()) # write fmesh # vertices mesh_vecList = mesh.vertices[:] UTILS_file_io.write_uint32(fmesh, len(mesh_vecList)) for vec in mesh_vecList: #swap yz UTILS_file_io.write_3vector(fmesh,vec.co[0],vec.co[2],vec.co[1]) # uv mesh_faceIndexPairs = [(face, index) for index, face in enumerate(mesh.polygons)] UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs) * 3) if mesh.uv_layers.active is not None: uv_layer = mesh.uv_layers.active.data[:] for f, f_index in mesh_faceIndexPairs: # it should be triangle face, otherwise throw a error if (f.loop_total != 3): raise Exception("Not a triangle", f.poly.loop_total) for loop_index in range(f.loop_start, f.loop_start + f.loop_total): uv = uv_layer[loop_index].uv # reverse v UTILS_file_io.write_2vector(fmesh, uv[0], -uv[1]) else: # no uv data. write garbage for i in range(len(mesh_faceIndexPairs) * 3): UTILS_file_io.write_2vector(fmesh, 0.0, 0.0) # normals UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs) * 3) for f, f_index in mesh_faceIndexPairs: # no need to check triangle again for loop_index in range(f.loop_start, f.loop_start + f.loop_total): nml = mesh.loops[loop_index].normal # swap yz UTILS_file_io.write_3vector(fmesh, nml[0], nml[2], nml[1]) # face # get material first mesh_usedBlenderMtl = mesh.materials[:] mesh_noMaterial = len(mesh_usedBlenderMtl) == 0 for mat in mesh_usedBlenderMtl: if mat not in materialSet: materialSet.add(mat) materialList.append(mat) UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs)) mesh_vtIndex = [] mesh_vnIndex = [] mesh_vIndex = [] for f, f_index in mesh_faceIndexPairs: # confirm material use if mesh_noMaterial: mesh_materialIndex = 0 else: mesh_materialIndex = materialList.index(mesh_usedBlenderMtl[f.material_index]) # export face mesh_vtIndex.clear() mesh_vnIndex.clear() mesh_vIndex.clear() counter = 0 for loop_index in range(f.loop_start, f.loop_start + f.loop_total): mesh_vIndex.append(mesh.loops[loop_index].vertex_index) mesh_vnIndex.append(f_index * 3 + counter) mesh_vtIndex.append(f_index * 3 + counter) counter += 1 # reverse vertices sort UTILS_file_io.write_face(fmesh, mesh_vIndex[2], mesh_vtIndex[2], mesh_vnIndex[2], mesh_vIndex[1], mesh_vtIndex[1], mesh_vnIndex[1], mesh_vIndex[0], mesh_vtIndex[0], mesh_vnIndex[0]) # set used material UTILS_file_io.write_bool(fmesh, not mesh_noMaterial) UTILS_file_io.write_uint32(fmesh, mesh_materialIndex) # free splited normals mesh.free_normals_split() # ====================== export material textureSet = set() textureList = [] textureCount = 0 with open(os.path.join(utils_tempFolder, "material.bm"), "wb") as fmaterial: for material in materialList: # write finfo first UTILS_file_io.write_string(finfo, material.name) UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.MATERIAL) UTILS_file_io.write_uint64(finfo, fmaterial.tell()) # try get original written data (material_colAmbient, material_colDiffuse, material_colSpecular, material_colEmissive, material_specularPower, material_alphaTest, material_alphaBlend, material_zBuffer, material_twoSided, material_texture) = UTILS_virtools_prop.get_virtools_material_data(material) # only try get from Principled BSDF when we couldn't get from virtools_material props if material_texture is None: # get node mat_wrap = node_shader_utils.PrincipledBSDFWrapper(material) # check existence of Principled BSDF if mat_wrap: # we trying get texture data from Principled BSDF # because bpy.types.Material.virtools_material now can provide # Virtools material data stablely, so i annotate following code # only keep texture data ''' use_mirror = mat_wrap.metallic != 0.0 if use_mirror: material_colAmbient = _set_value_when_none(material_colAmbient, (mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic)) else: material_colAmbient = _set_value_when_none(material_colAmbient, (1.0, 1.0, 1.0)) material_colDiffuse = _set_value_when_none(material_colDiffuse, (mat_wrap.base_color[0], mat_wrap.base_color[1], mat_wrap.base_color[2])) material_colSpecular = _set_value_when_none(material_colSpecular, (mat_wrap.specular, mat_wrap.specular, mat_wrap.specular)) material_colEmissive = _set_value_when_none(material_colEmissive, mat_wrap.emission_color[:3]) material_specularPower = _set_value_when_none(material_specularPower, 0.0) ''' # confirm texture tex_wrap = getattr(mat_wrap, "base_color_texture", None) if tex_wrap: image = tex_wrap.image if image: material_texture = image # check texture index if material_texture is None: material_useTexture = False material_textureIndex = 0 else: # add into texture list if material_texture not in textureSet: textureSet.add(material_texture) textureList.append(material_texture) textureIndex = textureCount textureCount += 1 else: textureIndex = textureList.index(material_texture) material_useTexture = True material_textureIndex = textureIndex UTILS_file_io.write_color(fmaterial, material_colAmbient) UTILS_file_io.write_color(fmaterial, material_colDiffuse) UTILS_file_io.write_color(fmaterial, material_colSpecular) UTILS_file_io.write_color(fmaterial, material_colEmissive) UTILS_file_io.write_float(fmaterial, material_specularPower) UTILS_file_io.write_bool(fmaterial, material_alphaTest) UTILS_file_io.write_bool(fmaterial, material_alphaBlend) UTILS_file_io.write_bool(fmaterial, material_zBuffer) UTILS_file_io.write_bool(fmaterial, material_twoSided) UTILS_file_io.write_bool(fmaterial, material_useTexture) UTILS_file_io.write_uint32(fmaterial, material_textureIndex) # ====================== export texture texture_blenderFilePath = os.path.dirname(bpy.data.filepath) texture_existedTextureFilepath = set() with open(os.path.join(utils_tempFolder, "texture.bm"), "wb") as ftexture: for texture in textureList: # write finfo first UTILS_file_io.write_string(finfo, texture.name) UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.TEXTURE) UTILS_file_io.write_uint64(finfo, ftexture.tell()) # confirm whether it is internal texture # get absolute texture path texture_filepath = io_utils.path_reference(texture.filepath, texture_blenderFilePath, utils_tempTextureFolder, 'ABSOLUTE', "", None, texture.library) # get file name and write it texture_filename = os.path.basename(texture_filepath) UTILS_file_io.write_string(ftexture, texture_filename) if (_is_external_texture(texture_filename)): # write directly, use Ballance texture UTILS_file_io.write_bool(ftexture, True) else: # copy internal texture, if this file is copied, do not copy it again UTILS_file_io.write_bool(ftexture, False) if texture_filename not in texture_existedTextureFilepath: shutil.copy(texture_filepath, os.path.join(utils_tempTextureFolder, texture_filename)) texture_existedTextureFilepath.add(texture_filename) # ============================================ # save zip and clean up folder UTILS_zip_helper.compress(utils_tempFolder, bmx_filepath) utils_tempFolderObj.cleanup()
def copy_image(image, context): base_src = os.path.dirname(bpy.data.filepath) filepath_full = bpy.path.abspath(image.filepath, library=image.library) image_src = path_reference(filepath_full, base_src, context.base_url, 'COPY', "textures", context.copy_set, image.library) # Stats are written when files have been copied (see Context.finalize) return image_src