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
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.
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 specifysearch_folder
(Unreal path, exclude ‘Content’). - This will batch-process all assets in that folder and subfolders.
- Set
-
Option 2: Manual Asset Paths
- Set
use_folder_scan = False
and add paths inmanual_asset_paths
to process only the assets you specify.
- Set
2. Parameters to Modify
-
start_cull_distance
andend_cull_distance
Set these to the values you want to batch apply. -
fields_to_copy
Must include every field you want preserved forGrassVariety
.
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.")