Image to Three.js Model
img2threejs/img2threejs
Rebuilds the object in a reference image as a procedural, animation-ready Three.js model written entirely in code, using staged sculpting with quality checks.
Create and edit landscape terrain — heightmaps, sculpting, paint layers, terrain analysis, mesh projection, and procedural features like mountains/valleys/craters (LandscapeService).
$ npx skills add kevinpbuckley/VibeUE --skill landscape -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install kevinpbuckley/VibeUE landscape --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .claude/skills && cp -r skills-src/Content/Skills/landscape .claude/skills/landscape && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "landscape" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/landscape into .claude/skills/landscape/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "landscape", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/landscapeType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add kevinpbuckley/VibeUE --skill landscape -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install kevinpbuckley/VibeUE landscape --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .agents/skills && cp -r skills-src/Content/Skills/landscape .agents/skills/landscape && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "landscape" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/landscape into .agents/skills/landscape/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "landscape", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add kevinpbuckley/VibeUE --skill landscape -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install kevinpbuckley/VibeUE landscape --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/Content/Skills/landscape .cursor/skills/landscape && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "landscape" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/landscape into .cursor/skills/landscape/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "landscape", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/kevinpbuckley/VibeUE.git --path Content/Skills/landscape--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add kevinpbuckley/VibeUE --skill landscape -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install kevinpbuckley/VibeUE landscape --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/Content/Skills/landscape .gemini/skills/landscape && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "landscape" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/landscape into .gemini/skills/landscape/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "landscape", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install kevinpbuckley/VibeUE landscapeInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add kevinpbuckley/VibeUE --skill landscape -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .github/skills && cp -r skills-src/Content/Skills/landscape .github/skills/landscape && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "landscape" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/landscape into .github/skills/landscape/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "landscape", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add kevinpbuckley/VibeUE --skill landscape -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install kevinpbuckley/VibeUE landscape --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/kevinpbuckley/VibeUE.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/Content/Skills/landscape .opencode/skills/landscape && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "landscape" agent skill from https://github.com/kevinpbuckley/VibeUE/tree/master/Content/Skills/landscape into .opencode/skills/landscape/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "landscape", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
landscapeCreate and edit landscape terrain — heightmaps, sculpting, paint layers, terrain analysis, mesh projection, and procedural features like mountains/valleys/craters (LandscapeService).
Landscape is an agent skill from kevinpbuckley/VibeUE. Create and edit landscape terrain — heightmaps, sculpting, paint layers, terrain analysis, mesh projection, and procedural features like mountains/valleys/craters (LandscapeService). Use when the user asks to create a landscape, sculpt/raise/lower terrain, import or export a heightmap, paint terrain layers, or add procedural terrain features. For terrain materials load landscape-materials; for real-world heightmaps load terrain-data.
Its SKILL.md is about 7k tokens, which your agent loads only when the skill is triggered. The skill folder holds 6 other files, including scripts (for example `intelligent-sculpting.md`, `return-types.md` and `workflows-creation.md`).
It sits in Game Development. The repository describes itself as: Unreal Engine Vibe Coding tool. The licence is MIT.
5 steps, taken from the first numbered list in SKILL.md.
Read from SKILL.md and the folder at commit dc051be. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 1 file in scripts/, which the agent can run.
Shell commands in SKILL.md call:
gitFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use git, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Landscape loads about 7k tokens when it runs. Until then it costs about 112 tokens; SKILL.md has 2,770 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from kevinpbuckley/VibeUE at commit dc051be, republished under its MIT licence (© kevinpbuckley). 2,770 words, ~7,000 tokens.
.claude/skills/landscape/SKILL.md (or your agent's skills folder). This skill also uses 5 other files; get the full folder from GitHub.🧠 Brains complement: IF an
unreal-engine-skills-managertool (external MCP) exists in this session, call it with{action: "load", skill: "landscape-and-foliage"}for UE domain knowledge on this topic — correct APIs, architecture, best practices — and treat it as the rubric for any review / "best practices" question. If no such tool is available (e.g. running under Claude Code or Codex without that MCP), skip this line entirely and proceed with this skill alone — do NOT attempt the call.
This skill is split into the lean index (you are reading it) plus sibling sub-docs. Skills
load via the engine's AgentSkillToolset (ListSkills / GetSkills) — request
landscape/<section> through GetSkills, or read the sibling *.md file (it sits next to
this SKILL.md on disk) directly:
| Sub-doc | When to load |
|---|---|
return-types.md | Exact property names of LandscapeCreateResult, LandscapeInfo_Custom, LandscapeLayerInfo_Custom, LandscapeSplinePointInfo, LandscapeSplineSegmentInfo, LandscapeSplineMeshEntryInfo, plus the create_landscape signature. |
workflows-creation.md | Creating landscapes, getting info, importing/exporting heightmaps, recreating splines, copying terrain/paint layers between landscapes, calculating side-by-side offsets, reading height data in a region. |
workflows-editing.md | Sculpting, direct region editing, procedural noise, painting layers, setting material, querying terrain, LandscapeGrassType creation/cloning, full landscape cloning, existence checks, heightmap sizing utilities. |
intelligent-sculpting.md | v3 semantic features (mountain/valley/ridge/plateau/crater/terraces/erosion/blend), terrain analysis (slope/normal/flat areas), mesh projection, batch line traces, complete v3 workflow example. |
QuadsPerSection must be one of: 7, 15, 31, 63, 127, 255 SectionsPerComponent must be 1 or 2
Common setup: QuadsPerSection=63, SectionsPerComponent=1, ComponentCount=8x8
Creating landscapes with many components is SLOW. Python execution has a 30-second timeout.
Safe configurations (under 30s):
ComponentCount=4x4 (253x253 resolution) — safe for throwaway/test terrainsComponentCount=8x8 (505x505 resolution) — can exceed 30s in a populated level (measured 67s in a level that already had another landscape); safe only in near-empty levelsWill TIMEOUT (avoid):
ComponentCount=16x16 or larger in a populated levelComponentCount=36x36 or larger — too many components, takes minutesComponentCount=72x72 — definitely exceeds timeout⚠️ A timeout does NOT cancel the operation. PYTHON_EXECUTION_TIMEOUT means the tool stopped waiting — the create keeps running on the game thread and usually completes. Never blindly retry: you'd stack a second landscape with the same label (then delete_landscape removes only the first match and landscape_exists stays true, which looks like a delete failure). After a timeout, check landscape_exists(label) first. create_landscape also refuses to create a landscape whose label already exists.
import_heightmap() requires the heightmap file resolution to exactly match the landscape resolution. There is NO automatic scaling. A size mismatch will fail or produce flat/corrupt terrain.
If the target landscape label does not exist, import_heightmap() now auto-creates a matching landscape at world origin with default scale (100,100,100) before importing. For real-world terrain workflows, explicitly creating the landscape first is still recommended so you can control XY/Z scale.
Landscape resolution formula: Resolution = (ComponentCount × QuadsPerSection × SectionsPerComponent) + 1
Safe performant configs (common resolutions):
| Components | Quads | Sections | Resolution | km at scale=100 | Notes |
|---|---|---|---|---|---|
| 8×8 | 63 | 1 | 505×505 | ~0.5 km | Fast, good for prototypes |
| 8×8 | 63 | 2 | 1009×1009 | ~1.0 km | Good balance of detail/speed |
| 16×16 | 63 | 1 | 1009×1009 | ~1.0 km | Same resolution, more LOD components |
| 8×8 | 127 | 1 | 1017×1017 | ~1.0 km | Larger quads, fewer components |
| 16×16 | 63 | 2 | 2017×2017 | ~2.0 km | Epic recommended for medium landscapes |
| 32×32 | 63 | 1 | 2017×2017 | ~2.0 km | Same resolution, more LOD components |
| 8×8 | 127 | 2 | 2033×2033 | ~2.0 km | High detail, fewer components |
| 16×16 | 127 | 1 | 2033×2033 | ~2.0 km | High detail, more components |
| 32×32 | 63 | 2 | 4033×4033 | ~4.0 km | Large open world |
| 32×32 | 127 | 2 | 8129×8129 | ~8.1 km | Max single tile |
⚠️ 1081×1081 is NOT a valid performant config! The only config producing 1081 is 36×36 components, 15 quads, 2 sections — which has 1296 components and will timeout. Never request or assume 1081.
Four Python utility functions help match heightmaps to landscapes:
| Function | Purpose |
|---|---|
get_heightmap_dimensions(file_path) | Read width/height/bitdepth of a PNG or RAW heightmap file |
resize_heightmap(source, width, height, output, interpolation) | Resample a 16-bit heightmap to new dimensions. Default interpolation="bicubic" (Catmull-Rom, smooth normals); pass "bilinear" only if you need the legacy behavior — bilinear upsampling of a low-res DEM produces blocky planar facets on flat plains |
calculate_landscape_resolution(cx, cy, quads, sections) | Compute resolution from landscape config parameters |
find_landscape_config_for_resolution(width, height) | Find the best landscape config that matches a given resolution |
BEST approach — request the heightmap at the exact resolution you need:
(8 × 63 × 2) + 1 = 1009terrain_data generate_heightmap with resolution=1009 and map_size=Nmap_size controls how many real-world km are captured (default 17.28)map_size = more detail for a specific feature (e.g., 2-5 km for a single mountain)map_size = wider area with less per-pixel detail (e.g., 10-20 km for a region)If you already have a heightmap at a non-matching resolution:
get_heightmap_dimensions(file_path)find_landscape_config_for_resolution(width, height)resize_heightmap(source, target_w, target_h)In a World Partition level, pass world_partition_grid_size (components per proxy grid cell,
e.g. 2) to create_landscape to split the landscape into ALandscapeStreamingProxy actors —
the same thing the editor's New Landscape tool does in a WP world:
result = unreal.LandscapeService.create_landscape(
unreal.Vector(0, 0, 0), unreal.Rotator(0, 0, 0), unreal.Vector(100, 100, 100),
sections_per_component=1, quads_per_section=63,
component_count_x=8, component_count_y=8,
landscape_label="WPLandscape", world_partition_grid_size=2)world_partition_grid_size=0 keeps the single monolithic ALandscape (old behavior).import_heightmap, sculpting, painting, and region edits are proxy-transparent: they resolve the
parent ALandscape and fan updates out to every proxy sharing its landscape GUID. Nothing else
in your workflow changes.get_last_landscape_write() + git statusOn a World Partition landscape the engine's dirtying mode (LandscapeSettings.LandscapeDirtyingMode,
default In Landscape Mode And User Triggered Changes) does not mark an edit dirty when it is made
outside Landscape mode — and every VibeUE/Python edit is outside Landscape mode. The engine
tracks the touched packages in an internal modified-package list instead of the normal dirty flag. The
practical consequences, all measured:
import_heightmap dirties the proxies but does not save them. You must save explicitly.flatten_at_location, set_height_in_region, sculpt/smooth/…) can
write the proxy .uasset files to disk during the edit-layer resolve while the editor's dirty list
shows nothing. One flatten_at_location left 49 proxy files modified on disk with an empty dirty
list. So the dirty list is not a reliable audit of a WP landscape after a brush op.git status is the only fully honest audit (the modified files live under
Content/__ExternalActors__/...; restore an unwanted change with the editor closed via
git checkout -- Content/__ExternalActors__/...).Two tools make this auditable from Python:
import unreal
svc = unreal.LandscapeService
svc.flatten_at_location("Landscape", 0.0, 0.0, 0.0, 250.0) # any height writer
# What did that write actually touch?
rep = svc.get_last_landscape_write()
print(rep.summary) # e.g. "49 proxies dirtied; 49 written to disk by the edit-layer flush (mtime changed)"
print(rep.num_proxies_dirtied, rep.num_written_to_disk, rep.b_any_written_to_disk)
for p in rep.proxies:
print(p.package_name, "dirty:", p.b_dirty_after, "written:", p.b_written_to_disk, p.file_size_bytes)get_last_landscape_write() reports, per proxy package the last write touched: b_dirty_before /
b_dirty_after (the unreliable flag) and b_written_to_disk — an on-disk mtime comparison
bracketing the edit-layer resolve, which is the signal you can trust. num_written_to_disk /
b_any_written_to_disk summarise it.
ForceUpdateLayersContent resolve). If the engine ever defers the on-disk save to a
later editor tick, b_written_to_disk reads false even though the file changes shortly after —
so git status remains the final word.save_landscape(label) is the deterministic, explicit save: it collects the parent ALandscape
package plus every proxy sharing the landscape GUID and saves them all through
UEditorLoadingAndSavingUtils::SavePackages(..., only_dirty=False) — i.e. regardless of the dirty
flag. Use it after import_heightmap (which never saves), or any time you want a clean, audited flush.
res = svc.save_landscape("Landscape")
if res.b_success:
print("saved", res.num_requested, "packages:", list(res.saved_packages))
else:
print("save failed:", res.error_message) # e.g. a read-only / source-controlled fileLow-res source DEMs upsampled to landscape resolution show planar facets on flat terrain. Fixes, in order of preference:
resize_heightmap(..., interpolation="bicubic") — now the default; re-run the resize+import if
the landscape was built with an older (bilinear) resize.blur_passes (20–40) on terrain_data generate_heightmap for plains-heavy regions.landscape-materials skill) when height data must stay pixel-exact.(100, 100, 100) = 1 meter per unit⚠️ When importing real-world terrain from terrain_data, ALWAYS calculate Z scale:
z_scale = 20000 / height_scaleDo NOT hardcode Z scale. The height_scale from preview_elevation determines the correct Z scale.
The heightmap is uint16 (0–65535, midpoint 32768). Maximum world height depends on Z scale:
| Z Scale | Max Height (world units) | Min Height (world units) | Total Range |
|---|---|---|---|
| 100 | ~25,599 | ~-25,600 | ~51,200 |
| 200 | ~51,198 | ~-51,200 | ~102,400 |
| 50 | ~12,800 | ~-12,800 | ~25,600 |
Formula: MaxHeight = (65535 - 32768) × (1/128) × ZScale
When sculpting pushes vertices to 0 or 65535, a saturation warning is logged. To increase range:
set_height_in_region are world-space Z valuesget_height_in_region are world-space Z valuesGrassVariety (and its nested structs like PerPlatformFloat, FloatInterval, etc.) cannot be set via direct attribute assignment. You MUST use set_editor_property() for all properties.
# WRONG - raises "Property is read-only and cannot be set"
nv = unreal.GrassVariety()
nv.affect_distance_field_lighting = True
nv.grass_density.default = 50.0
# CORRECT - use set_editor_property and construct new struct instances
nv = unreal.GrassVariety()
nv.set_editor_property('affect_distance_field_lighting', True)
nv.set_editor_property('grass_density', unreal.PerPlatformFloat(default=50.0))
nv.set_editor_property('scale_x', unreal.FloatInterval(min=1.0, max=3.0))This applies to ALL nested structs: PerPlatformFloat, PerPlatformInt, PerQualityLevelFloat, PerQualityLevelInt, FloatInterval, LightingChannels. Always construct a new struct instance with keyword args.
LandscapeGrassType assets are NOT created via LandscapeService. Use AssetToolsHelpers with LandscapeGrassTypeFactory:
# WRONG - no such method exists
unreal.LandscapeService.create_grass_type("LGT_MyGrass", "/Game/Grass")
# CORRECT - use AssetTools + Factory
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
factory = unreal.LandscapeGrassTypeFactory()
new_asset = asset_tools.create_asset("LGT_MyGrass", "/Game/Grass", unreal.LandscapeGrassType, factory)unreal.LandscapeMaterialService.create_landscape_material("M_Terrain", "/Game/Mats")unreal.MaterialService.create_material("M_Terrain", "/Game/Mats") → causes TypeError: Nativize: Cannot nativize 'MaterialCreateResult' as 'String'MaterialService is for generic materials. Landscape materials MUST use LandscapeMaterialService.
Every paint layer needs a ULandscapeLayerInfoObject asset:
LandscapeMaterialService.create_layer_info_object().asset_path from the result — never guess pathsLI_<LayerName> (e.g., LI_Grass, LI_Rock) — NOT Grass_LayerInfoLandscapeService.add_layer(landscape_label, info.asset_path)After creating/changing a landscape material, compile it before assigning to the landscape.
compile_material() can take minutes for complex landscape materials. NEVER combine material creation + compile + layer info creation in one code block — it WILL timeout.
Do this in separate steps:
compile_material() (this is slow)| WRONG | CORRECT |
|---|---|
create_landscape(..., actor_label="X") | create_landscape(..., landscape_label="X") |
result.actor_path | result.actor_label (no actor_path exists) |
info.success | Check info.actor_label (no success field on info) |
info.num_sections | info.num_subsections |
info.quads_per_section | info.subsection_size_quads |
MaterialService.create_material() for landscapes | LandscapeMaterialService.create_landscape_material() |
Guessing layer info path Grass_LayerInfo | Use create_layer_info_object().asset_path → LI_Grass |
segment.start_control_point_index | segment.start_point_index (spline segment) |
segment.end_control_point_index | segment.end_point_index (spline segment) |
layer.name on list_layers result | layer.layer_name (LandscapeLayerInfo_Custom property) |
LandscapeService.create_spline_control_point(...) | LandscapeService.create_spline_point(...) (no "control" in name) |
LandscapeMaterialService.add_material_layer(...) | LandscapeMaterialService.add_layer_to_blend_node(...) |
mat_path = create_landscape_material(...) as string | Returns LandscapeMaterialCreateResult — use result.asset_path |
connect_spline_points(..., tangent_length=25.0) ignoring negative source | Pass tangent_length=seg.start_tangent_length, end_tangent_length=seg.end_tangent_length — both tangents are needed for exact shape |
Only passing tangent_length to connect_spline_points | Must also pass end_tangent_length=seg.end_tangent_length — end tangent is typically negative (UE convention). Omitting it defaults to -start_tangent which is usually correct for NEW splines, but when copying, always pass both explicitly |
create_spline_point("L", location=vec) | Parameter is world_location, NOT location: create_spline_point("L", world_location=vec) |
modify_spline_point("L", 0, location=vec) | Parameter is world_location, NOT location: modify_spline_point("L", 0, world_location=vec) |
modify_spline_point(...) with wrong rotation | Use auto_calc_rotation=False, rotation=pt.rotation to set exact rotation from get_spline_info |
Setting rotation BEFORE connect_spline_points | connect_spline_points triggers auto-calc rotation — set rotations AFTER all segments are connected |
pt.paint_layer_name on LandscapeSplinePointInfo | Property is pt.layer_name (NOT paint_layer_name) — paint_layer_name is the parameter name in create_spline_point |
| Reading weights right after painting and getting 0.0 | Weights are written to edit layer — reads use same path |
spline_info.points on LandscapeSplineInfo | Property is spline_info.control_points (NOT points) |
| Not setting spline segment meshes after connecting | Use set_spline_segment_meshes() to assign mesh entries (e.g. SM_River) — splines are green without meshes |
| Forgetting to set control point meshes | Use set_spline_point_mesh() — control points can have their own static mesh |
layer.asset_path on LandscapeLayerInfo_Custom | Property is layer.layer_info_path (NOT asset_path) |
| Guessing random offset like 110000 for side-by-side | Calculate: offset = (resolution - 1) * scale.x e.g. (1009-1)*100 = 100800 |
Using FoliageService.clear_all_foliage() thinking it only clears one landscape | clear_all_foliage() removes ALL foliage from the ENTIRE level — use remove_foliage_in_radius() or remove_all_foliage_of_type() to target specific areas |
| Manually scattering foliage to replicate a landscape that uses LandscapeGrassType | If foliage is procedural (from LandscapeGrassType on the material), just copy paint layers — foliage auto-generates from weights. Check if list_foliage_types() returns 0; if so, foliage is procedural. |
Using terrain_data without resolution and getting 1081×1081 | Always pass resolution=N matching your landscape. Common values: 505, 1009, 1017, 2033 |
| Assuming 1081×1081 heightmap will fit any landscape | 1081 requires 36×36 components which WILL timeout. Use resolution=1009 (8×8, 63q, 2s) instead |
| Importing a heightmap without checking dimensions | Always call get_heightmap_dimensions() first, then resize_heightmap() if sizes don't match |
| Creating landscape then downloading heightmap (wrong order) | Decide config → calculate resolution → download at that resolution → create landscape → import |
info.scale_x on LandscapeInfo_Custom | info.scale.x — scale, location, rotation are Vector/Rotator structs, not flattened floats |
| Assuming a landscape is centered on its location | It spans +X/+Y from its location (corner-anchored): bounds = location to location + (resolution-1)*scale. A landscape created at (0,0) with 505 res @ scale 100 covers (0,0)–(50400,50400); its center is (25200,25200) |
create_layer_info_object("Grass", "/Game/X", "LI_Grass") | Third param is a bool: create_layer_info_object(layer_name, destination_path, is_weight_blended=True) — passing a string asset name causes TypeError: Cannot nativize 'str' as 'bool' |
AssetTools.create_asset(..., unreal.LandscapeLayerInfoObject, None) to make a layer info | Yields a broken asset with LayerName=None, and layer_name is read-only from Python so it can't be fixed afterwards. Use LandscapeMaterialService.create_layer_info_object(layer_name, path) — or EditorAssetLibrary.duplicate_asset an existing LayerInfo, which keeps its baked LayerName |
Retrying create_landscape after PYTHON_EXECUTION_TIMEOUT | The first call usually still completes in the background — check landscape_exists(label) before retrying (duplicate labels now return an error) |
result.location on LandscapeCreateResult | Result has ONLY success, actor_label, error_message — get location via get_landscape_info(label).location |
result.resolution_x / .resolution_y on HeightmapImportResult | The field is result.resolution — a string like "1009x1009", not separate ints. Split it if you need numbers. |
Treating get_height_at_location() as a float | It returns a LandscapeHeightSample struct — use .height (float) and .valid (bool); float(sample) / f"{sample}" raise TypeError |
| Clearing a World-Partition landscape (parent + streaming proxies) | delete_landscape(label) now removes the streaming proxies too by default (include_proxies=True): it unregisters each LandscapeStreamingProxy sharing the landscape's GUID from the LandscapeInfo, destroys the proxies, then the parent. Pass include_proxies=False for the parent actor only. Do NOT destroy_actor the proxies yourself — destroying a proxy while the LandscapeInfo still references it crashes the editor (access violation in the Landscape module). With the editor closed, deleting the proxy package files under __ExternalActors__ is the other safe route. |
info.component_count_x / info.component_count_y | info.num_components is the TOTAL count (e.g. 64 for 8×8) — there are no per-axis fields |
info.rotation.x (Rotator) | Rotator fields are .roll / .pitch / .yaw — there is no .x/.y/.z on Rotator (only Vector has those) |
| Task | Skills to Load |
|---|---|
| Sculpt terrain only | landscape |
| Simple painted material (LayerBlend) | landscape + landscape-materials |
| Production auto-material (material functions, RVT, instances) | landscape + landscape-auto-material |
| Material instance / biome configuration | landscape-auto-material |
LandscapeLayerBlend nodes. Good for prototyping with 2-5 layers.When you need to create or modify the material applied to a landscape, load the appropriate
material skill through GetSkills (AgentSkillToolset):
landscape-materialslandscape-auto-materialNOTE: For landscape material, texture, and layer blending operations, use
LandscapeMaterialService(load thelandscape-materialsorlandscape-auto-materialskill).
execute_python_code)scripts/sculpt_terrain.txt — create a landscape and sculpt procedural mountain/valley features.line_trace_single grid; the only safe write path is export_heightmap -> stamp in Python -> import_heightmap. Never synthesise a flat baseline — a flat-plus-pit import erases carved features.world = min + px * scale, worldZ = (H - 32768) * 100/128, no row flip; check against a known trace height before writing.import_heightmap does not write proxies to disk — call save_landscape (or save_packages the Landscape and every proxy). Use get_last_landscape_write to see what changed, since the dirty list is unreliable right after landscape calls.flatten_at_location and set_height_in_region read and write the same edit layer now, so a region write stays inside its brush on World Partition; apply_erosion was always safe.load_level from Python leaves all WP cells unloaded for the rest of the session, and create_landscape run headless produces a broken landscape (zero heights, no collision).delete_landscape(include_proxies=True); destroying proxy actors by hand crashes the editor.apply_splines_to_landscape); re-verify landmarks by trace after any landscape-touching op.WaterBrushManager (affects_landscape defaults True) that adds a "Water" edit layer and poisons the terrain; flip that CDO default first and delete any spawned manager. WaterZone.zone_extent is on the actor; the river/lake seam material is lake_transition_material; target_wave_mask_depth is the still-water knob.© kevinpbuckley, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 5 other files (scripts) in Content/Skills/landscape of kevinpbuckley/VibeUE.
Open the folder on GitHubat commit dc051be
Landscape next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Landscape this skillkevinpbuckley/VibeUE | 730 | — | ~7k | Automated safety check: Pass | MIT | |
| Image to Three.js Modelimg2threejs/img2threejs | 18k | 1 repos | ~8.2k | Automated safety check: Pass | Apache-2.0 | |
| Web CloneJane-xiaoer/claude-skill-web-clone | 1k | 1 repos | ~2.7k | Automated safety check: Pass | MIT | |
| Threejs Game Directormajidmanzarpour/threejs-game-skills | 2.5k | — | ~2.2k | Automated safety check: Pass | MIT | |
| Game Asset Generatorhtdt/godogen | 7.1k | — | ~2.8k | Automated safety check: Pass | MIT | |
| Threejs Gameplay Systemsvalkor-ai/loom | 1.2k | 1 repos | ~1.4k | Automated safety check: Pass | Apache-2.0 |
img2threejs/img2threejs
Rebuilds the object in a reference image as a procedural, animation-ready Three.js model written entirely in code, using staged sculpting with quality checks.
Jane-xiaoer/claude-skill-web-clone
网站复刻 / 克隆方法论。USE WHEN 用户说 复刻网站、克隆网站、clone website、抄个站、仿站、 照着这个站做一个、reproduce site、还原某个网页效果、把这个站搬下来改成我的、 复刻某个交互/WebGL/Canvas/Three.js 效果。提供「先拿真源码 → 判路径 → 逆向拆解 → 搭工程 → 替换内容」的可移植决策树,覆盖静态站 /…
majidmanzarpour/threejs-game-skills
Entrypoint for building, upgrading, and finishing Three.js browser games.
htdt/godogen
Generates game art from text prompts: PNG images, GLB 3D models, rigged characters, animations and sprites, with background removal.
valkor-ai/loom
Build and iterate playable Three.js game systems: starter scaffold, architecture, design briefs, core loops, level and encounter design, entities, input, camera, collision and physics, scoring…
CyberAgentGameEntertainment/NovaShader
Execute C with Unity APIs when existing uloop tools cannot inspect or edit enough.
kevinpbuckley/VibeUE
Previews, validates and bakes bone-rotation edits on Unreal Engine AnimSequences, enforcing a coordinate space and a constraint-checked preview-validate-bake loop.
kevinpbuckley/VibeUE
Creates and edits Unreal Engine Animation Montages through AnimMontageService: sections, slots, segments, branching points, notifies and blend settings.
kevinpbuckley/VibeUE
Creates and edits AnimSequence keyframes and bone tracks in Unreal Engine through AnimSequenceService, following an inspect, preview, validate and bake workflow with correct bone-space handling.
kevinpbuckley/VibeUE
Imports files into Unreal Engine, exports textures, reads the Content Browser selection and checks open assets via VibeUE, plus mesh reimport material fixes.
kevinpbuckley/VibeUE
Changes Unreal Engine console variables, scalability levels and raw INI values through VibeUE's Python API, now that registered settings moved to the engine's own toolset.
kevinpbuckley/VibeUE
Creates and wires Unreal Engine Enhanced Input assets through VibeUE's InputService: Input Actions, Mapping Contexts, key bindings, triggers and modifiers.
Categories
Create and edit landscape terrain — heightmaps, sculpting, paint layers, terrain analysis, mesh projection, and procedural features like mountains/valleys/craters (LandscapeService). Landscape is an agent skill from kevinpbuckley/VibeUE. Create and edit landscape terrain — heightmaps, sculpting, paint layers, terrain analysis, mesh projection, and procedural features like mountains/valleys/craters (LandscapeService).
Landscape fits situations like: the user asks to create a landscape; sculpt/raise/lower terrain; export a heightmap; paint terrain layers.
Run `npx skills add kevinpbuckley/VibeUE --skill landscape -a claude-code`. Or copy the skill folder (Content/Skills/landscape in kevinpbuckley/VibeUE) into .claude/skills/landscape in your project. Claude Code loads it when a task matches its description.
Run `npx skills add kevinpbuckley/VibeUE --skill landscape -a codex`. Or copy the skill folder (Content/Skills/landscape in kevinpbuckley/VibeUE) into .agents/skills/landscape in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add kevinpbuckley/VibeUE --skill landscape -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/landscape, .gemini/skills/landscape, .github/skills/landscape and .opencode/skills/landscape in your project.
Going by SKILL.md and its folder, Landscape needs the command-line tools its instructions call (git). Our summary lists: Python 3.
SKILL.md contains no URLs. Its commands use git, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
Landscape is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 7k tokens (SKILL.md is roughly 28k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Landscape: Image to Three.js Model (img2threejs/img2threejs, 18k stars), Web Clone (Jane-xiaoer/claude-skill-web-clone, 1k stars), Threejs Game Director (majidmanzarpour/threejs-game-skills, 2.5k stars) and Game Asset Generator (htdt/godogen, 7.1k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
kevinpbuckley (a GitHub user) maintains it in kevinpbuckley/VibeUE, which has 730 GitHub stars. The repository holds 39 skills in this directory. The repository was last updated on October 7, 2026.
Source: kevinpbuckley/VibeUE on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.