Can I edit LandscapeGrassType varieties as a batch?

Hi guys - very new to Unreal Development, so please bare with me.
So have created a landscape and testing out building a grass layer, uisng the LandscapeGrassType assett. Have used about 30 varieties in the assett. Now I want to amend the scaling and the placement jitter and the cull distances, and I started editing these one at a time but is there a way to edit all varieties as a batch? i.e change all Scale X min to 0.8 etc, Thanks

1 Like

Bump, would like help regarding this too. I attempted using Python and the set_editor_property method to set values. When I print the values after setting, they appear correct. However, these new values are not reflected in the LandscapeGrassType asset, nor do they have any impact on the assets rendered in viewport. Restarting the project shows that the changes were not applied at all.

1 Like

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.")