Can I edit LandscapeGrassType varieties as a batch?

Batch Modify Landscape Grass Type Properties

Tested on Unreal Engine 5.3


Script Overview

This script enables batch modification of specific properties for all LandscapeGrassType assets’ GrassVarieties array elements in a given folder within a UE5.3 project.
Because Unreal Engine’s set_editor_property does not work reliably for struct array elements,
the script creates a new struct, deep-copies all fields, overrides selected properties, writes the array back, and saves the asset to ensure all data is retained and changes take effect.


Usage Instructions

1. Asset Scope Selection

  • Option 1: Folder Scan

    • Set use_folder_scan = True and specify search_folder (Unreal path, exclude ‘Content’).
    • This will batch-process all assets in that folder and subfolders.
  • Option 2: Manual Asset Paths

    • Set use_folder_scan = False and add paths in manual_asset_paths to process only the assets you specify.

2. Parameters to Modify

  • start_cull_distance and end_cull_distance
    Set these to the values you want to batch apply.

  • fields_to_copy
    Must include every field you want preserved for GrassVariety.
    Use lowercase, underscore-separated field names as shown below.

3. Process

For each asset, and for each GrassVariety element:

  • Create a new struct
  • Copy all fields
  • Override the specified parameters
  • Set the array back on the asset and save

Important Notes

  • fields_to_copy:
    You must maintain a complete list (including any custom or blueprint-exposed fields).
    Omitting a field will cause it to be lost (reset to default) for affected varieties.
import unreal

# == USER SETTINGS ==
# Option 1: Scan assets in a folder
use_folder_scan = True       # True = scan folder, False = use manual asset list

search_folder = "/Game/Brushify/Materials/Landscape/GrassTypes"   # Unreal path (exclude 'Content')

# Option 2: Manual asset paths
manual_asset_paths = [
    "/Game/Brushify/Materials/Landscape/GrassTypes/LG_Beach",
    # "/Game/Brushify/Materials/Landscape/GrassTypes/OtherAsset",
]

# == PARAMETERS TO MODIFY ==
start_cull_distance = 666
end_cull_distance = 888

fields_to_copy = [
    "grass_mesh",
    "override_materials",
    "grass_density",
    "use_grid",
    "placement_jitter",
    "start_cull_distance",
    "end_cull_distance",
    "min_lod",
    "scaling",
    "scale_x",
    "scale_y",
    "scale_z",
    "random_rotation",
    "align_to_surface",
    "use_landscape_lightmap",
    "lighting_channels",
    "receives_decals",
    "affect_distance_field_lighting",
    "cast_dynamic_shadow",
    "cast_contact_shadow",
    "keep_instance_buffer_cpucopy",
    "instance_world_position_offset_disable_distance",
    "shadow_cache_invalidation_behavior",
]

# == ASSET PATHS TO PROCESS ==
if use_folder_scan:
    print(f"Scanning folder: {search_folder}")
    asset_paths = unreal.EditorAssetLibrary.list_assets(search_folder, recursive=True, include_folder=False)
else:
    print("Using manual asset path list")
    asset_paths = manual_asset_paths

#MAIN PROCESS

for asset_path in asset_paths:
    print(f"Processing: {asset_path}")
    asset = unreal.EditorAssetLibrary.load_asset(asset_path)
    if not asset:
        print(f"Failed to load asset: {asset_path}")
        continue

    # Retrieve grass_varieties property (array of structs)
    try:
        grass_varieties = asset.get_editor_property("grass_varieties")
    except Exception:
        try:
            grass_varieties = asset.get_editor_property("GrassVarieties")
        except Exception:
            print(f"Cannot find GrassVarieties property: {asset_path}")
            continue

    new_grass_varieties = []
    changed = False

    for i, grass_object in enumerate(grass_varieties):
        new_grass = unreal.GrassVariety()

        # Deep-copy all fields to preserve data
        for field in fields_to_copy:
            try:
                value = grass_object.get_editor_property(field)
                new_grass.set_editor_property(field, value)
            except Exception:
                pass  # Ignore if property does not exist on this asset

        # Override the target properties
        try:
            scd_struct = new_grass.get_editor_property("start_cull_distance")
            scd_struct.set_editor_property("default", start_cull_distance)
        except Exception as e:
            print(f"Variety {i}: Failed to set start_cull_distance: {e}")

        try:
            ecd_struct = new_grass.get_editor_property("end_cull_distance")
            ecd_struct.set_editor_property("default", end_cull_distance)
        except Exception as e:
            print(f"Variety {i}: Failed to set end_cull_distance: {e}")

        new_grass_varieties.append(new_grass)
        changed = True

    if changed:
        asset.set_editor_property("grass_varieties", new_grass_varieties)
        unreal.EditorAssetLibrary.save_loaded_asset(asset)
        print(f"Batch-modified: {asset_path}")

print("All assets updated.")