Creating a Lanscape using C++ or the Python API

Hello, I am trying to create and spawn a Landscape through code using C++ or the python API of UE4.27.

For C++, with GetWorld()->SpawnActor<ALandscape> , ALandscapeProxy::Import(...) and LandscapeEditorDetailCustomization_NewLandscape::OnCreateButtonClicked() methods as a learning ground, I couldn’t produce a working result.

For Python API, I followed the approach of using unreal.EditorLevelLibrary.spawn_actor_from_class()
to spawn a LandscapeProxy actor and then alter its heightmap using the landscape_import_heightmap_from_render_target() of the LandscapeProxy class.
Unfortunately the spawned actor is of class LandscapePlaceholder which does not support any heightmap operations and due to no examples and the lacking documentation of the python unreal API, I can’t seem to find another solution.

Is there a way to make/spawn a Landscape using either C++ or Python. Examples and guides are very much appreciated!

1 Like

I extented the landscape python API in my plugin, and lots of others editor API

Below is my tool’s snippet using the API.

    def ui_on_button_ImportHeight_click(self):
        selected_tiles = self.current_selected_tiles()
        taskSteps = len(selected_tiles)

        with unreal.ScopedSlowTask(taskSteps, "Starting Import Landscape Heights") as slow_task:
            slow_task.make_dialog(True)
            for x, y in selected_tiles:
                unreal.EditorLevelLibrary.load_level(self.get_tile_level_path(x, y, self.lod_option))
                slow_task.enter_progress_frame(1, "Importing landscape height {}_{}_LOD{}".format(x, y, self.lod_option))
                landscape = self.get_landcapeproxy(x, y, self.lod_option)
                if landscape:
                    heighmapPath = self.get_heightmap_source_path(x, y, self.lod_option)
                    height_data = self.read_heigh_data(heighmapPath)
                    print("type: {}  length: {} elementType: {}".format(type(height_data), len(height_data), type(height_data[0])))
                    unreal.PythonLandscapeLib.set_heightmap_data(landscape, height_data=height_data)
                    landscape.modify()
                    unreal.EditorLevelLibrary.save_current_level()
2 Likes

I still have not checked your plugin, but I studied your methods and I am interested in your implementation of them.

In your PythonLandscapeLib.create_landscape() method specifically, you create a landscape using spawn_actor_from_class() and set the components, sections, etc. using other methods?

Hi s_zervos,
yes, call World->SpawnActor<ALandscape>(), and set other info, like material layer, HeightData and so on.

I learn it from FDatasmithLandscapeImporter::ImportLandscapeActor and FLandscapeEditorDetailCustomization_NewLandscape::OnCreateButtonClicked, FYI.

If create a big world, create_landscape_proxy_with_guid is much useful than create_landscape, which can make each part of the big landscape one by one. It’s SpawnActor<ALandscapeStreamingProxy> instead of ALandscape. then assign the same guid.

below is the code of UPythonLandscapeLib::CreateLandscape

ALandscape * UPythonLandscapeLib::CreateLandscape(const FTransform& LandscapeTransform, const int32& SectionSize, const int32& SectionsPerComponent, const int32& ComponentCountX, const int32& ComponentCountY)
{
	int32 QuadsPerComponent = SectionSize * SectionsPerComponent;

	int32 SizeX = ComponentCountX * QuadsPerComponent + 1;
	int32 SizeY = ComponentCountY * QuadsPerComponent + 1;

	TArray<FLandscapeImportLayerInfo> MaterialImportLayers;
	MaterialImportLayers.Reserve(0);


	TMap<FGuid, TArray<uint16>> HeightDataPerLayers;
	TMap<FGuid, TArray<FLandscapeImportLayerInfo>> MaterialLayerDataPerLayers;


	TArray<uint16> HeightData;
	HeightData.SetNum(SizeX * SizeY);
	for (int32 i = 0; i < HeightData.Num(); i++)
	{
		HeightData[i] = 32768;
	}

	HeightDataPerLayers.Add(FGuid(), MoveTemp(HeightData)); /*ENewLandscapePreviewMode.NewLandscape*/
	// ComputeHeightData will also modify/expand material layers data, which is why we create MaterialLayerDataPerLayers after calling ComputeHeightData
	MaterialLayerDataPerLayers.Add(FGuid(), MoveTemp(MaterialImportLayers));

	//FScopedTransaction Transaction(TEXT("Undo", "Creating New Landscape"));

	UWorld* World = nullptr;
	{
		// We want to create the landscape in the landscape editor mode's world
		FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext();
		World = EditorWorldContext.World();
	}

	ALandscape* Landscape = World->SpawnActor<ALandscape>();
	Landscape->bCanHaveLayersContent = false;
	Landscape->LandscapeMaterial = nullptr;

	Landscape->SetActorTransform(LandscapeTransform);

	// automatically calculate a lighting LOD that won't crash lightmass (hopefully)
	// < 2048x2048 -> LOD0
	// >=2048x2048 -> LOD1
	// >= 4096x4096 -> LOD2
	// >= 8192x8192 -> LOD3


	//const FGuid& InGuid, int32 InMinX, int32 InMinY, int32 InMaxX, int32 InMaxY, int32 InNumSubsections, int32 InSubsectionSizeQuads, const TMap<FGuid, TArray<uint16>>& InImportHeightData,
	//	const TCHAR* const InHeightmapFileName, const TMap<FGuid, TArray<FLandscapeImportLayerInfo>>& InImportMaterialLayerInfos, ELandscapeImportAlphamapType InImportMaterialLayerType, const TArray<struct FLandscapeLayer>* InImportLayers = nullptr

	Landscape->Import(FGuid::NewGuid(), 0, 0, SizeX - 1, SizeY - 1, SectionsPerComponent, QuadsPerComponent, HeightDataPerLayers, nullptr, MaterialLayerDataPerLayers, ELandscapeImportAlphamapType::Additive);

	Landscape->StaticLightingLOD = FMath::DivideAndRoundUp(FMath::CeilLogTwo((SizeX * SizeY) / (2048 * 2048) + 1), (uint32)2);
	// Register all the landscape components
	ULandscapeInfo* LandscapeInfo = Landscape->GetLandscapeInfo();

	LandscapeInfo->UpdateLayerInfoMap(Landscape);


	Landscape->RegisterAllComponents();

	// Need to explicitly call PostEditChange on the LandscapeMaterial property or the landscape proxy won't update its material
	FPropertyChangedEvent MaterialPropertyChangedEvent(FindFieldChecked< FProperty >(Landscape->GetClass(), FName("LandscapeMaterial")));
	Landscape->PostEditChangeProperty(MaterialPropertyChangedEvent);
	Landscape->PostEditChange();


	return Landscape;
}
3 Likes

Thank you very much for your answers they really helped a lot! Both your python plugin and C++ code were very helpful. I will follow your logic for importing a height/weight map. Thanks again!

In order to load a material layer weightmap from png you can use

	FLandscapeImportLayerInfo LayInfoGrass;
	LayInfoGrass.LayerData.Reset();
	LayInfoGrass.LayerData.AddUninitialized(TotalSize);
	LayInfoGrass.LayerName = "Grass";
	LayInfoGrass.SourceFilePath = "path/to/weightmap.png";
	// Important to have a layer info, without it, it will not show at all, and crash.
	LayInfoGrass.LayerInfo = CreateLandscapeLayerInfo(LayInfoGrass.LayerName, false); 
	ReadWeightmapFile(LayInfoGrass.LayerData, LayInfoGrass.SourceFilePath, LayInfoGrass.LayerName, SizeX, SizeY);
	MaterialImportLayers.Add(LayInfoGrass);

.........
MaterialLayerDataPerLayers.Add(BaseLayer.Guid, MaterialImportLayers);
.........

Landscape->Import(FGuid::NewGuid(), 0, 0, SizeX - 1, SizeY - 1,
	                  SectionsPerComponent, QuadsPerSection, HeightDataPerLayers,
	                  *ReimportHeightmapFilePath, MaterialLayerDataPerLayers, ELandscapeImportAlphamapType::Additive, &ImportLayers);
.....

CreateLandscapeLayerInfo is a copy of // Engine\UE5\Source\Editor\LandscapeEditor\Private\LandscapeEditorDetailCustomization_ImportLayers.cpp:322

ReadWeightmapFile is a copy of // Engine\UE5\Source\Editor\WorldBrowser\Private\Tiles\WorldTileCollectionModel.cpp:1504

1 Like

Hi there,

I’ve been searching on how to create a new landscape in unreal using python and apply a heightmap texture.

Finally i found your library which seems to do the job but to be honest i’m having issues on how to start with it…

I have just created a landscape with:

tr1=unreal.Transform(unreal.Vector(0,0,0),unreal.Rotator(0,0,0),unreal.Vector(1,1,1))
unreal.PythonLandscapeLib.create_landscape(tr1,63,2,32,32)

But… how do i apply a heightmap to that landscape? I see the function “set_heightmap_data” but i don’t know how should i define the “height_data” required…

Can you provide an example?

Kind regards

Roberto

Can you provide an example of create_landscape_proxy_with_guid in C++ I can create LandscapeStremingProxy and Landscape separately
but I don’t understand how to merge them. Thank you.