Editor/WeaponImporter/Tool/UI/Steps/MaterialsStep.cs
using Editor;
using Sandbox;
using WeaponImporter.Core.Materials;
namespace WeaponImporter.Tool.UI;
/// <summary>
/// Materials: which texture fills each material slot. Attach Textures (on by default) uses the
/// images found beside the model that belong to its materials; sure matches are attached, weaker
/// ones are proposed. Click a row to use or skip that texture.
/// </summary>
public sealed class MaterialsStep : StepPanel
{
public MaterialsStep( Widget parent, ImporterController controller ) : base( parent, controller )
{
}
public override string Title => "Materials";
protected override string StructureKey()
{
var s = C.Setup;
return $"{s.AttachTextures}|{string.Join( ",", s.TextureChoices.Select( kv => kv.Key + "=" + kv.Value ) )}|{C.Session.TextureMatches.Count}|{System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode( C.Analysis )}";
}
protected override void Build()
{
var session = C.Session;
var s = C.Setup;
var matches = session.TextureMatches;
var found = matches.Where( m => !m.Linked ).ToList();
var used = found.Count( m => ImportSession.Accepted( s, m ) );
var card = AddCard( "texture", "Attach Textures", out var header, "Images beside the model that belong to its materials but were never linked" );
header.AddStretchCell();
header.Add( new Pill( card, found.Count == 0 ? "NONE FOUND" : $"{used} / {found.Count} USED", found.Count == 0 ? Theme.TextLight : Theme.Green ) );
var toggle = card.Layout.Add( new Checkbox( "Attach textures found beside the model", card ) { Value = s.AttachTextures } );
toggle.ToolTip = "Match image files next to the model (and in texture folders) to material slots by name. Sure matches are attached; weaker ones are shown for you to confirm.";
toggle.StateChanged = _ =>
{
if ( s.AttachTextures == toggle.Value )
return;
s.AttachTextures = toggle.Value;
Reapply();
};
if ( found.Count == 0 )
card.Layout.Add( UiStyle.Muted( new Label( matches.Count == 0 ? "No textures linked or found beside the model." : "Every texture the materials use is linked in the file.", card ) { WordWrap = true }, small: true ) );
// One card per material with its slots.
foreach ( var group in matches.GroupBy( m => m.MaterialName ) )
{
var materialCard = AddCard( "palette", group.Key, out var h, "Material" );
foreach ( var slotGroup in group.GroupBy( m => m.Slot ).OrderBy( g => g.Key ) )
foreach ( var match in slotGroup.OrderByDescending( m => m.Confidence ) )
AddRow( materialCard, match );
}
}
private void AddRow( Card card, TextureMatch match )
{
var s = C.Setup;
var accepted = ImportSession.Accepted( s, match );
var tip = $"{match.Path}\n{match.Reason}{(match.Linked ? "" : $" · {match.Confidence * 100:0}% sure")}" + (match.Linked ? "" : accepted ? "\nClick to leave this slot empty." : "\nClick to use this texture.");
var row = card.Layout.Add( new ClickRow( card, match.Linked ? null : () => Toggle( match, !accepted ), tip ) );
row.Layout.Add( UiStyle.Muted( new Label( TextureMatch.Label( match.Slot ), row ) { FixedWidth = 84, FixedHeight = UiStyle.ControlHeight, Alignment = TextFlag.LeftCenter } ) );
var name = row.Layout.Add( new Label( UiStyle.Breakable( System.IO.Path.GetFileName( match.Path ) ), row ) { WordWrap = true, MinimumHeight = UiStyle.ControlHeight, Alignment = TextFlag.LeftCenter, MinimumWidth = 40 }, 1 );
if ( !accepted )
UiStyle.Muted( name );
if ( match.Linked )
row.Layout.Add( new Pill( row, "LINKED", Theme.TextLight, "Linked in the model file", column: true ) );
else if ( accepted )
row.Layout.Add( new Pill( row, match.Confidence >= TextureMatcher.AutoAttach ? "ATTACHED" : "USED", Theme.Green, $"{match.Confidence * 100:0}% sure: {match.Reason}", column: true ) );
else
row.Layout.Add( new Pill( row, $"{match.Confidence * 100:0}%", Theme.Yellow, $"Proposed: {match.Reason}", column: true ) );
}
private void Toggle( TextureMatch match, bool use )
{
var s = C.Setup;
s.TextureChoices[match.Key] = use ? match.Path : "";
Reapply();
}
private void Reapply()
{
var session = C.Session;
ForceRebuild();
_ = C.RunAsync( "Attaching textures", ( p, c ) => session.ApplyTextureChoicesAsync( p, c ) );
}
}