Automatic atlas baking

This commit is contained in:
2026-02-12 00:03:18 +03:00
parent bf25ef1a80
commit 7947690e80
5 changed files with 139 additions and 8 deletions

View File

@@ -18,14 +18,20 @@ endforeach()
foreach(FURNITURE_FILE ${FURNITURE_FILES})
get_filename_component(FILE_NAME ${FURNITURE_FILE} NAME_WE)
set(PARTS_OUTPUT_DIR ${CMAKE_BINARY_DIR}/resources/buildings/parts/${FILE_NAME})
add_custom_command(
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}_baked.blend
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${FURNITURE_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${FURNITURE_FILE}
COMMAND ${BLENDER} -b ${CMAKE_CURRENT_BINARY_DIR}/${FURNITURE_FILE} -Y -P
${CMAKE_CURRENT_SOURCE_DIR}/bake_furniture.py
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${FURNITURE_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/bake_furniture.py)
add_custom_command(
OUTPUT ${PARTS_OUTPUT_DIR}
COMMAND ${CMAKE_COMMAND} -E make_directory ${PARTS_OUTPUT_DIR}
COMMAND ${BLENDER} ${CMAKE_CURRENT_SOURCE_DIR}/${FURNITURE_FILE}
-b -Y -P
COMMAND ${BLENDER} -b ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}_baked.blend
-Y -P
${CMAKE_CURRENT_SOURCE_DIR}/export_furniture_parts.py
-- ${PARTS_OUTPUT_DIR}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${PARTS_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/export_furniture_parts.py)
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}_baked.blend ${CMAKE_CURRENT_SOURCE_DIR}/export_furniture_parts.py)
list(APPEND PARTS_OUTPUT_DIRS ${PARTS_OUTPUT_DIR})
endforeach()
add_custom_target(import_building_parts ALL DEPENDS ${PARTS_OUTPUT_DIRS})

View File

@@ -0,0 +1,125 @@
import bpy
def get_all_children_recursive(obj, mesh_list):
"""Recursively finds all mesh objects in the hierarchy."""
for child in obj.children:
if child.type == 'MESH' and child.data.uv_layers:
mesh_list.append(child)
get_all_children_recursive(child, mesh_list)
def setup_furniture_atlas():
# 1. Gather valid objects: Children of empties starting with "furniture-"
furniture_meshes = []
roots = [o for o in bpy.data.objects if o.name.startswith("furniture-")]
for root in roots:
# If the root itself is a mesh with UVs, add it
if root.type == 'MESH' and root.data.uv_layers:
furniture_meshes.append(root)
# Find all nested children
get_all_children_recursive(root, furniture_meshes)
# Remove duplicates if any (in case of complex parenting)
furniture_meshes = list(set(furniture_meshes))
if not furniture_meshes:
print("No recursive mesh objects found with UVs.")
return
furniture_objs = furniture_meshes
if not furniture_objs:
print("No valid furniture mesh objects found with UVs.")
return
# 2. Manage the Atlas Image (FurnitureColor)
image_name = "FurnitureColor"
if image_name in bpy.data.images:
atlas_img = bpy.data.images[image_name]
else:
atlas_img = bpy.data.images.new(image_name, width=2048, height=2048)
# 3. Setup Materials & Nodes
processed_mats = set()
for obj in furniture_objs:
for slot in obj.material_slots:
mat = slot.material
if mat and mat not in processed_mats:
mat.use_nodes = True
nodes = mat.node_tree.nodes
# Find or create the target node
bake_node = nodes.get("ATLAS_TARGET")
if not bake_node:
bake_node = nodes.new('ShaderNodeTexImage')
bake_node.name = "ATLAS_TARGET"
bake_node.image = atlas_img
nodes.active = bake_node # Essential for the Bake operator
processed_mats.add(mat)
# 4. Selection & Context Setup
bpy.ops.object.select_all(action='DESELECT')
for obj in furniture_objs:
obj.select_set(True)
bpy.context.view_layer.objects.active = furniture_objs[0]
# 5. Bake Configuration (Cycles, Diffuse, Color only)
scene = bpy.context.scene
scene.render.engine = 'CYCLES'
scene.cycles.device = 'CPU'
scene.cycles.samples = 10
scene.render.bake.use_pass_direct = False
scene.render.bake.use_pass_indirect = False
scene.render.bake.use_pass_color = True
scene.render.bake.target = 'IMAGE_TEXTURES'
print("Baking Furniture Atlas...")
override = {
'active_object': bpy.context.view_layer.objects.active,
'selected_objects': bpy.context.selected_objects,
}
with bpy.context.temp_override(**override):
bpy.ops.object.bake(type='DIFFUSE', margin=2)
atlas_img.pack()
# 6. Create and Assign Final Atlas Material
atlas_mat_name = "M_Furniture_Atlas"
if atlas_mat_name in bpy.data.materials:
atlas_mat = bpy.data.materials[atlas_mat_name]
else:
atlas_mat = bpy.data.materials.new(name=atlas_mat_name)
atlas_mat.use_nodes = True
bsdf = atlas_mat.node_tree.nodes.get("Principled BSDF")
tex_node = atlas_mat.node_tree.nodes.new('ShaderNodeTexImage')
tex_node.image = atlas_img
atlas_mat.node_tree.links.new(tex_node.outputs['Color'], bsdf.inputs['Base Color'])
nodes = atlas_mat.node_tree.nodes
bsdf = nodes.get("Principled BSDF")
if bsdf:
bsdf.inputs['Roughness'].default_value = 1.0
# Reassign all valid objects to the new material
for obj in furniture_objs:
obj.data.materials.clear()
obj.data.materials.append(atlas_mat)
print("Process Complete. Atlas material applied.")
for mat in bpy.data.materials:
if mat.users == 0:
bpy.data.materials.remove(mat)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
scene.render.engine = 'BLENDER_EEVEE'
current_path = bpy.data.filepath
if current_path:
new_path = current_path.replace(".blend", "_baked.blend")
bpy.ops.wm.save_as_mainfile(filepath=new_path)
print(f"File saved to: {new_path}")
else:
print("Warning: File not saved (unsaved blend file).")
setup_furniture_atlas()

View File

@@ -81,7 +81,7 @@ class TEST_PT_ObjectPanel(bpy.types.Panel):
# Link button with preset arguments
row = layout.row(align=True)
op = row.operator("test.link_and_play", text="Link & Sit", icon='PLAY')
op.filepath = "//../../edited-normal-male.blend" # SET YOUR FILEPATH
op.filepath = "//../../characters/edited-normal-male.blend" # SET YOUR FILEPATH
op.rig_name = "male" # SET YOUR RIG NAME
op.action_name = "sitting-chair" # SET YOUR ACTION NAME