# Material and Material Instance Workflow

All material operations use `execute_python` with `import unreal`. Always verify results with `print()`.

## Material system architecture

Unreal materials are **shader graphs** — visual programs compiled to GPU shaders. The key objects:

- **UMaterial** — the base material asset. Contains a graph of material expression nodes connected to material property inputs (BaseColor, Normal, Roughness, Metallic, etc.). Editing a material recompiles shaders for all instances.
- **UMaterialInstanceConstant** — editor-time override of parent material parameters. No shader recompilation — only parameter values change. This is what you assign to meshes in most cases.
- **UMaterialInstanceDynamic (MID)** — runtime-created instance for per-actor parameter changes (health bars, damage flash, dissolve effects). Created in C++/Blueprint, not in editor.
- **UMaterialFunction** — reusable sub-graph (like a function). Can be shared across multiple materials.

## Workflow overview

The general workflow for creating a complete material via Python:

1. **Create the material asset** — `query_ue_api('MaterialFactoryNew')` to find the factory class.
2. **Set material properties** — blend mode, shading model, two-sided, material domain. These are editor properties on the material object (use `set_editor_property`).
3. **Add expression nodes** — each expression is a node in the shader graph (texture sample, constant, math operations, etc.). `query_ue_api('MaterialExpression')` to find expression classes. Expressions are positioned with X,Y coordinates on the graph canvas.
4. **Connect expressions to each other** — wire one expression's output pin to another's input pin (e.g., texture RGB → multiply A).
5. **Connect expressions to material property inputs** — wire final expression outputs to material properties like BaseColor, Normal, Roughness. Material properties are defined in the `MaterialProperty` enum.
6. **Recompile the material** — required after any graph modification. `query_ue_api('recompile material')` to find the function.
7. **Save the asset** — `query_ue_api('save asset')` to find the save method.

## Material expressions (shader graph nodes)

Material expressions are the building blocks of the shader graph. Each expression is a UClass derived from `UMaterialExpression`. Common categories:

- **Texture sampling** — reads texture data (diffuse, normal, roughness maps)
- **Constants** — scalar values, vector/color values, static parameters
- **Math** — add, multiply, lerp, clamp, power, dot product, normalize
- **Coordinates** — texture coordinates, world position, vertex normal
- **Utility** — component mask, append, break material attributes
- **Scene** — scene color, scene depth (for post-process materials)

Expression nodes have **output pins** with names like `''` (default/unnamed), `'RGB'`, `'R'`, `'G'`, `'B'`, `'A'`. When connecting, you reference these pin names.

`query_ue_api('MaterialExpression')` to discover available expression types, then use `dir()` on the expression object in Python to inspect its properties and pins.

## Connection concepts

There are two types of connections in a material graph:

- **Expression → Expression**: wires one node's output to another node's input. For example, wiring a texture sample's RGB output into a Multiply node's A input.
- **Expression → Material Property**: wires a node's output to one of the material's final property inputs. Material properties are enumerated in the `MaterialProperty` enum (discover values via `query_ue_api`). Common properties: BaseColor, Normal, Roughness, Metallic, Specular, EmissiveColor, Opacity, OpacityMask, WorldPositionOffset.

`query_ue_api('MaterialEditingLibrary')` to find the editing library and its connection functions.

## Material properties (blend mode, shading model, domain)

Materials have properties set via `set_editor_property()`:

- **Blend mode** — controls transparency. Values in the `BlendMode` enum: Opaque (default, fastest), Masked (binary alpha via OpacityMask pin), Translucent (full alpha blending, expensive). `query_ue_api('EBlendMode')` to find values.
- **Shading model** — how the material responds to light. Values in the `MaterialShadingModel` enum: DefaultLit, Unlit, Subsurface, ClearCoat, etc. `query_ue_api('EMaterialShadingModel')` to find values.
- **Material domain** — where the material is used. Values in the `MaterialDomain` enum: Surface (3D objects), PostProcess (screen-space effects), UI, LightFunction. `query_ue_api('EMaterialDomain')` to find values.
- **Two-sided** — boolean, renders both faces of triangles (useful for foliage, curtains).

## Material instances

Material instances override **parameters** exposed by the parent material without recompiling shaders. This is the preferred way to create material variations (e.g., same PBR shader but different color/roughness/textures).

Parameter types: Scalar (float), Vector (color/RGBA), Texture, StaticSwitch (compile-time branching).

To create a material instance via Python:
1. Load the parent material
2. `query_ue_api('MaterialInstanceConstantFactoryNew')` to find the factory class
3. Set the factory's `initial_parent` property to the parent material
4. Create the asset via `create_asset()`
5. Set parameter values — `query_ue_api('MaterialEditingLibrary')` to find parameter-setting functions
6. Save

## Post-process materials

Post-process materials operate on screen-space pixels rather than 3D geometry. To create one:
1. Create a regular material
2. Set `material_domain` to PostProcess — `query_ue_api('EMaterialDomain')` to find enum values
3. Use scene-related expressions (scene color, scene depth) as inputs — `query_ue_api('MaterialExpression scene')` to find these classes
4. Connect to the EmissiveColor material property (this is the final output for post-process materials)

Post-process materials are applied via PostProcessVolume or camera PostProcessSettings.

## Dynamic Material Instance (runtime parameter changes)

In C++ (stable API — does not change between UE versions):
```cpp
// In BeginPlay:
UMaterialInterface* BaseMat = MeshComp->GetMaterial(0);
DynMaterial = UMaterialInstanceDynamic::Create(BaseMat, this);
MeshComp->SetMaterial(0, DynMaterial);

// Change parameter at runtime:
DynMaterial->SetScalarParameterValue(TEXT("Opacity"), 0.5f);
DynMaterial->SetVectorParameterValue(TEXT("Color"), FLinearColor::Red);
```

## Applying materials to actors

To apply a material to a mesh component in the level:
1. Get a reference to the actor
2. Get the mesh component (StaticMeshComponent, SkeletalMeshComponent)
3. Call `set_material(slot_index, material)` on the component
4. Slot index 0 is the first material slot — use a loop over `get_num_materials()` to set all slots

## Discovering material API at runtime

Use `query_ue_api` with these keywords, then `dir(unreal.ClassName)` to inspect methods:

| What you need | query_ue_api keyword |
|---|---|
| Asset creation factory | `MaterialFactoryNew` |
| Editing library (expressions, connections, recompile) | `MaterialEditingLibrary` |
| Expression node types | `MaterialExpression` |
| Material instance factory | `MaterialInstanceConstantFactoryNew` |
| Blend mode enum | `EBlendMode` |
| Shading model enum | `EMaterialShadingModel` |
| Material domain enum | `EMaterialDomain` |
| Material property enum | `EMaterialProperty` |
| Dynamic material (runtime) | `MaterialInstanceDynamic` |

## Common PBR material recipes

These are conceptual recipes — `query_ue_api('MaterialExpression')` for expression classes, `query_ue_api('EBlendMode')` for blend modes:

- **Opaque PBR**: BaseColor texture + Normal map + Roughness (0.1-1.0) + Metallic (0 or 1). Most common material type.
- **Glass**: Translucent blend mode, low Roughness (~0.1), high Specular, Opacity < 1.0
- **Emissive/glow**: Multiply EmissiveColor by high constant (10-100) for bloom effect
- **Masked foliage**: Masked blend mode, OpacityMask from texture Alpha channel, TwoSided=true
- **Layered terrain**: Use landscape layer blend expressions for multi-texture blending
- **Decal**: Set material domain to Deferred Decal, apply via DecalComponent

Material instances allow varying parameters (color, roughness, textures) without recompiling shaders — always prefer instances over duplicating base materials.

## Naming conventions

- `M_` prefix for base materials: M_Ground, M_Metal, M_Glass
- `MI_` prefix for material instances: MI_Ground_Mud, MI_Metal_Rusty
- `MF_` prefix for material functions: MF_WindAnimation, MF_VertexBlend
- `T_` prefix for textures with suffixes: `_D` (Diffuse), `_N` (Normal), `_R` (Roughness), `_M` (Metallic), `_E` (Emissive), `_O` (AO), `_A` (Alpha)
- `PP_` prefix for post-process materials

## Critical rules

- Always recompile the material after modifying the expression graph — `query_ue_api('recompile material')` to find the function
- Always save after modifying — `query_ue_api('save asset')` to find the method
- Material expressions are positioned with X,Y coordinates on the graph canvas (negative X = left of the material output node)
- Output pin names for texture samples: `''` (default), `'RGB'`, `'R'`, `'G'`, `'B'`, `'A'`
- Use `query_ue_api` with specific keywords (see table above) — do NOT guess class names
- Check `does_asset_exist()` before creating to avoid duplicates
- Load assets with `unreal.load_asset(path)` and verify the result is not None before using
- Content paths use `/Game/` prefix (e.g., `/Game/Materials/M_Ground`), NOT filesystem paths
