0
$\begingroup$

I am running baking operation for blender through python script but its taking a lot of time due to update image operations running for same mesh. (total time 23 mins)

I need to optimize the process as same scene takes <5mins in the GUI to bake.

The thing is I run blender in background because I don't need any GUI.

Here is the snippet which helps me prepare mesh and bake.

def process_meshes_for_baking(output_dir, args):
    """
    Process and bake each mesh individually, without using collections.
    Uses batched operations to minimize synchronization issues.
    
    Args:
        output_dir: Directory where the output files will be saved
        args: Arguments from parse_args() function
    
    Returns:
        bool: True if successful
    """
    all_meshes = [obj for obj in bpy.context.scene.objects if obj.type == 'MESH' and obj.visible_get() and not obj.hide_render]
    
    print(f"\n=== Found {len(all_meshes)} meshes to process ===")
    
    metallic_materials_exist = handle_metallic_materials(output_dir)
    
    textures_dir = os.path.join(output_dir, "textures")
    if not os.path.exists(textures_dir):
        os.makedirs(textures_dir)
    
    prepared_meshes = []
    
    for mesh in all_meshes:
        print(f"\n=== Preparing Mesh: {mesh.name} ===")
        
        if mesh.get("skipped_lightmap"):
            print(f"  Skipping {mesh.name} - marked to skip: {mesh.get('skip_reason', 'unknown')}")
            continue
            
        is_glass = False
        for slot in mesh.material_slots:
            if slot.material and is_glass_material(slot.material):
                is_glass = True
                break
                
        if is_glass:
            print(f"  Skipping glass object: {mesh.name}")
            continue
        
        irradiance_img = prepare_mesh_revised_batch(mesh, output_dir, args)
        if not irradiance_img:
            print(f"Failed to prepare mesh: {mesh.name}")
            continue
            
        prepared_meshes.append((mesh, irradiance_img))
    
    print(f"\n=== Batch assigning images to {len(prepared_meshes)} meshes ===")
    batch_assign_images_to_materials(prepared_meshes)
    
    baked_blend_path = os.path.join(output_dir, "after_prepare_mesh_revised.blend")
    save_blender_file(baked_blend_path)
    print(f"Saved prepared state to {baked_blend_path}")
    
    print(f"\n=== Batch baking {len(prepared_meshes)} meshes ===")
    batch_bake_meshes(prepared_meshes, output_dir, args)
    
    connect_material_nodes_after_baking_revised()
    
    baked_blend_path = os.path.join(output_dir, "reconnected.blend")
    save_blender_file(baked_blend_path)
    
    restore_metallic_materials(output_dir)
    
    switch_off_all_lights(debug=False)
    
    return True


def batch_assign_images_to_materials(prepared_meshes):
    """
    Batch assign images to all material nodes to avoid synchronization issues.
    
    Args:
        prepared_meshes: List of (mesh, irradiance_img) tuples
    """
    print("  Batch assigning images to avoid sync issues...")
    
    for mesh, irradiance_img in prepared_meshes:
        for slot in mesh.material_slots:
            if not slot.material:
                continue
                
            mat = slot.material
            if not mat.use_nodes:
                continue
                
            nodes = mat.node_tree.nodes
            
            for node in nodes:
                if node.type == 'TEX_IMAGE' and ('Irradiance' in node.name or 'Irradiance' in node.label):
                    node.image = irradiance_img
                    print(f"    Assigned image to {node.name} in {mat.name}")


def batch_bake_meshes(prepared_meshes, output_dir, args):
    """
    Batch bake all meshes to minimize synchronization overhead.
    
    Args:
        prepared_meshes: List of (mesh, irradiance_img) tuples
        output_dir: Output directory
        args: Command line arguments
    """
    print("  Starting batch baking process...")
    
    bpy.ops.object.select_all(action='DESELECT')
    for mesh, _ in prepared_meshes:
        mesh.select_set(True)
    
    if prepared_meshes:
        bpy.context.view_layer.objects.active = prepared_meshes[0][0]
    
    meshes = [mesh for mesh, _ in prepared_meshes]
    select_texture_nodes_for_baking(meshes, texture_type='irradiance')
    
    print("  Batch baking irradiance textures...")
    successful_bakes = []
    i = 0
    for mesh, irradiance_img in prepared_meshes:
        print(f"    Baking irradiance for {mesh.name}")
        
        bpy.ops.object.select_all(action='DESELECT')
        mesh.select_set(True)
        bpy.context.view_layer.objects.active = mesh
        
        irradiance_success = run_cycles_bake(
            bake_type='IRRADIANCE',
            samples=int(args.samples),
            margin=int(args.margin_px),
            use_denoising=True,
            MAX_BOUNCES=int(args.max_bounces),
            DIFFUSE_BOUNCES=int(args.diffuse_bounces),
            output_dir=output_dir,
            image=irradiance_img
        )
        

        if irradiance_success:
            save_texture(irradiance_img, irradiance_img.name, os.path.join(output_dir, "textures"), ['PNG'])
            print(f"    ✅ Saved irradiance texture for {mesh.name}")
            
            save_texture(irradiance_img, irradiance_img.name, os.path.join(output_dir, "textures"), ['HDR'])
            print(f"    ✅ Saved HDR irradiance texture for {mesh.name}")
            
            successful_bakes.append(irradiance_img)
        else:
            print(f"    ❌ Irradiance baking failed for {mesh.name}")

    
    if successful_bakes:
        print(f"  Batch updating {len(successful_bakes)} images...")
        batch_update_images(successful_bakes)
        
        print(f"  Batch denoising {len(successful_bakes)} textures...")
        batch_denoise_textures(successful_bakes, output_dir)
    
    print("  Batch baking complete")



def run_cycles_bake(bake_type, samples, margin=4, use_denoising=True, debug=True, MAX_BOUNCES=12, DIFFUSE_BOUNCES=12, output_dir=None, image=None):
    """
    Runs Cycles baking process for objects with active texture nodes.
    
    Args:
        bake_type (str): Type of bake to perform ('ATLAS' for diffuse color, 'IRRADIANCE' for lighting)
        samples (int): Number of render samples for baking
        margin (int): Bake margin in pixels
        use_denoising (bool): Enable denoising for baking
        debug (bool): Whether to print debug information
        MAX_BOUNCES (int): Maximum light bounces
        DIFFUSE_BOUNCES (int): Maximum diffuse bounces
        output_dir (str): Directory to save baked textures
        image (bpy.types.Image): The image to bake into and save
    
    Returns:
        bool: True if baking completed successfully
    """
    start_time = time.time()
    
    if debug:
        print(f"\n=== Starting Cycles Bake: {bake_type} ===")
        
    if hasattr(bpy.context.preferences.addons['cycles'], 'preferences'):
        cycles_prefs = bpy.context.preferences.addons['cycles'].preferences
        if hasattr(cycles_prefs, 'compute_device_type'):
            cycles_prefs.compute_device_type = 'OPTIX'
    
    bpy.context.scene.render.engine = 'CYCLES'
    bpy.context.scene.cycles.device = 'GPU'
    bpy.context.scene.cycles.samples = samples
    
    bpy.context.view_layer.cycles.denoising_store_passes = True
    bpy.context.scene.cycles.use_preview_denoising = False
    bpy.context.scene.cycles.use_denoising = False
    
    bpy.context.scene.cycles.caustics_reflective = False
    bpy.context.scene.cycles.caustics_refractive = False
    
    
    bpy.context.scene.render.bake.use_clear = False 
    bpy.context.scene.render.bake.margin = margin
    bpy.context.scene.render.bake.margin_type = 'ADJACENT_FACES'
    bpy.context.scene.cycles.max_bounces = MAX_BOUNCES       # Increased from 12
    bpy.context.scene.cycles.diffuse_bounces = DIFFUSE_BOUNCES   # Increased from 12
    bpy.context.scene.cycles.glossy_bounces = 4
    bpy.context.scene.cycles.transmission_bounces = 1
    bpy.context.scene.cycles.transparent_max_bounces = 8
    bpy.context.scene.cycles.filter_width = 3
    
    if bake_type == 'ATLAS':
        bpy.context.scene.render.bake.use_pass_direct = False
        bpy.context.scene.render.bake.use_pass_indirect = False
        bpy.context.scene.render.bake.use_pass_color = True
        
    elif bake_type == 'IRRADIANCE':
        bpy.context.scene.render.bake.use_pass_direct = True
        bpy.context.scene.render.bake.use_pass_indirect = True        
        bpy.context.scene.render.bake.use_pass_color = False
    
    bpy.context.scene.render.bake.use_selected_to_active = False
    
    selected_objects = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH']
    
    if debug:
        print(f"Found {len(selected_objects)} selected mesh objects for baking")
        
        print(f"Bake settings for {bake_type}:")
        print(f"  - use_pass_direct: {bpy.context.scene.render.bake.use_pass_direct}")
        print(f"  - use_pass_indirect: {bpy.context.scene.render.bake.use_pass_indirect}")
        print(f"  - use_pass_color: {bpy.context.scene.render.bake.use_pass_color}")
        print(f"  - bounce settings: max={bpy.context.scene.cycles.max_bounces}, diffuse={bpy.context.scene.cycles.diffuse_bounces}")
    
    if not selected_objects:
        print("Error: No objects selected for baking.")
        return False
    
    try:
        
        if image and output_dir:
            if bake_type == 'ATLAS':
                filepath = os.path.join(output_dir, f"{bake_type}_Texture.png")
                image.filepath_raw = filepath
                image.colorspace_settings.name = 'sRGB'
                if debug:
                    print(f"Set target filepath: {filepath}")
                    
            elif bake_type == 'IRRADIANCE':
                filepath = os.path.join(output_dir, f"{bake_type}_Texture.exr")
                image.filepath_raw = filepath
                image.colorspace_settings.name = 'Linear Rec.709'
                if debug:
                    print(f"Set target filepath: {filepath}")

        if debug:
            print(f"Starting DIFFUSE bake with {samples} samples and {margin}px margin...")
        
        bpy.ops.object.bake(type='DIFFUSE', save_mode='EXTERNAL')
        
        
       
        time.sleep(1)

        if image:
            if not image.packed_file:
                image.pack()
            
            if output_dir:
                if debug:
                    print(f"Saving {bake_type} texture immediately after baking...")
                
                if bake_type == 'ATLAS':
                    bpy.context.scene.render.image_settings.file_format = 'PNG'
                    bpy.context.scene.render.image_settings.color_depth = '16'
                    bpy.context.scene.render.image_settings.compression = 15
                    
                    filepath = os.path.join(output_dir, f"{bake_type}_Texture.png")
                    image.filepath_raw = filepath
                    image.colorspace_settings.name = 'sRGB'
                    
                    try:
                        image.save_render(filepath, scene=bpy.context.scene)
                        print(f"✅ Saved {bake_type} texture to {filepath}")
                    except Exception as e:
                        print(f"❌ Failed to save {bake_type} texture: {e}")
                
                elif bake_type == 'IRRADIANCE':
                    bpy.context.scene.render.image_settings.file_format = 'PNG'
                    filepath_png = os.path.join(output_dir, f"{bake_type}_Texture.png")
                    image.filepath_raw = filepath_png
                    image.colorspace_settings.name = 'Linear Rec.709'
                    
                    try:
                        image.save_render(filepath_png, scene=bpy.context.scene)
                        print(f"✅ Saved {bake_type} PNG texture to {filepath_png}")
                    except Exception as e:
                        print(f"❌ Failed to save {bake_type} PNG texture: {e}")
                    
                    bpy.context.scene.render.image_settings.file_format = 'OPEN_EXR'
                    bpy.context.scene.render.image_settings.color_depth = '32'
                    filepath_exr = os.path.join(output_dir, f"{bake_type}_Texture.exr")
                    image.filepath_raw = filepath_exr
                    
                    try:
                        image.save_render(filepath_exr, scene=bpy.context.scene)
                        print(f"✅ Saved {bake_type} EXR texture to {filepath_exr}")
                    except Exception as e:
                        print(f"❌ Failed to save {bake_type} EXR texture: {e}")

        
        if debug:
            print(f"Baking completed successfully in {time.time() - start_time:.2f} seconds")
        
        return True
        
    except Exception as e:
        print(f"Error during baking: {e}")
        return False
        
    finally:
        
        if debug:
            print(f"\n=== Baking process finished ===")
            print(f"Total time: {time.time() - start_time:.2f} seconds")

i believe this is the part causing unecessary syncronization and updating logs

for node in nodes:
                if node.type == 'TEX_IMAGE' and ('Irradiance' in node.name or 'Irradiance' in node.label):
                    node.image = irradiance_img
                    print(f"    Assigned image to {node.name} in {mat.name}")

here is part of the logs that gets repeated enter image description here

$\endgroup$

1 Answer 1

0
$\begingroup$

It restarts the baking for each object, because it bakes it per-object onto the same image. You need to join the objects.

$\endgroup$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.