Extension - API Operators

Documentation for operators designed for scripting and automation within the Ultibake extension.

Last Updated: Thursday, April 3, 2025

Using Operators in Scripts (Blender Workflow)

Blender often allows you to discover the Python command associated with an action by right-clicking on a UI element (like a button or menu item) and selecting an option like "Copy Python Command" or "Edit Source". This is a great way to learn how Blender performs operations internally. Important: To ensure options like "Edit Source" or potentially "Copy Python Command" are visible when right-clicking UI elements, make sure "Developer Extras" is enabled in Blender's Preferences (Edit -> Preferences -> Interface -> Display section).

However, directly using these copied commands in your own scripts might not always work as expected. Some operators rely heavily on the specific context they are run from (e.g., mouse position, active window type), require complex internal setup, or operate 'modally' (taking over user input temporarily). These factors can make the raw, copied command unsuitable for reliable execution within a script.

The API operators documented on this page (like api.run_ultibake below) are specifically designed as script-friendly wrappers. They handle the necessary setup or context internally, providing a stable interface that you can reliably call from your Python scripts using bpy.ops.[idname](...).

Therefore, if an Ultibake operation needs to be scripted and has a corresponding API wrapper listed here, you should use the documented API wrapper. If a specific Ultibake operator or function is *not* documented here as requiring an API wrapper, you can generally assume that the command copied directly from the Blender UI is suitable for direct use in scripts. Read the sections below for details on how to modify Bake Set parameters or manage sets/units before running an operator.

Audience & Scope: This documentation details specific API operators intended for advanced users who wish to automate Ultibake workflows using Python scripting.

For information on standard extension usage, features, and general setup, please refer to the main product page: blendermarket.com/products/ultibake.

These wrapped API operators are created only when necessary to provide a stable scripting interface for complex operations where Blender's raw operators might be unreliable in scripts. More API wrappers may be added in future updates as needed.

For additional help, questions, or community support regarding Ultibake, please join our Discord channel: discord.com/invite/Pm7TDf2rV5.

API Operators

This section lists the available script-friendly API operators provided by Ultibake.

Run Ultibake (API)

Executes the main background baking process provided by the Ultibake extension. This is a wrapper around the core baking logic (`sna.bake_sets_118ea`). Call this operator *after* configuring Bake Set parameters as needed (see Parameter Assignment section below). For preparing meshes for export after baking, see the Finalize operator in the next section.

Operator ID (bl_idname): api.run_ultibake

Parameters

Parameter Name Data Type Description Default Value
bake_set_index Integer Specifies which Bake Set(s) to process. A value of -1 (the default) will process all available Bake Sets defined in the scene. An integer value of 0 or greater specifies the index of a single Bake Set to process from the bpy.context.scene.sna_objectsets collection. -1

Usage Example (Python)


# Basic execution examples (assuming parameters are already set)
import bpy

# Example 1: Run Ultibake for ALL Bake Sets (using default index -1)
try:
    bpy.ops.api.run_ultibake()
    print("Ultibake bake process initiated for ALL Bake Sets.")
except Exception as e:
    print(f"Error running Ultibake: {e}")

# Example 2: Run Ultibake for the FIRST Bake Set (index 0)
# try:
#    bpy.ops.api.run_ultibake(bake_set_index=0)
#    print("Ultibake bake process initiated for Bake Set index 0.")
# except Exception as e:
#    print(f"Error running Ultibake: {e}")
                

Return Value

When called via `bpy.ops`, this operator immediately returns {'FINISHED'} if it successfully initiated the underlying Ultibake process. Note that the actual baking happens in the background (modal operation) and may take time to complete. The return value only indicates successful *initiation*. If the operator cannot start (e.g., due to polling conditions or if the target operator isn't registered), it will return {'CANCELLED'}.

Parameter Assignment via Script

Beyond simply running the API operators, you often need to configure the Bake Sets and Bake Units programmatically before execution. Blender provides a helpful way to find the specific Python data paths for these settings.

By right-clicking on a parameter field in the Ultibake UI (like a UV map selection dropdown, an object input field, a checkbox, or a color swatch) and choosing "Copy Full Data Path" (make sure Developer Extras are enabled), you can get the exact Python expression needed to access that property. This typically looks something like bpy.context.scene.sna_objectsets[0].bakeunit[0].activeuvname.

The examples below demonstrate how to use these data paths to modify common Bake Set and Bake Unit settings before calling an operator like api.run_ultibake. Remember that Ultibake settings are stored per Scene.

Setting the Target UV Map

Each Bake Unit targets a specific UV Map on the low-poly object for baking. You can change this target UV map name:


import bpy
scene = bpy.context.scene # Assuming context scene is correct one

# Check if Bake Sets exist and access the first one (index 0)
if scene.sna_objectsets:
    bake_set = scene.sna_objectsets[0]
    # Check if Bake Units exist and access the first one (index 0)
    if bake_set.bakeunit:
        bake_unit = bake_set.bakeunit[0]
        # Set the active UV map name (replace with your actual UV map name)
        bake_unit.activeuvname = "TARGET_UVMAP_NAME"
        print(f"Set active UV map for BU 0 in BS 0 to: {bake_unit.activeuvname}")
            

Assigning Objects & Decals Collection

Assign specific Blender objects or collections to the different roles within a Bake Unit:


# Assuming bake_unit is defined as above (scene.sna_objectsets[0].bakeunit[0])

# Assign Low Poly object
if 'Suzanne_LP' in bpy.data.objects:
    bake_unit.low = bpy.data.objects['Suzanne_LP']
    print(f"Assigned low poly: {bake_unit.low.name}")

# Assign High Poly object
if 'Suzanne_HP' in bpy.data.objects:
    bake_unit.high = bpy.data.objects['Suzanne_HP']
    print(f"Assigned high poly: {bake_unit.high.name}")

# Assign Cage object
if 'Suzanne_Cage' in bpy.data.objects:
    bake_unit.cage = bpy.data.objects['Suzanne_Cage']
    print(f"Assigned cage: {bake_unit.cage.name}")

# Assign Decals Collection
if 'MyDecals' in bpy.data.collections:
    bake_unit.decals = bpy.data.collections['MyDecals']
    print(f"Assigned decals collection: {bake_unit.decals.name}")
            

Setting Color ID

Set the RGB color (values 0.0 to 1.0) used by the Color ID bake type:


# Assuming bake_unit is defined as above

# Set Color ID to pure Red
bake_unit.color = (1.0, 0.0, 0.0)
print(f"Set Color ID to: {bake_unit.color[:]}")
            

Setting the Active Profile

Select the Profile (which defines active Bake Types) by its name before baking:


# Assuming bake_set is defined as above (scene.sna_objectsets[0])

# Replace "My_ColorID_Profile" with the exact name of your profile
profile_name = "My_ColorID_Profile"
# Optional: Check if profile exists in the specific bake_set if needed
# bake_set.profiles is likely where profiles are stored per set
bake_set.currentprofile = profile_name
print(f"Set current profile for BS 0 to: {bake_set.currentprofile}")

            

Tip: It's often easier to configure complex profiles using the UI once, save the .blend file, and then load that file for automated baking, especially for headless operation.

Using Collection Mode for Low Polys

Bake all objects within a collection as low-polys (High, Cage, Decals cannot be assigned per-object in this mode):


# Assuming bake_unit is defined as above

# Assign the collection containing low poly objects
low_poly_collection_name = 'MyLowPolyObjects'
if low_poly_collection_name in bpy.data.collections:
    bake_unit.lowcollection = bpy.data.collections[low_poly_collection_name]
    bake_unit.usecollection = True # Enable collection mode AFTER assigning
    print(f"Enabled collection mode using collection: {bake_unit.lowcollection.name}")
else:
    bake_unit.usecollection = False # Ensure it's disabled if collection missing
    print(f"Warning: Collection '{low_poly_collection_name}' not found. Collection mode disabled.")
            

Managing Bake Sets & Units

You can also create and delete Bake Sets and Bake Units directly via script using specific operators.

Creating a Bake Set

This operator adds a new Bake Set to the current scene's sna_objectsets collection. Remember that Ultibake settings, including Bake Sets, are stored per scene.


import bpy

# Operator to add a new Bake Set
bpy.ops.sna.make_a_bake_set_c7c5f()

# The new Bake Set will be the last one in the list
new_bake_set_index = len(bpy.context.scene.sna_objectsets) - 1
print(f"Created a new Bake Set at index: {new_bake_set_index}")
            

Deleting a Bake Set

This operator removes a Bake Set based on its index within the sna_objectsets collection.


import bpy

scene = bpy.context.scene
bake_set_index_to_delete = -1 # Default to invalid

# Example: Delete the 4th set (index 3) if it exists
target_index = 3
if target_index < len(scene.sna_objectsets):
    bake_set_index_to_delete = target_index
    print(f"Attempting to delete Bake Set at index: {bake_set_index_to_delete}")
    # Note: Parameter name 'sna_objectsetindex' as provided
    bpy.ops.sna.delete_this_set_547c4(sna_objectsetindex=bake_set_index_to_delete)
else:
    print(f"Bake Set index {target_index} does not exist, cannot delete.")

             

Creating a Bake Unit

This adds a new Bake Unit to the currently active Bake Set. The active Bake Set is determined by the scene's active_bakeset property (which seems to store the index).


import bpy

scene_data = bpy.data.scenes["Scene"] # Accessing scene data directly as per user example

# Example: Set the second Bake Set (index 1) as active
target_bake_set_index = 1
if target_bake_set_index < len(scene_data.sna_objectsets):
     # Assuming active_bakeset stores the index
     scene_data.active_bakeset = target_bake_set_index
     print(f"Set active Bake Set index to: {scene_data.active_bakeset}")

     # Operator to add a Bake Unit to the active Bake Set
     bpy.ops.sna.add_bakeunit_item()

     new_bake_unit_index = len(scene_data.sna_objectsets[target_bake_set_index].bakeunit) - 1
     print(f"Added Bake Unit at index {new_bake_unit_index} to Bake Set {target_bake_set_index}")
else:
     print(f"Bake Set index {target_bake_set_index} does not exist.")

             

Deleting a Bake Unit

This removes the currently active Bake Unit from the currently active Bake Set. You need to set both the scene's active_bakeset index and the active_bakeunit index within that specific Bake Set first.


import bpy

scene_data = bpy.data.scenes["Scene"] # Accessing scene data directly

# Example: Delete the first Bake Unit (index 0) from the third Bake Set (index 2)
target_bake_set_index = 2
target_bake_unit_index = 0

if target_bake_set_index < len(scene_data.sna_objectsets):
    bake_set = scene_data.sna_objectsets[target_bake_set_index]
    if target_bake_unit_index < len(bake_set.bakeunit):
        # Set the active Bake Set index
        scene_data.active_bakeset = target_bake_set_index
        # Set the active Bake Unit index within that Bake Set
        # Assuming property name is 'active_bakeunit' based on user info
        bake_set.active_bakeunit = target_bake_unit_index

        print(f"Set active BS={scene_data.active_bakeset}, BU={bake_set.active_bakeunit}")
        print(f"Attempting to delete Bake Unit {target_bake_unit_index} from Bake Set {target_bake_set_index}...")

        # Operator to delete the active Bake Unit in the active Bake Set
        bpy.ops.sna.delete_bakeunit_item()

        print("Deletion operator called.")
    else:
        print(f"Bake Unit index {target_bake_unit_index} does not exist in Bake Set {target_bake_set_index}.")
else:
    print(f"Bake Set index {target_bake_set_index} does not exist.")

             

Finalizing & Exporting Results

After baking textures using an operator like api.run_ultibake, Ultibake provides a dedicated operator to prepare meshes for export: bpy.ops.sna.create_finalized_meshes_b853c().

This "Finalize" operator creates **new mesh objects** based on the low-poly sources defined in your Bake Sets. These new objects typically have:

To programmatically export these finalized objects (e.g., to FBX for game engines), you first need to identify and select them. A common method is to compare the objects present in the scene before and after running the Finalize operator:


import bpy
import os

# --- 1. Get object names BEFORE finalizing ---
objects_before = set(obj.name for obj in bpy.data.objects)

# --- 2. Run the Finalize process ---
print("Running Finalize Meshes process...")
try:
    # Use the specific Finalize operator
    bpy.ops.sna.create_finalized_meshes_b853c()
except Exception as e:
    print(f"Error during finalize: {e}")
print("Finalize Meshes process called.")

# --- 3. Get object names AFTER finalizing ---
objects_after = set(obj.name for obj in bpy.data.objects)

# --- 4. Find the newly created objects ---
new_object_names = objects_after - objects_before
new_objects = [bpy.data.objects[name] for name in new_object_names if name in bpy.data.objects]

if not new_objects:
    print("No new objects detected after finalize.")
else:
    print(f"Detected {len(new_objects)} new objects:")
    for obj in new_objects:
        print(f"- {obj.name}")

    # --- 5. Select the new objects ---
    bpy.ops.object.select_all(action='DESELECT')
    for obj in new_objects:
        obj.select_set(True)
    if new_objects: # Make first new object active
         bpy.context.view_layer.objects.active = new_objects[0]

    # --- 6. Export the selection ---
    export_path = "C:/path/to/your/export/folder/exported_bake.fbx" # CHANGE THIS PATH
    export_dir = os.path.dirname(export_path)
    if not os.path.exists(export_dir):
        os.makedirs(export_dir) # Create dir if it doesn't exist

    print(f"Exporting {len(new_objects)} selected objects to {export_path}...")
    try:
        bpy.ops.export_scene.fbx(
            filepath=export_path,
            use_selection=True, # Export only selected
            check_existing=True,
            # Add other relevant FBX settings
            object_types={'MESH'},
            # axis_forward='-Z', axis_up='Y',
        )
        print("Export successful.")
    except Exception as e:
        print(f"Error during FBX export: {e}")

            

Cleaning Up Extra UV Maps on Finalized Objects

Note that the finalized objects created by sna.create_finalized_meshes_b853c inherit all UV maps from the original low-poly source. If your target platform or workflow requires only the primary UV map used for the baked textures, you may need to remove the others after finalizing but before exporting:


# Assuming 'new_objects' is the list of finalized objects found previously
# Replace "UVMap" with the actual name of the UV map you want to KEEP
primary_uv_name = "UVMap"

print(f"Cleaning up UV maps on {len(new_objects)} finalized objects, keeping '{primary_uv_name}'...")

for obj in new_objects:
    # Ensure it's a mesh object with UV layers
    if obj.type == 'MESH' and obj.data.uv_layers:

        # Check if the primary map exists before attempting removal
        if primary_uv_name not in obj.data.uv_layers:
            print(f"  Warning: Primary UV map '{primary_uv_name}' not found on {obj.name}. Skipping UV cleanup.")
            continue

        # List UV maps to remove (iterate by index is safer when removing)
        indices_to_remove = []
        for i, uv_layer in enumerate(obj.data.uv_layers):
            if uv_layer.name != primary_uv_name:
                indices_to_remove.append(i)

        # Remove layers by index, starting from the highest index to avoid shifting
        if indices_to_remove:
            print(f"  Removing {len(indices_to_remove)} UV map(s) from {obj.name}...")
            indices_to_remove.sort(reverse=True) # Important: remove from end to start
            for i in indices_to_remove:
                try:
                    uv_layer_to_remove = obj.data.uv_layers[i]
                    print(f"    Removing '{uv_layer_to_remove.name}'")
                    obj.data.uv_layers.remove(uv_layer_to_remove)
                except Exception as e:
                    print(f"    Error removing UV map at index {i} from {obj.name}: {e}")
        else:
            print(f"  No extra UV maps found on {obj.name}.")

print("UV map cleanup complete.")
            

Remember to adjust the export path and FBX settings in the first example according to your needs and target platform (Unreal Engine, Unity, Godot, etc.).