def __init__(self): global optionSubmaterials global matList global numMats self.material_list = [] # Get all of the materials used by non-collision object meshes for object in bpy.context.selected_objects: if collisionObject(object) == True: continue elif object.type != 'MESH': continue else: print(object.name + ': Constructing Materials') for slot in object.material_slots: # if the material is not in the material_list, add it if self.material_list.count(slot.material) == 0: self.material_list.append(slot.material) mat_wrap = node_shader_utils.PrincipledBSDFWrapper( slot.material) if slot.material else None # image = mat_wrap.base_color_texture.image matList.append(slot.material.name) self.material_count = len(self.material_list) numMats = self.material_count # Raise an error if there are no materials found if self.material_count == 0: raise Error('Mesh must have at least one applied material') else: if (optionSubmaterials): self.dump = cSubMaterials(self.material_list) else: self.dump = cMultiMaterials(self.material_list)
def test_shader_material_w3x_rgb_colors_roundtrip(self): mesh = get_mesh(shader_mats=True) mesh.shader_materials = [ get_shader_material(w3x=True, rgb_colors=True) ] for source in mesh.shader_materials: (material, _) = create_material_from_shader_material(self, mesh.name(), source) principled = node_shader_utils.PrincipledBSDFWrapper( material, is_readonly=True) actual = retrieve_shader_material(self, material, principled, w3x=True) for prop in source.properties: if prop.name in [ 'ColorAmbient', 'ColorEmissive', 'ColorDiffuse', 'ColorSpecular' ]: prop.type = 5 prop.value = Vector( (prop.value[0], prop.value[1], prop.value[2], 1.0)) compare_shader_materials(self, source, actual)
def image_get(mat): from bpy_extras import node_shader_utils if mat.use_nodes: mat_wrap = node_shader_utils.PrincipledBSDFWrapper(mat) base_color_tex = mat_wrap.base_color_texture if base_color_tex and base_color_tex.image: return base_color_tex.image
def create_material_from_vertex_material(name, vert_mat): name = name + "." + vert_mat.vm_name if name in bpy.data.materials: material = bpy.data.materials[name] principled = node_shader_utils.PrincipledBSDFWrapper(material, is_readonly=False) return material, principled material = bpy.data.materials.new(name) material.material_type = 'VERTEX_MATERIAL' material.use_nodes = True material.blend_method = 'BLEND' material.show_transparent_back = False attributes = {'DEFAULT'} attribs = vert_mat.vm_info.attributes if attribs & USE_DEPTH_CUE: attributes.add('USE_DEPTH_CUE') if attribs & ARGB_EMISSIVE_ONLY: attributes.add('ARGB_EMISSIVE_ONLY') if attribs & COPY_SPECULAR_TO_DIFFUSE: attributes.add('COPY_SPECULAR_TO_DIFFUSE') if attribs & DEPTH_CUE_TO_ALPHA: attributes.add('DEPTH_CUE_TO_ALPHA') principled = node_shader_utils.PrincipledBSDFWrapper(material, is_readonly=False) principled.base_color = vert_mat.vm_info.diffuse.to_vector_rgb() principled.alpha = vert_mat.vm_info.opacity principled.specular = vert_mat.vm_info.shininess principled.emission_color = vert_mat.vm_info.emissive.to_vector_rgb() material.attributes = attributes material.specular = vert_mat.vm_info.specular.to_vector_rgb() material.ambient = vert_mat.vm_info.ambient.to_vector_rgba() material.translucency = vert_mat.vm_info.translucency material.stage0_mapping = '0x%08X' % (attribs & STAGE0_MAPPING_MASK) material.stage1_mapping = '0x%08X' % (attribs & STAGE1_MAPPING_MASK) material.vm_args_0 = vert_mat.vm_args_0.replace('\r\n', ', ') material.vm_args_1 = vert_mat.vm_args_1.replace('\r\n', ', ') return material, principled
def get_plane_color(obj): """Get the object's emission and base color, or 0.5 gray if no color is found.""" if obj.active_material is None: color = (0.5, ) * 3 elif obj.active_material: from bpy_extras import node_shader_utils wrapper = node_shader_utils.PrincipledBSDFWrapper(obj.active_material) color = Color(wrapper.base_color[:3]) + wrapper.emission_color return str(list(color))
def test_vertex_material_no_attributes_roundtrip(self): mesh = get_mesh() for source in mesh.vert_materials: source.vm_info.attributes = 0 (material, _) = create_material_from_vertex_material(mesh.name(), source) principled = node_shader_utils.PrincipledBSDFWrapper( material, is_readonly=True) actual = retrieve_vertex_material(material, principled) compare_vertex_materials(self, source, actual)
def test_vertex_material_vm_args_with_spaces(self): mesh = get_mesh() for source in mesh.vert_materials: (material, _) = create_material_from_vertex_material(mesh.name(), source) material.vm_args_0 = ' UPerSec = -2.0 , VPerSec = 0.0 ,UScale = 1.0, VScale = 1.0 ' material.vm_args_0 = ' UPerSec = -2.0 , VPerSec = 0.0 ,UScale = 1.0, VScale = 1.0 ' principled = node_shader_utils.PrincipledBSDFWrapper( material, is_readonly=True) actual = retrieve_vertex_material(material, principled) compare_vertex_materials(self, source, actual)
def test_vertex_material_roundtrip(self): mesh = get_mesh() copyfile( up(up(self.relpath())) + '/testfiles/texture.dds', self.outpath() + 'texture.dds') for source in mesh.vert_materials: (material, _) = create_material_from_vertex_material(mesh.name(), source) principled = node_shader_utils.PrincipledBSDFWrapper( material, is_readonly=True) actual = retrieve_vertex_material(material, principled) compare_vertex_materials(self, source, actual)
def test_shader_material_w3x_two_tex_roundtrip(self): mesh = get_mesh(shader_mats=True) mesh.shader_materials = [get_shader_material(w3x=True, two_tex=True)] for source in mesh.shader_materials: (material, _) = create_material_from_shader_material(self, mesh.name(), source) principled = node_shader_utils.PrincipledBSDFWrapper( material, is_readonly=True) actual = retrieve_shader_material(self, material, principled, w3x=True) compare_shader_materials(self, source, actual)
def retrieve_principled_bsdf(material): result = PrincipledBSDF() principled = node_shader_utils.PrincipledBSDFWrapper(material, is_readonly=True) result.base_color = principled.base_color result.alpha = principled.alpha diffuse_tex = principled.base_color_texture if diffuse_tex and diffuse_tex.image: result.diffuse_tex = diffuse_tex.image.name normalmap_tex = principled.normalmap_texture if normalmap_tex and normalmap_tex.image: result.normalmap_tex = normalmap_tex.image.name result.bump_scale = principled.normalmap_strength return result
def test_duplicate_shader_material_roundtrip(self): mesh = get_mesh(shader_mats=True) mesh.shader_materials = [get_shader_material(), get_shader_material()] materials = [] for mat in mesh.shader_materials: (material, _) = create_material_from_shader_material(self, 'meshName', mat) materials.append(material) self.assertEqual(1, len(bpy.data.materials)) self.assertTrue('meshName.NormalMapped.fx' in bpy.data.materials) for i, expected in enumerate(mesh.shader_materials): principled = node_shader_utils.PrincipledBSDFWrapper( materials[i], is_readonly=True) actual = retrieve_shader_material(self, materials[i], principled) compare_shader_materials(self, expected, actual)
def create_dazzle(context, dazzle, coll): # Todo: proper dimensions for cone (dazzle_mesh, dazzle_cone) = create_cone(dazzle.name()) dazzle_cone.data.object_type = 'DAZZLE' dazzle_cone.data.dazzle_type = dazzle.type_name link_object_to_active_scene(dazzle_cone, coll) material = bpy.data.materials.new(dazzle.name()) material.use_nodes = True material.blend_method = 'BLEND' material.show_transparent_back = False principled = node_shader_utils.PrincipledBSDFWrapper(material, is_readonly=False) principled.base_color = (255, 255, 255) principled.base_color_texture.image = find_texture(context, 'SunDazzle.tga') dazzle_mesh.materials.append(material)
def load_b3d(filepath, context, IMPORT_CONSTRAIN_BOUNDS=10.0, IMAGE_SEARCH=True, APPLY_MATRIX=True, global_matrix=None): global ctx ctx = context data = B3DTree().parse(filepath) global images, materials images = {} materials = {} # load images dirname = os.path.dirname(filepath) for i, texture in enumerate(data['textures'] if 'textures' in data else []): texture_name = os.path.basename(texture['name']) for mat in data.materials: if mat.tids[0]==i: images[i] = load_image(texture_name, dirname, check_existing=True, place_holder=False, recursive=IMAGE_SEARCH) # create materials for i, mat in enumerate(data.materials if 'materials' in data else []): name = mat.name material = bpy.data.materials.new(name) material_wrap = node_shader_utils.PrincipledBSDFWrapper(material, is_readonly=False) # material.diffuse_color = mat.rgba[:] material_wrap.base_color = mat.rgba[:-1] # texture = bpy.data.textures.new(name=name, type='IMAGE') tid = mat.tids[0] if tid in images: material_wrap.base_color_texture.image = images[tid] material_wrap.base_color_texture.texcoords = 'UV' materials[i] = material global armatures, bonesdata, weighting, bones_ids, bones_node walk(data) if data.frames: make_skeleton(data)
def create_principled_bsdf(self, material, base_color=None, alpha=0, diffuse_tex=None, normal_tex=None, bump_scale=0): principled = node_shader_utils.PrincipledBSDFWrapper(material, is_readonly=False) if base_color is not None: principled.base_color = base_color if alpha > 0: principled.alpha = alpha if diffuse_tex is not None: tex = load_texture(self, diffuse_tex) if tex is not None: principled.base_color_texture.image = tex #principled.alpha_texture.image = tex if normal_tex is not None: tex = load_texture(self, normal_tex) if tex is not None: principled.normalmap_texture.image = tex principled.normalmap_strength = bump_scale return principled
def blender_to_skm(mesh, rig, WRITE_MDF): skm_data = SkmFile() contextMaterial = None context_mat_wrap = None contextMatrix_rot = None contextObName = "ToEE Model" rigObName = "ToEE Rig" armatureName = "ToEE Model Skeleton" TEXTURE_DICT = {} MATDICT = {} WRAPDICT = {} copy_set = set() # set of files to copy (texture images...) def mesh_to_skm_mesh( skm_data ): # myContextMesh_vertls, myContextMesh_facels, myContextMeshMaterials): ''' Creates Mesh Object from vertex/face/material data ''' bmesh = bpy.data.meshes['ToEE Model'] vertex_count = len(bmesh.vertices) face_count = len(bmesh.polygons) print("%d vertices, %d faces" % (vertex_count, face_count)) # Create vertices for vtx in bmesh.vertices: skm_vtx = SkmVertex() skm_vtx.pos = vtx.co.to_tuple() + (0.0, ) skm_vtx.normal = vtx.normal.to_tuple() + (0.0, ) skm_data.vertex_data.append(skm_vtx) assert len(skm_data.vertex_data) == vertex_count # Create faces (Triangles). Note: face should be triangles only! for p in bmesh.polygons: loop_start = p.loop_start loop_total = p.loop_total assert loop_total == 3, "Faces must be triangles!" face = bmesh.loops[loop_start + 0].vertex_index, bmesh.loops[ loop_start + 1].vertex_index, bmesh.loops[loop_start + 2].vertex_index skm_face = SkmFace() skm_face.vertex_ids = face skm_data.face_data.append(skm_face) assert len(skm_data.face_data) == face_count # Get UV coordinates for each polygon's vertices print("Setting UVs") uvl = bmesh.uv_layers[0].data[:] for fidx, fa in enumerate(skm_data.face_data): fa.material_id = bmesh.polygons[fidx].material_index for fidx, pl in enumerate(bmesh.polygons): face = skm_data.face_data[fidx] v1, v2, v3 = face.vertex_ids skm_data.vertex_data[v1].uv = uvl[pl.loop_start + 0].uv skm_data.vertex_data[v2].uv = uvl[pl.loop_start + 1].uv skm_data.vertex_data[v3].uv = uvl[pl.loop_start + 2].uv def rig_to_skm_bones(skm_data): ''' Converts rig/armature objects to SKM Bones ''' # Bones print("Getting bones") obj = bpy.data.objects[contextObName] barm = bpy.data.armatures[armatureName] rig = bpy.data.objects[rigObName] bpy.context.view_layer.objects.active = rig rig.select_set(True) bpy.ops.object.mode_set( mode='EDIT') # set to Edit Mode so bones can be accessed bone_ids = {} for bone_id, bone in enumerate(barm.edit_bones): bone_name = bone.name bone_ids[bone_name] = bone_id skm_bone = SkmBone(Name=bone_name) if bone.parent is None: skm_bone.parent_id = -1 else: skm_bone.parent_id = bone_ids[bone.parent.name] world = bone.matrix wi = world.inverted_safe() skm_bone.world_inverse = matrix4_to_3x4_array(wi) skm_data.bone_data.append(skm_bone) # Exit edit mode if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode='OBJECT') for vidx, vtx in enumerate(obj.data.vertices): for i, vg in enumerate(vtx.groups): bone_id = vg.group bone_wt = vg.weight skm_data.vertex_data[vidx].attachment_bones.append(bone_id) skm_data.vertex_data[vidx].attachment_weights.append(bone_wt) if skm_data.vertex_data[vidx].attachment_count > 6: raise Exception( f"Too many bone attachments for vertex {vidx}! Max is 6") return def material_to_skm_mat(mat_wrap, mdf_file_path): skm_mat = SkmMaterial(mdf_file_path) return skm_mat ## Create materials progress.enter_substeps(3, "Processing data...") progress.step("Processing Materials and images...") for mm in bpy.data.materials: #skm_data.material_data: material_name = mm.name if not material_name.lower().endswith('mdf'): print('Skipping material whose name doesn\'t end with .mdf: %r' % material_name) continue assert mm.use_nodes, "export_ska assumes use_nodes = True!" contextMaterial = mm mat_wrap = node_shader_utils.PrincipledBSDFWrapper(contextMaterial, is_readonly=False) assert mat_wrap.use_nodes == True, "huh? no use_nodes in wrapper?" context_mat_wrap = mat_wrap print("Converting material to SKM format: %s" % material_name) skm_mat = material_to_skm_mat(mat_wrap, material_name) if WRITE_MDF: mat_to_mdf_file(mat_wrap, skm_mat.id) MATDICT[material_name] = contextMaterial WRAPDICT[contextMaterial] = context_mat_wrap skm_data.material_data.append(skm_mat) # Convert Mesh object progress.step("Processing Mesh...") mesh_to_skm_mesh(skm_data) # Create Rig progress.step("Processing Rig...") rig_to_skm_bones(skm_data) # copy all collected files. io_utils.path_reference_copy(copy_set) progress.leave_substeps("Finished SKM conversion.") return skm_data
def retrieve_meshes(context, hierarchy, rig, container_name, force_vertex_materials=False): mesh_structs = [] used_textures = [] naming_error = False bone_names = [bone.name for bone in rig.pose.bones] if rig is not None else [] switch_to_pose(rig, 'REST') depsgraph = bpy.context.evaluated_depsgraph_get() for mesh_object in get_objects('MESH'): if mesh_object.data.object_type != 'MESH': continue if mesh_object.mode != 'OBJECT': bpy.ops.object.mode_set(mode='OBJECT') mesh_struct = Mesh() mesh_struct.header = MeshHeader(mesh_name=mesh_object.name, container_name=container_name) header = mesh_struct.header header.sort_level = mesh_object.data.sort_level mesh_struct.user_text = mesh_object.data.userText if mesh_object.hide_get(): header.attrs |= GEOMETRY_TYPE_HIDDEN if mesh_object.data.casts_shadow: header.attrs |= GEOMETRY_TYPE_CAST_SHADOW if mesh_object.data.two_sided: header.attrs |= GEOMETRY_TYPE_TWO_SIDED mesh_object = mesh_object.evaluated_get(depsgraph) mesh = mesh_object.data b_mesh = prepare_bmesh(context, mesh) if len(mesh.vertices) == 0: context.warning( f'mesh \'{mesh.name}\' did not have a single vertex!') continue center, radius = calculate_mesh_sphere(mesh) header.sph_center = center header.sph_radius = radius if mesh.uv_layers: mesh.calc_tangents() header.vert_count = len(mesh.vertices) loop_dict = dict() for loop in mesh.loops: loop_dict[loop.vertex_index] = loop _, _, scale = mesh_object.matrix_local.decompose() is_skinned = False for vertex in mesh.vertices: if vertex.groups: is_skinned = True unskinned_vertices_error = False overskinned_vertices_error = False for i, vertex in enumerate(mesh.vertices): mesh_struct.shade_ids.append(i) matrix = Matrix.Identity(4) if vertex.groups: vert_inf = VertexInfluence() vert_inf.bone_idx = find_bone_index(hierarchy, mesh_object, vertex.groups[0].group) vert_inf.bone_inf = vertex.groups[0].weight if len(vertex.groups) > 1: mesh_struct.multi_bone_skinned = True vert_inf.xtra_idx = find_bone_index( hierarchy, mesh_object, vertex.groups[1].group) vert_inf.xtra_inf = vertex.groups[1].weight if vert_inf.bone_inf < 0.01 and vert_inf.xtra_inf < 0.01: context.warning( f'mesh \'{mesh_object.name}\' vertex {i} both bone weights where 0!' ) vert_inf.bone_inf = 1.0 if abs(vert_inf.bone_inf + vert_inf.xtra_inf - 1.0) > 0.1: context.warning( f'mesh \'{mesh_object.name}\' vertex {i} both bone weights did not add up to 100%! ({vert_inf.bone_inf:.{2}f}, {vert_inf.xtra_inf:.{2}f})' ) vert_inf.bone_inf = 1.0 - vert_inf.xtra_inf mesh_struct.vert_infs.append(vert_inf) if vert_inf.bone_idx > 0: matrix = matrix @ rig.data.bones[hierarchy.pivots[ vert_inf.bone_idx].name].matrix_local.inverted() else: matrix = matrix @ rig.matrix_local.inverted() if len(vertex.groups) > 2: overskinned_vertices_error = True context.error( f'mesh \'{mesh_object.name}\' vertex {i} is influenced by more than 2 bones ({len(vertex.groups)})!' ) elif is_skinned: unskinned_vertices_error = True context.error( f'skinned mesh \'{mesh_object.name}\' vertex {i} is not rigged to any bone!' ) vertex.co.x *= scale.x vertex.co.y *= scale.y vertex.co.z *= scale.z mesh_struct.verts.append(matrix @ vertex.co) _, rotation, _ = matrix.decompose() if i in loop_dict: loop = loop_dict[i] # do NOT use loop.normal here! that might result in weird shading issues mesh_struct.normals.append(rotation @ vertex.normal) if mesh.uv_layers: # in order to adapt to 3ds max orientation mesh_struct.tangents.append( (rotation @ loop.bitangent) * -1) mesh_struct.bitangents.append((rotation @ loop.tangent)) else: context.warning( f'mesh \'{mesh_object.name}\' vertex {i} is not connected to any face!' ) mesh_struct.normals.append(rotation @ vertex.normal) if mesh.uv_layers: # only dummys mesh_struct.tangents.append( (rotation @ vertex.normal) * -1) mesh_struct.bitangents.append((rotation @ vertex.normal)) if unskinned_vertices_error or overskinned_vertices_error: return ([], []) header.min_corner = Vector( (mesh_object.bound_box[0][0], mesh_object.bound_box[0][1], mesh_object.bound_box[0][2])) header.max_corner = Vector( (mesh_object.bound_box[6][0], mesh_object.bound_box[6][1], mesh_object.bound_box[6][2])) for poly in mesh.polygons: triangle = Triangle(vert_ids=list(poly.vertices), normal=Vector(poly.normal)) vec1 = mesh.vertices[poly.vertices[0]].co vec2 = mesh.vertices[poly.vertices[1]].co vec3 = mesh.vertices[poly.vertices[2]].co tri_pos = (vec1 + vec2 + vec3) / 3.0 triangle.distance = tri_pos.length mesh_struct.triangles.append(triangle) if context.file_format == 'W3X' and len(mesh_object.face_maps) > 0: context.warning( 'triangle surface types (mesh face maps) are not supported in W3X file format!' ) else: face_map_names = [map.name for map in mesh_object.face_maps] Triangle.validate_face_map_names(context, face_map_names) for map in mesh.face_maps: for i, val in enumerate(map.data): mesh_struct.triangles[i].set_surface_type( face_map_names[val.value]) header.face_count = len(mesh_struct.triangles) center, radius = calculate_mesh_sphere(mesh) header.sphCenter = center header.sphRadius = radius tx_stages = [] for i, uv_layer in enumerate(mesh.uv_layers): stage = TextureStage(tx_ids=[[i]], tx_coords=[[Vector( (0.0, 0.0))] * len(mesh_struct.verts)]) for j, face in enumerate(b_mesh.faces): for loop in face.loops: vert_index = mesh_struct.triangles[j].vert_ids[loop.index % 3] stage.tx_coords[0][vert_index] = uv_layer.data[ loop.index].uv.copy() tx_stages.append(stage) b_mesh.free() for i, material in enumerate(mesh.materials): mat_pass = MaterialPass() if material is None: context.warning( f'mesh \'{mesh_object.name}\' uses a invalid/empty material!' ) continue principled = node_shader_utils.PrincipledBSDFWrapper( material, is_readonly=True) used_textures = get_used_textures(material, principled, used_textures) if context.file_format == 'W3X' or ( material.material_type == 'SHADER_MATERIAL' and not force_vertex_materials): mat_pass.shader_material_ids = [i] if i < len(tx_stages): mat_pass.tx_coords = tx_stages[i].tx_coords[0] mesh_struct.shader_materials.append( retrieve_shader_material(context, material, principled)) else: shader = retrieve_shader(material) mesh_struct.shaders.append(shader) mat_pass.shader_ids = [i] mat_pass.vertex_material_ids = [i] mesh_struct.vert_materials.append( retrieve_vertex_material(material, principled)) base_col_tex = principled.base_color_texture if base_col_tex is not None and base_col_tex.image is not None: info = TextureInfo() img = base_col_tex.image filepath = os.path.basename(img.filepath) if filepath == '': filepath = img.name tex = Texture(id=img.name, file=filepath, texture_info=info) mesh_struct.textures.append(tex) shader.texturing = 1 if i < len(tx_stages): mat_pass.tx_stages.append(tx_stages[i]) mesh_struct.material_passes.append(mat_pass) for layer in mesh.vertex_colors: if '_' in layer.name: index = int(layer.name.split('_')[-1]) else: index = 0 if 'DCG' in layer.name: target = mesh_struct.material_passes[index].dcg elif 'DIG' in layer.name: target = mesh_struct.material_passes[index].dig elif 'SCG' in layer.name: target = mesh_struct.material_passes[index].scg else: context.warning( f'vertex color layer name \'{layer.name}\' is not one of [DCG, DIG, SCG]' ) continue target = [RGBA] * len(mesh.vertices) for i, loop in enumerate(mesh.loops): target[loop.vertex_index] = RGBA(layer.data[i].color) header.vert_channel_flags = VERTEX_CHANNEL_LOCATION | VERTEX_CHANNEL_NORMAL if mesh_struct.vert_infs: header.attrs |= GEOMETRY_TYPE_SKIN header.vert_channel_flags |= VERTEX_CHANNEL_BONE_ID if len(mesh_object.constraints) > 0: context.warning( f'mesh \'{mesh_object.name }\' is rigged and thus does not support any constraints!' ) else: if len(mesh_object.constraints) > 1: context.warning( f'mesh \'{mesh_object.name}\' has multiple constraints applied, only \'Copy Rotation\' OR \'Damped Track\' are supported!' ) for constraint in mesh_object.constraints: if constraint.name == 'Copy Rotation': header.attrs |= GEOMETRY_TYPE_CAMERA_ORIENTED break if constraint.name == 'Damped Track': header.attrs |= GEOMETRY_TYPE_CAMERA_ALIGNED break context.warning( f'mesh \'{mesh_object.name}\' constraint \'{constraint.name}\' is not supported!' ) if mesh_object.name in bone_names: if not (mesh_struct.is_skin() or mesh_object.parent_type == 'BONE' and mesh_object.parent_bone == mesh_object.name): naming_error = True context.error( f'mesh \'{mesh_object.name}\' has same name as bone \'{mesh_object.name}\' but is not configured properly!' ) context.info( 'EITHER apply an armature modifier to it, create a vertex group with the same name as the mesh and do the weight painting OR set the armature as parent object and the identically named bone as parent bone.' ) continue if mesh_struct.shader_materials: header.vert_channel_flags |= VERTEX_CHANNEL_TANGENT | VERTEX_CHANNEL_BITANGENT else: mesh_struct.tangents = [] mesh_struct.bitangents = [] mesh_struct.mat_info = MaterialInfo( pass_count=len(mesh_struct.material_passes), vert_matl_count=len(mesh_struct.vert_materials), shader_count=len(mesh_struct.shaders), texture_count=len(mesh_struct.textures)) mesh_struct.header.matl_count = max(len(mesh_struct.vert_materials), len(mesh_struct.shader_materials)) mesh_structs.append(mesh_struct) switch_to_pose(rig, 'POSE') if naming_error: return [], [] return mesh_structs, used_textures
def generate_pass(self, mat, pass_name=""): usermat = texnodes = None if mat.use_ogre_parent_material: usermat = get_ogre_user_material(mat.ogre_parent_material) texnodes = shader.get_texture_subnodes(self.material, mat) if usermat: self.w.iword('pass %s : %s/PASS0' % (pass_name, usermat.name)) else: self.w.iword('pass') if pass_name: self.w.word(pass_name) with self.w.embed(): # Texture wrappers textures = {} mat_wrapper = node_shader_utils.PrincipledBSDFWrapper(mat) for tex_key in self.TEXTURE_KEYS: texture = getattr(mat_wrapper, tex_key, None) if texture and texture.image: textures[tex_key] = texture # adds image to the list for later copy self.images.add(texture.image) color = mat_wrapper.base_color alpha = 1.0 if mat.blend_method != "OPAQUE": alpha = mat_wrapper.alpha self.w.iword('scene_blend alpha_blend').nl() if mat.show_transparent_back: self.w.iword('cull_hardware none').nl() self.w.iword('depth_write off').nl() # arbitrary bad translation from PBR to Blinn Phong # derive proportions from metallic bf = 1.0 - mat_wrapper.metallic mf = max(0.04, mat_wrapper.metallic) # derive specular color sc = mathutils.Color( color[:3]) * mf + (1.0 - mf) * mathutils.Color( (1, 1, 1)) * (1.0 - mat_wrapper.roughness) si = (1.0 - mat_wrapper.roughness) * 128 self.w.iword('diffuse').round(color[0] * bf).round( color[1] * bf).round(color[2] * bf).round(alpha).nl() self.w.iword('specular').round(sc[0]).round(sc[1]).round( sc[2]).round(alpha).round(si, 3).nl() for name in dir( mat): #mat.items() - items returns custom props not pyRNA: if name.startswith('ogre_') and name != 'ogre_parent_material': var = getattr(mat, name) op = name.replace('ogre_', '') val = var if type(var) == bool: if var: val = 'on' else: val = 'off' self.w.iword(op).word(val).nl() self.w.nl() if texnodes and usermat.texture_units: for i, name in enumerate(usermat.texture_units_order): if i < len(texnodes): node = texnodes[i] if node.texture: geo = shader.get_connected_input_nodes( self.material, node)[0] # self.generate_texture_unit( node.texture, name=name, uv_layer=geo.uv_layer ) raise NotImplementedError( "TODO: slots dont exist anymore - use image") elif textures: for key, texture in textures.items(): self.generate_texture_unit(key, texture)
def create_material_from_shader_material(context, name, shader_mat): name = name + '.' + shader_mat.header.type_name if name in bpy.data.materials: material = bpy.data.materials[name] principled = node_shader_utils.PrincipledBSDFWrapper(material, is_readonly=False) return material, principled material = bpy.data.materials.new(name) material.material_type = 'SHADER_MATERIAL' material.use_nodes = True material.blend_method = 'BLEND' material.show_transparent_back = False material.technique = shader_mat.header.technique principled = node_shader_utils.PrincipledBSDFWrapper(material, is_readonly=False) for prop in shader_mat.properties: if prop.name == 'DiffuseTexture' and prop.value != '': principled.base_color_texture.image = find_texture( context, prop.value) elif prop.name == 'NormalMap' and prop.value != '': principled.normalmap_texture.image = find_texture( context, prop.value) elif prop.name == 'BumpScale': principled.normalmap_strength = prop.value elif prop.name == 'SpecMap' and prop.value != '': principled.specular_texture.image = find_texture( context, prop.value) elif prop.name == 'SpecularExponent' or prop.name == 'Shininess': material.specular_intensity = prop.value / 200.0 elif prop.name == 'DiffuseColor' or prop.name == 'ColorDiffuse': material.diffuse_color = prop.to_rgba() elif prop.name == 'SpecularColor' or prop.name == 'ColorSpecular': material.specular = prop.to_rgb() elif prop.name == 'CullingEnable': material.use_backface_culling = prop.value elif prop.name == 'Texture_0': principled.base_color_texture.image = find_texture( context, prop.value) # all props below have no effect on shading -> custom properties for roundtrip purpose elif prop.name == 'AmbientColor' or prop.name == 'ColorAmbient': material.ambient = prop.to_rgba() elif prop.name == 'EmissiveColor' or prop.name == 'ColorEmissive': principled.emission_color = prop.to_rgb() elif prop.name == 'Opacity': principled.alpha = prop.value elif prop.name == 'AlphaTestEnable': material.alpha_test = prop.value elif prop.name == 'BlendMode': # is blend_method ? material.blend_mode = prop.value elif prop.name == 'BumpUVScale': material.bump_uv_scale = prop.value.xy elif prop.name == 'EdgeFadeOut': material.edge_fade_out = prop.value elif prop.name == 'DepthWriteEnable': material.depth_write = prop.value elif prop.name == 'Sampler_ClampU_ClampV_NoMip_0': material.sampler_clamp_uv_no_mip_0 = prop.value elif prop.name == 'Sampler_ClampU_ClampV_NoMip_1': material.sampler_clamp_uv_no_mip_1 = prop.value elif prop.name == 'NumTextures': material.num_textures = prop.value # is 1 if texture_0 and texture_1 are set elif prop.name == 'Texture_1': # second diffuse texture material.texture_1 = prop.value elif prop.name == 'SecondaryTextureBlendMode': material.secondary_texture_blend_mode = prop.value elif prop.name == 'TexCoordMapper_0': material.tex_coord_mapper_0 = prop.value elif prop.name == 'TexCoordMapper_1': material.tex_coord_mapper_1 = prop.value elif prop.name == 'TexCoordTransform_0': material.tex_coord_transform_0 = prop.value elif prop.name == 'TexCoordTransform_1': material.tex_coord_transform_1 = prop.value elif prop.name == 'EnvironmentTexture': material.environment_texture = prop.value elif prop.name == 'EnvMult': material.environment_mult = prop.value elif prop.name == 'RecolorTexture': material.recolor_texture = prop.value elif prop.name == 'RecolorMultiplier': material.recolor_mult = prop.value elif prop.name == 'UseRecolorColors': material.use_recolor = prop.value elif prop.name == 'HouseColorPulse': material.house_color_pulse = prop.value elif prop.name == 'ScrollingMaskTexture': material.scrolling_mask_texture = prop.value elif prop.name == 'TexCoordTransformAngle_0': material.tex_coord_transform_angle = prop.value elif prop.name == 'TexCoordTransformU_0': material.tex_coord_transform_u_0 = prop.value elif prop.name == 'TexCoordTransformV_0': material.tex_coord_transform_v_0 = prop.value elif prop.name == 'TexCoordTransformU_1': material.tex_coord_transform_u_1 = prop.value elif prop.name == 'TexCoordTransformV_1': material.tex_coord_transform_v_1 = prop.value elif prop.name == 'TexCoordTransformU_2': material.tex_coord_transform_u_2 = prop.value elif prop.name == 'TexCoordTransformV_2': material.tex_coord_transform_v_2 = prop.value elif prop.name == 'TextureAnimation_FPS_NumPerRow_LastFrame_FrameOffset_0': material.tex_ani_fps_NPR_lastFrame_frameOffset_0 = prop.value elif prop.name == 'IonHullTexture': material.ion_hull_texture = prop.value elif prop.name == 'MultiTextureEnable': material.multi_texture_enable = prop.value else: context.error('shader property not implemented: ' + prop.name) return material, principled
def import_skn(context, file, directory): skn_type = read_string(file, 256) # todo: check version if 'Eternity Engine Skin File' not in skn_type: context.window_manager.popup_menu(invalid_skn_type, title='Warning', icon='ERROR') return None skn_data = {'materials': [], 'diffuse_textures': []} # todo: check with msh name msh_name = read_string(file, 256) # todo: what is it (maybe version) file.seek(4, os.SEEK_CUR) materials_num = read_int(file) # todo file.seek(4, os.SEEK_CUR) file.seek(4, os.SEEK_CUR) file.seek(0x400) for _ in range(materials_num): mat_name = read_string(file, 256) mat = bpy.data.materials.get(mat_name) mat_wrap = None if not mat: mat = bpy.data.materials.new(name=mat_name) mat_wrap = node_shader_utils.PrincipledBSDFWrapper( mat, is_readonly=False) # todo: other material types mat_type = read_string(file, 256) file.seek(0x200, os.SEEK_CUR) prop_num = read_int(file) for _ in range(prop_num): prop_section_name = read_string(file, read_int(file)) prop = read_int(file) # todo if prop == 1: read_float(file) # todo elif prop == 2: read_float(file, 4) # todo: test elif prop == 3: prop_name = read_string(file, read_int(file)) # new material if mat_wrap: tex = bpy.data.textures.get(prop_name) if not tex: tex = bpy.data.textures.new(prop_name, type='IMAGE') image = load_image(prop_name, directory) else: image = bpy.data.images.get(prop_name) if 'Diffuse' in prop_section_name: if image: tex.image = image nodetex = mat_wrap.base_color_texture nodetex.image = image nodetex.texcoords = 'UV' if image.depth in {32, 128}: mat.blend_method = 'HASHED' add_transparent_node(mat.node_tree) skn_data['diffuse_textures'].append(tex) skn_data['materials'].append(mat) return skn_data
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 importMaterials(MATName, image_transparency, texture_ext): with open(MATName, 'rb') as mt: mt.seek(0x10, 0) MATCheck = struct.unpack('<L', mt.read(4))[0] if (MATCheck == 0x4D41544C): MATVerA = struct.unpack('<H', mt.read(2))[0] MATVerB = struct.unpack('<H', mt.read(2))[0] MATHeadOff = mt.tell() + struct.unpack('<L', mt.read(4))[0]; mt.seek(0x04, 1) MATCount = struct.unpack('<L', mt.read(4))[0]; mt.seek(0x04, 1) mt.seek(MATHeadOff, 0) for m in range(MATCount): pe = MaterialData() MATNameOff = mt.tell() + struct.unpack('<L', mt.read(4))[0]; mt.seek(0x04, 1) MATParamGrpOff = mt.tell() + struct.unpack('<L', mt.read(4))[0]; mt.seek(0x04, 1) MATParamGrpCount = struct.unpack('<L', mt.read(4))[0]; mt.seek(0x04, 1) MATShdrNameOff = mt.tell() + struct.unpack('<L', mt.read(4))[0]; mt.seek(0x04, 1) MATRet = mt.tell() mt.seek(MATNameOff, 0) pe.materialName = readVarLenString(mt) print("Textures for " + pe.materialName + ":") mt.seek(MATParamGrpOff, 0) for p in range(MATParamGrpCount): MatParamID = struct.unpack('<L', mt.read(4))[0]; mt.seek(0x04, 1) MatParamOff = mt.tell() + struct.unpack('<L', mt.read(4))[0]; mt.seek(0x04, 1) MatParamType = struct.unpack('<L', mt.read(4))[0]; mt.seek(0x04, 1) MatParamRet = mt.tell() if (MatParamType == 0x0B): mt.seek(MatParamOff + 0x08, 0) TexName = str.lower(readVarLenString(mt)) print("(" + hex(MatParamID) + ") for " + TexName) if (MatParamID == 0x5C): pe.color1Name = TexName elif (MatParamID == 0x5D): pe.color2Name = TexName elif (MatParamID == 0x5F): pe.bakeName = TexName elif (MatParamID == 0x60): pe.normalName = TexName elif (MatParamID == 0x61): pe.emissive1Name = TexName if (pe.color1Name == ""): pe.color1Name = TexName elif (MatParamID == 0x62): pe.prmName = TexName elif (MatParamID == 0x63): pe.envName = TexName elif (MatParamID == 0x65): pe.bakeName = TexName elif (MatParamID == 0x66): pe.color1Name = TexName elif (MatParamID == 0x67): pe.color2Name = TexName elif (MatParamID == 0x6A): pe.emissive2Name = TexName if (pe.color2Name == ""): pe.color2Name = TexName elif (MatParamID == 0x133): print("noise_for_warp") else: print("Unknown type (" + hex(MatParamID) + ") for " + TexName) mt.seek(MatParamRet, 0) print("-----") Materials_array.append(pe) mt.seek(MATRet, 0) for m in range(MATCount): # Check and reuse existing same-name material, or create it if it doesn't already exist if (bpy.data.materials.find(Materials_array[m].materialName) > 0): mat = bpy.data.materials[Materials_array[m].materialName] else: mat = bpy.data.materials.new(Materials_array[m].materialName) mat.use_fake_user = True # Check and reuse existing same-name primary texture slot, or create it if it doesn't already exist if (Materials_array[m].color1Name != ""): img = image_utils.load_image(Materials_array[m].color1Name + texture_ext, dirPath, place_holder=True, check_existing=True, force_reload=True) img.alpha_mode = image_transparency ma_wrap = node_shader_utils.PrincipledBSDFWrapper(mat, is_readonly=False) ma_wrap.base_color_texture.image = img ma_wrap.base_color_texture.texcoords = 'UV' # Check and reuse existing same-name primary texture slot, or create it if it doesn't already exist # if (Materials_array[m].color2Name != ""): # if (bpy.data.textures.find(Materials_array[m].color2Name) > 0): # altTex = bpy.data.textures[Materials_array[m].color2Name] # else: # altTex = bpy.data.textures.new(Materials_array[m].color2Name, type='IMAGE') # altImg = image_utils.load_image(Materials_array[m].color2Name + texture_ext, dirPath, place_holder=True, check_existing=True, force_reload=True) # altImg.alpha_mode = image_transparency # altTex.image = altImg # if (altTex.name not in mat.texture_slots): # altSlot = mat.texture_slots.add() # altSlot.texture = altTex # altSlot.texture_coords = 'UV' print(Materials_array)
def write_level(filepath, context): scene = context.scene # collect meshes level_meshes = [] for obj in scene.objects: if obj.type != 'MESH': continue mesh = obj.to_mesh() mesh.transform(obj.matrix_world) print(obj.matrix_world) mesh_vertices = mesh.vertices[:] mesh_polygons = mesh.polygons[:] materials = mesh.materials[:] uv_layer = None if len(mesh.uv_layers) > 0: uv_layer = mesh.uv_layers.active.data # one mesh per material for mat_index, mat in enumerate(materials): vertex_dict = {} vertices = [] faces = [] polygons = [ p for p in mesh_polygons if p.material_index == mat_index ] for poly in polygons: if uv_layer is not None: texcoords = [ uv_layer[i].uv for i in range(poly.loop_start, poly.loop_start + poly.loop_total) ] else: texcoords = [[0, 0] * len(poly.vertices)] face = [] for i in range(len(poly.vertices)): vertex = mesh_vertices[poly.vertices[i]] position = vertex.co normal = vertex.normal texcoord = texcoords[i] key = vec3_to_key(position), vec3_to_key( normal), vec2_to_key(texcoord) vertex_index = vertex_dict.get(key) if vertex_index is None: vertex_index = vertex_dict[key] = len(vertices) vertices.append((position, normal, texcoord)) face.append(vertex_index) faces.append(face) mat_wrap = node_shader_utils.PrincipledBSDFWrapper(mat) base_color_texture = mat_wrap.base_color_texture.image.filepath material = Material( name=mat.name, base_color_texture=os.path.basename(base_color_texture)) level_meshes.append( LevelMesh(material=material, vertices=vertices, faces=faces)) obj.to_mesh_clear() # dump meshes print('Exporting %d meshes' % (len(level_meshes))) with open(filepath, 'wb') as outfile: write_int32(outfile, len(level_meshes)) for mesh in level_meshes: print('Exporting submesh (material: `%s`, verts: %d, faces: %d)' % (mesh.material.name, len(mesh.vertices), len(mesh.faces))) # write material write_string(outfile, mesh.material.name) write_string(outfile, mesh.material.base_color_texture) # write vertices write_int32(outfile, len(mesh.vertices)) for vertex in mesh.vertices: write_vec3(outfile, vertex[0]) # position write_vec3(outfile, vertex[1]) # normal write_vec2(outfile, vertex[2]) # texcoord # write faces write_int32(outfile, len(mesh.faces)) for face in mesh.faces: write_int8(outfile, len(face)) for vert_index in face: write_int32(outfile, vert_index)
def write_mesh(mesh): mesh_vertices = mesh.vertices[:] mesh_polygons = mesh.polygons[:] materials = mesh.materials[:] uv_layer = None if len(mesh.uv_layers) > 0: uv_layer = mesh.uv_layers.active.data # write meshes (one per material) write_int32(outfile, len(materials)) for mat_index, mat in enumerate(materials): # collect vertices/triangles polygons = [ p for p in mesh_polygons if p.material_index == mat_index ] vertex_dict = {} vertices = [] triangles = [] for poly in polygons: if uv_layer is not None: texcoords = [ uv_layer[i].uv for i in range(poly.loop_start, poly.loop_start + poly.loop_total) ] else: texcoords = [[0, 0] * len(poly.vertices)] def vec3_to_key(v): return round(v[0], 6), round(v[1], 6), round(v[2], 6) def vec2_to_key(v): return round(v[0], 6), round(v[1], 6) def vertex_index(i): vertex = mesh_vertices[poly.vertices[i]] position = vertex.co normal = vertex.normal texcoord = texcoords[i] key = vec3_to_key(position), vec3_to_key( normal), vec2_to_key(texcoord) vertex_index = vertex_dict.get(key) if vertex_index is None: vertex_index = vertex_dict[key] = len(vertices) vertices.append((position, normal, texcoord)) return vertex_index for i in range(1, len(poly.vertices) - 1): triangles.append((vertex_index(0), vertex_index(i), vertex_index(i + 1))) print( 'Exporting submesh (material: `%s`, vertices: %d, triangles: %d)' % (mat.name, len(vertices), len(triangles))) # write material write_string(outfile, mat.name) mat_wrap = node_shader_utils.PrincipledBSDFWrapper(mat) base_color_texture = mat_wrap.base_color_texture.image.filepath write_string(outfile, os.path.basename(base_color_texture)) # write vertices write_int32(outfile, len(vertices)) for vertex in vertices: write_vec3(outfile, vertex[0]) # position write_vec3(outfile, vertex[1]) # normal write_vec2(outfile, vertex[2]) # texcoord # write triangles write_int32(outfile, len(triangles)) for tri in triangles: write_int32(outfile, tri[0]) write_int32(outfile, tri[1]) write_int32(outfile, tri[2])
def create_material_from_RSE_specification( materialSpecification: RSEMaterialDefinition, texturePaths: List[str]): """Creates a material from an RSE specification. This does ignore some values that don't map well to PBR and don't influence model behaviour much. Materials will be more finely tuned in the game engine. materialSpecification is an RSEMaterialDefinition as read by RSEModelReader gameDataPath is meant to be the Data folder within the games installation directory, as that directory structure is used when loading textures""" #Create new material newMaterial = bpy.data.materials.new( name=materialSpecification.material_name.string) newMaterialBSDFWrap = node_shader_utils.PrincipledBSDFWrapper( newMaterial, is_readonly=False) textureName = materialSpecification.texture_name.string texToLoad = None #Search for texture to load for path in texturePaths: texToLoad = find_texture(textureName, path) #if a texture was found, don't continue searching if texToLoad is not None: texToLoad = texToLoad + ".PNG" break if texToLoad is None: log.error("Failed to find texture: %s", textureName) else: log.debug("Final texture to load: %s", texToLoad) #TODO: Refactor texture loading, expand to support image sequences if texToLoad is not None: # Texture loading code adapted from https://stackoverflow.com/q/19076062 # Load the image texImage = bpy.data.images.load(texToLoad) # Create texture from image # Add texture slot for color texture #textureSlot = newMaterial.texture_paint_slots.add() newMaterialBSDFWrap.base_color_texture.use_alpha = True #textureSlot.use_map_alpha = True #textureSlot.alpha_factor = materialSpecification.opacity newMaterialBSDFWrap.base_color_texture.image = texImage newMaterialBSDFWrap.base_color_texture.texcoords = 'UV' materialBlendMode = "opaque" if materialSpecification.CXPMaterialProperties is not None: materialBlendMode = materialSpecification.CXPMaterialProperties.blendMode if materialBlendMode != "opaque": #TODO: Enable proper translucency in blender 2.8 #TODO: Toggle shadows in blender 2.8 #newMaterial.use_transparency = True #Blenders material transparency method is different to how masked alpha would work in a game engine, # this still provides alpha blending, but if you use Z method the transparent part of the surface # still has specular properties. In this instance, MASK provides expected results #On further reflection, this might actually be desired. Left the comments here and other parameters which can be tweaked as more areas are tested. This also has the benefit of allowing transparency to work in Rendered mode. #newMaterial.transparency_method = 'MASK' if materialBlendMode == "colorkey": #TODO: Setup a node in material to mask based on the colorkey color newMaterial.blend_method = 'CLIP' else: newMaterial.blend_method = 'BLEND' #newMaterial.specular_alpha = 0.0 #disable shadowing on translucent materials, as unexpected results occur otherwise #TODO: Reenable the options for toggling shadows when API is better documented #newMaterialBSDFWrap.use_cast_shadows = False #newMaterialBSDFWrap.use_shadows = False if texToLoad is not None: newMaterialBSDFWrap.transmission = 0.0 else: #TODO: Check that opacity is not used when alphaMethod == 1 or SAM_Opaque newMaterialBSDFWrap.transmission = materialSpecification.opacity # TODO: work out if materialSpecification.ambient should be averaged and applied # to newMaterial.ambient, or if it's for the lighting model that might be # specified in materialSpecification.unknown2 newMaterial.diffuse_color = materialSpecification.diffuseColorFloat[ 0:3] # change color newMaterial.specular_color = materialSpecification.specularColorFloat[0:3] newMaterialBSDFWrap.specular = materialSpecification.specularLevel newMaterialBSDFWrap.roughness = 1 - materialSpecification.specularLevel #newMaterial.use_vertex_color_light = True return newMaterial
def skm_to_blender(skm_data, importedObjects, IMAGE_SEARCH): from bpy_extras.image_utils import load_image contextObName = None contextMaterial = None context_mat_wrap = None contextMatrix_rot = None # Blender.mathutils.Matrix(); contextMatrix.identity() # contextMatrix_tx = None # Blender.mathutils.Matrix(); contextMatrix.identity() TEXTURE_DICT = {} MATDICT = {} WRAPDICT = {} # mdf_resolve # read_texture # bpy_extras.image_utils.load_image # load_material_image def read_texture(texture_path, name, mapto): new_texture = bpy.data.textures.new(name, type='IMAGE') u_scale, v_scale, u_offset, v_offset = 1.0, 1.0, 0.0, 0.0 mirror = False extension = 'wrap' # 'mirror', 'decal' img = TEXTURE_DICT[contextMaterial.name] = load_image( texture_path, ToEE_data_dir) # add the map to the material in the right channel if img: load_material_image(context_mat_wrap, img, new_texture, (u_scale, v_scale), (u_offset, v_offset), extension, mapto) def mdf_resolve(mdf_filename): mdf_fullpath = os.path.join(ToEE_data_dir, mdf_filename) if not os.path.exists(mdf_fullpath): print("MDF %s not found" % mdf_fullpath) return with open(mdf_fullpath, 'rb') as mdf_file: mdf_raw = mdf_file.read() mdf_data = MdfFile() mdf_data.from_raw_data(mdf_raw) print("Registering texture: %s" % mdf_data.texture_filepath) read_texture( mdf_data.texture_filepath, "Diffuse", "COLOR" ) # "Specular / SPECULARITY", "Opacity / ALPHA", "Bump / NORMAL" return def putContextMesh( skm_data ): # myContextMesh_vertls, myContextMesh_facels, myContextMeshMaterials): ''' Creates Mesh Object from vertex/face/material data ''' # Create new mesh bmesh = bpy.data.meshes.new(contextObName) vertex_count = len(skm_data.vertex_data) face_count = len(skm_data.face_data) print("------------ FLAT ----------------") print("%d vertices, %d faces" % (vertex_count, face_count)) # Create vertices bmesh.vertices.add(vertex_count) flattened_vtx_pos = [ t for vtx in skm_data.vertex_data for t in vtx.pos[0:3] ] bmesh.vertices.foreach_set("co", flattened_vtx_pos) # Create faces (Triangles) - make face_count Polygons, each loop defined by 3 vertices bmesh.polygons.add(face_count) bmesh.loops.add(face_count * 3) bmesh.polygons.foreach_set("loop_start", range(0, face_count * 3, 3)) bmesh.polygons.foreach_set("loop_total", (3, ) * face_count) flattened_face_vtx_map = [ t for fa in skm_data.face_data for t in fa.vertex_ids ] bmesh.loops.foreach_set("vertex_index", flattened_face_vtx_map) # Apply Materials for mm in skm_data.material_data: mat_name = mm.id.name bmat = MATDICT.get(mm.id.name) if bmat: img = TEXTURE_DICT.get(bmat.name) else: print(" WARNING! Material %s not defined!" % mat_name) bmat = MATDICT[mat_name] = bpy.data.materials.new(mat_name) img = None bmesh.materials.append(bmat) # Get UV coordinates for each polygon's vertices print("Setting UVs") bmesh.uv_layers.new(do_init=False) uv_faces = bmesh.uv_layers.active.data[:] if uv_faces: for fidx, fa in enumerate(skm_data.face_data): bmesh.polygons[fidx].material_index = fa.material_id # bmat = bmesh.materials[fa.material_id] # img = TEXTURE_DICT.get(bmat.name) # uv_faces[fidx].image = img uvl = bmesh.uv_layers.active.data[:] for fidx, pl in enumerate(bmesh.polygons): face = skm_data.face_data[fidx] v1, v2, v3 = face.vertex_ids uvl[pl.loop_start + 0].uv = skm_data.vertex_data[v1].uv uvl[pl.loop_start + 1].uv = skm_data.vertex_data[v2].uv uvl[pl.loop_start + 2].uv = skm_data.vertex_data[v3].uv # Finish up bmesh.validate() bmesh.update() # Create new object from mesh ob = bpy.data.objects.new(contextObName, bmesh) object_dictionary[contextObName] = ob importedObjects.append(ob) collection = SCN.collection collection.objects.link(ob) ob.select_set(True) if contextMatrix_rot: ob.matrix_local = contextMatrix_rot object_matrix[ob] = contextMatrix_rot.copy() def putRig(skm_data): ''' Creates rig object for Mesh Object, and parents it (Armature type parenting, so it deforms it via bones) ''' # Bones print("Getting bones") obj = object_dictionary[contextObName] barm = bpy.data.armatures.new(armatureName) rig = bpy.data.objects.new(rigObName, barm) SCN.collection.objects.link(rig) bpy.context.view_layer.objects.active = rig rig.select_set(True) bpy.ops.object.mode_set( mode='EDIT') # set to Edit Mode so bones can be added bone_dump = open(os.path.join(ToEE_data_dir, "bone_dump.txt"), 'w') fh = open("D:/bones.txt", "wt") vertex_groups = dict() for bone_id, bd in enumerate(skm_data.bone_data): bone_name = str(bd.name) bone = barm.edit_bones.new(bone_name) # type: bpy.types.EditBone bone.select = True wi = mathutils.Matrix(bd.world_inverse_matrix) world = wi.inverted_safe() if bd.parent_id != -1: bone.parent = barm.edit_bones[bd.parent_id] bone.head = Vector([0, 0, 0]) bone.tail = Vector([0, 0, 5]) bone.matrix = world print( "****************************************************************************", file=fh) print(bone_name, file=fh) print("BONE MATRIX:", file=fh) print(bone.matrix, file=fh) print("WORLD INVERSE:", file=fh) print(world, file=fh) print( "****************************************************************************", file=fh) vg = obj.vertex_groups.new( name=bone_name) # Create matching vertex groups vertex_groups[bone_id] = vg fh.close() bone_dump.close() bpy.ops.object.mode_set(mode='OBJECT') # do an implicit update # parent obj with rig, using Armaturre type parenting so Bones will deform vertices obj.parent = rig obj.parent_type = 'ARMATURE' print("********************************************************") print("BONE WEIGHTS") print("********************************************************") # Set Vertex bone weights for vidx, vtx in enumerate(skm_data.vertex_data): attachment_count = len(vtx.attachment_bones) for i in range(0, attachment_count): bone_id = vtx.attachment_bones[i] bone_wt = vtx.attachment_weights[i] vertex_groups[bone_id].add((vidx, ), bone_wt, 'ADD') # object_dictionary[rigObName] = obj # rig_dictionary[contextObName] = obj importedObjects.append(obj) def dump_bones(): bone_dump = open(os.path.join(ToEE_data_dir, 'bone_dump_skm.txt'), 'w') for skm_bone_id, bd in enumerate(skm_data.bone_data): bone_dump.write("\n\n******* %d %s ********** " % (skm_bone_id, str(bd.name)) + " Parent: (%d)" % (bd.parent_id)) bone_dump.write("\n") bone_dump.write("WorldInverse matrix: " + str(bd.world_inverse)) bone_dump.write("\n") bone_dump.close() contextObName = "ToEE Model" rigObName = "ToEE Rig" armatureName = "ToEE Model Skeleton" ## Create materials progress.step("Loading materials and images...") # create_materials(skm_data) for mm in skm_data.material_data: mdf_path = mm.id.name # path relative to data_dir (that's how ToEE rolls) material_name = mdf_path contextMaterial = bpy.data.materials.new( material_name) # material_name.rstrip() contextMaterial.use_nodes = True context_mat_wrap = node_shader_utils.PrincipledBSDFWrapper( contextMaterial, is_readonly=False) context_mat_wrap.use_nodes = True print("Registering material: %s" % material_name) MATDICT[material_name] = contextMaterial WRAPDICT[contextMaterial] = context_mat_wrap mdf_resolve(mdf_path) # Create Mesh object progress.step("Creating Mesh...") putContextMesh(skm_data) # Create Rig progress.step("Creating Rig...") putRig(skm_data) dump_bones() return
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 __init__(self, model, blender_index, index): self.blender_index = blender_index self.index = index material = bpy.data.materials[blender_index] self.name = material.name self.type = material.nns_mat_type self.light0 = 'on' if material.nns_light0 else 'off' self.light1 = 'on' if material.nns_light1 else 'off' self.light2 = 'on' if material.nns_light2 else 'off' self.light3 = 'on' if material.nns_light3 else 'off' self.shininess_table_flag = 'on' if material.nns_use_srst else 'off' self.fog_flag = 'on' if material.nns_fog else 'off' self.wire_mode = 'on' if material.nns_wireframe else 'off' self.depth_test_decal = 'on' if material.nns_depth_test else 'off' self.translucent_update_depth = ( 'on' if material.nns_update_depth_buffer else 'off') self.render_1_pixel = 'on' if material.nns_render_1_pixel else 'off' self.far_clipping = 'on' if material.nns_far_clipping else 'off' self.polygon_id = material.nns_polygonid self.face = material.nns_display_face self.polygon_mode = material.nns_polygon_mode self.tex_gen_mode = material.nns_tex_gen_mode self.tex_gen_st_src = material.nns_tex_gen_st_src self.tex_tiling_u = material.nns_tex_tiling_u self.tex_tiling_v = material.nns_tex_tiling_v row0 = material.nns_tex_effect_mtx_0 row1 = material.nns_tex_effect_mtx_1 row2 = material.nns_tex_effect_mtx_2 row3 = material.nns_tex_effect_mtx_3 matrix = f'{row0[0]} {row0[1]} 0.0 0.0 ' \ f'{row1[0]} {row1[1]} 0.0 0.0 ' \ f'{row2[0]} {row2[1]} 1.0 0.0 ' \ f'{row3[0]} {row3[1]} 0.0 1.0' self.tex_effect_mtx = matrix self.tex_scale = f'{material.nns_tex_scale[0]} ' \ f'{material.nns_tex_scale[1]}' self.tex_rotate = str(material.nns_tex_rotate) self.tex_translate = f'{material.nns_tex_translate[0]} ' \ f'{material.nns_tex_translate[1]}' self.image_idx = -1 self.palette_idx = -1 if material.is_nns: self.alpha = material.nns_alpha self.diffuse = ' '.join( [str(int(round(lin2s(x) * 31))) for x in material.nns_diffuse]) self.specular = ' '.join([ str(int(round(lin2s(x) * 31))) for x in material.nns_specular ]) self.ambient = ' '.join( [str(int(round(lin2s(x) * 31))) for x in material.nns_ambient]) self.emission = ' '.join([ str(int(round(lin2s(x) * 31))) for x in material.nns_emission ]) if material.nns_image is not None \ and "tx" in material.nns_mat_type: filepath = material.nns_image.filepath path = os.path.realpath(bpy.path.abspath(filepath)) _, extension = os.path.splitext(path) if extension == '.tga': texture = model.find_texture(path) self.image_idx = texture.index self.palette_idx = texture.palette_idx else: # For now let's use PrincipledBSDF to get the color and image. wrap = node_shader_utils.PrincipledBSDFWrapper(material) self.alpha = int(wrap.alpha * 31) self.diffuse = ' '.join([ str(int(round(lin2s(wrap.base_color[0]) * 31))), str(int(round(lin2s(wrap.base_color[1]) * 31))), str(int(round(lin2s(wrap.base_color[2]) * 31))) ]) self.specular = ' '.join( [str(int(round(wrap.specular * 31))) for _ in range(3)]) self.ambient = '31 31 31' self.emission = '0 0 0' tex_wrap = getattr(wrap, 'base_color_texture', None) if tex_wrap is not None and tex_wrap.image is not None: path = os.path.realpath( bpy.path.abspath(tex_wrap.image.filepath, library=tex_wrap.image.library)) _, extension = os.path.splitext(path) if extension == '.tga': texture = model.find_texture(path) self.image_idx = texture.index self.palette_idx = texture.palette_idx