我在搞一个网格轻量化的流程,现在卡到用blender为简化后的网格重新烘焙颜色贴图的这个步骤上了,有ABC个文件夹,我想把A文件夹中的原网格的纹理烘焙到重新展UV,然后清理材质之后的B文件夹中的目标网格上,然后输出到输出文件夹C中;A文件夹中的网格是ContextCapture Center Master输出的带颜色贴图的OBJ文件。B文件夹中的网格是通过MeshLab简化过的OBJ文件,但是我目前无法正常处理B文件夹中有多个材质球的网格的情况,而且我python算瞎折腾,学的也不怎么明白,求指点或者麻烦帮忙改改
import bpy
import os
class BakePanel(bpy.types.Panel):
bl_label = "Bake Panel"
bl_idname = "OBJECT_PT_bake_panel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "橙_重烘焙"
def draw(self, context):
layout = self.layout
layout.label(text="输入路径 A(原网格):")
layout.prop(context.scene, "path_a")
layout.label(text="输入路径 B(目标网格):")
layout.prop(context.scene, "path_b")
layout.label(text="输出路径:")
layout.prop(context.scene, "output_path")
layout.operator("object.bake_meshes", text="开始烘焙")
class BakeMeshesOperator(bpy.types.Operator):
bl_idname = "object.bake_meshes"
bl_label = "Bake Meshes"
def execute(self, context):
path_a = bpy.context.scene.path_a
path_b = bpy.context.scene.path_b
output_path = bpy.context.scene.output_path
log_path = os.path.join(output_path, "skipped_files.log") # 日志文件路径
skipped_files = []
def get_matching_file(base_name):
target_name = base_name.replace(".obj", "_out.obj")
for file in os.listdir(path_b):
if file == target_name:
return os.path.join(path_b, file)
return None
def clear_scene():
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
bpy.ops.outliner.orphans_purge() # 清空缓存
clear_scene() # 先清理场景
def bake_and_export(base_name, target_file):
clear_scene() # 清空场景
# 导入原网格和目标网格
obj_path = os.path.join(path_a, base_name)
bpy.ops.wm.obj_import(filepath=obj_path)
obj_path = target_file
bpy.ops.wm.obj_import(filepath=obj_path)
original_mesh = bpy.data.objects[base_name.replace(".obj", "")]
target_mesh = bpy.data.objects[base_name.replace(".obj", "_out")]
# 将原网格和目标网格的旋转值改为 0
original_mesh.rotation_euler = (0, 0, 0)
target_mesh.rotation_euler = (0, 0, 0)
# 切换到编辑模式并选中目标网格的所有面
bpy.context.view_layer.objects.active = target_mesh
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
# 自动展 UV
bpy.ops.uv.smart_project(
# 角度限制,角度越小,投影组越多,失真越小;角度越大,投影组越少,失真越大
angle_limit=1.15192,
# 边距方法
# SCALED – 使用现有 UV 的缩放比例来乘以边距。
# ADD – 只添加边距,忽略任何 UV 缩放。
# FRACTION – 指定最终 UV 输出的精确分数边距。
margin_method='SCALED',
# 旋转方法
# AXIS_ALIGNED – 旋转到最小矩形,可以是垂直或水平。
# AXIS_ALIGNED_X – 水平对齐岛屿。
# AXIS_ALIGNED_Y – 垂直对齐岛屿。
rotate_method='AXIS_ALIGNED_Y',
# 岛屿边距,相邻岛屿之间的边距
island_margin=0.0,
# 面积权重,按面的面积加权投影向量
area_weight=0.0,
# 修正纵横比,根据图像纵横比映射 UV
correct_aspect=True,
# 缩放到边界,展开后缩放 UV 坐标到边界
scale_to_bounds=False
)
bpy.ops.object.mode_set(mode='OBJECT') # 回到物体模式
# 切换到 UV 编辑模式
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.uv.select_all(action='SELECT') # 全选 UV 编辑器中的面
# 对 UV 进行拼排孤岛操作
bpy.ops.uv.pack_islands(
udim_source='CLOSEST_UDIM',
rotate=True, rotate_method='ANY',
scale=True, merge_overlap=False,
margin_method='SCALED',
margin=0.001,
pin=False,
pin_method='LOCKED',
shape_method='CONCAVE'
)
bpy.ops.object.mode_set(mode='OBJECT') # 回到物体模式
# 确保两个对象都存在
if original_mesh and target_mesh:
# 将原网格设置为选中对象,目标网格设置为活动对象
original_mesh.select_set(True)
bpy.context.view_layer.objects.active = target_mesh
# 定义 bake_settings
bake_settings = bpy.context.scene.render.bake
# 烘焙纹理贴图
bpy.context.scene.render.engine = 'CYCLES'
bake_settings.use_pass_direct = False
bake_settings.use_pass_indirect = False
bake_settings.use_pass_color = True
bake_settings.width = 2048
bake_settings.height = 2048
bake_settings.use_selected_to_active = True
bake_settings.use_clear = True
# 设置 cage_extrusion 为 0.1
bake_settings.cage_extrusion = 0.1
# 确保使用 selected_to_active 模式进行正确烘焙
bpy.ops.object.bake(type='DIFFUSE')
# 查找生成的纹理
baked_image = None
if target_mesh.data.materials:
material = target_mesh.data.materials[0]
if material.use_nodes:
node_tree = material.node_tree
bsdf_node = node_tree.nodes.get('Principled BSDF')
if bsdf_node and bsdf_node.inputs['Base Color'].is_linked:
texture_node = bsdf_node.inputs['Base Color'].links[0].from_node
if texture_node.type == 'TEX_IMAGE':
baked_image = texture_node.image
if baked_image:
# 生成保存纹理的文件名
texture_filename = base_name.replace(".obj", "_baked_texture.png")
texture_path = os.path.join(output_path, texture_filename)
# 保存纹理
baked_image.save_render(texture_path)
# 取消原网格的选择,只保留目标网格的选择
original_mesh.select_set(False)
target_mesh.select_set(True)
bpy.context.view_layer.objects.active = target_mesh
# 保存目标网格为 OBJ 文件,并生成 MTL 文件
obj_output_path = os.path.join(output_path, base_name.replace(".obj", "_baked.obj") )
bpy.ops.wm.obj_export(
filepath=obj_output_path,
export_animation=False,
start_frame=-2147483648,
end_frame=2147483647,
forward_axis='NEGATIVE_Z',
up_axis='Y',
global_scale=1.0,
apply_modifiers=True,
export_eval_mode='DAG_EVAL_VIEWPORT',
export_selected_objects=True, # 仅导出选中对象
export_uv=True,
export_normals=True,
export_colors=False,
export_materials=True,
export_pbr_extensions=False,
path_mode='AUTO',
export_triangulated_mesh=False,
export_curves_as_nurbs=False,
export_object_groups=False,
export_material_groups=False,
export_vertex_groups=False,
export_smooth_groups=False,
smooth_group_bitflags=False,
filter_glob='*.obj;*.mtl',
collection=''
)
clear_scene() # 在每一对模型完成处理后清空场景
for file in os.listdir(path_a):
if file.endswith(".obj"):
target_file = get_matching_file(file)
if target_file:
bake_and_export(file, target_file)
else:
skipped_files.append(file)
if skipped_files:
with open(log_path, 'a') as log_file: # 以追加模式打开日志文件
for skipped_file in skipped_files:
log_file.write(skipped_file + "\n")
# 添加绿色提示信息
# 确保提示信息显示在状态栏中
self.report({'INFO'}, "处理完成")
return {'FINISHED'}
def register():
bpy.utils.register_class(BakePanel)
bpy.utils.register_class(BakeMeshesOperator)
bpy.types.Scene.path_a = bpy.props.StringProperty(name="Path A", subtype='DIR_PATH')
bpy.types.Scene.path_b = bpy.props.StringProperty(name="Path B", subtype='DIR_PATH')
bpy.types.Scene.output_path = bpy.props.StringProperty(name="Output Path", subtype='DIR_PATH')
def unregister():
bpy.utils.unregister_class(BakePanel)
bpy.utils.unregister_class(BakeMeshesOperator)
del bpy.types.Scene.path_a
del bpy.types.Scene.path_b
del bpy.types.Scene.output_path
if __name__ == "__main__":
register()