Editor/AssetContextMenuHelper.cs
using Editor;
using Sandbox;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace GeneralGame.Editor;
/// <summary>
/// Helper for building asset context menus with Create Material/Texture options.
/// </summary>
public static class AssetContextMenuHelper
{
/// <summary>
/// Add asset-type specific options like Create Material, Create Texture, etc.
/// </summary>
public static void AddAssetTypeOptions(Menu menu, Asset asset)
{
if (asset == null) return;
var assetType = asset.AssetType;
if (assetType == null) return;
// Image files - can create Material, Texture, Sprite
if (assetType == AssetType.ImageFile)
{
menu.AddSeparator();
menu.AddOption("Create Material", "image", () => CreateMaterialFromImage(asset));
menu.AddOption("Create Texture", "texture", () => CreateTextureFromImage(asset));
menu.AddOption("Create Sprite", "emoji_emotions", () => CreateSpriteFromImage(asset));
}
// Shader files - can create Material
if (assetType == AssetType.Shader)
{
menu.AddSeparator();
menu.AddOption("Create Material", "image", () => CreateMaterialFromShader(asset));
}
// Mesh files (FBX, OBJ) - can create Model
var meshExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { ".fbx", ".obj", ".dmx", ".gltf", ".glb" };
if (meshExtensions.Contains(Path.GetExtension(asset.AbsolutePath)))
{
menu.AddSeparator();
menu.AddOption("Create Model", "view_in_ar", () => CreateModelFromMesh(asset));
}
}
private static void CreateTextureFromImage(Asset asset)
{
var assetName = asset.Name;
var fd = new FileDialog(null);
fd.Title = "Create Texture from Image..";
fd.Directory = Path.GetDirectoryName(asset.AbsolutePath);
fd.DefaultSuffix = ".vtex";
fd.SelectFile($"{assetName}.vtex");
fd.SetFindFile();
fd.SetModeSave();
fd.SetNameFilter("Texture File (*.vtex)");
if (!fd.Execute())
return;
var imagePath = asset.RelativePath;
// Create simple vtex JSON structure
var vtexContent = new Dictionary<string, object>
{
{ "Sequences", new object[]
{
new Dictionary<string, object>
{
{ "Source", imagePath },
{ "IsLooping", true }
}
}
}
};
var json = Json.Serialize(vtexContent);
File.WriteAllText(fd.SelectedFile, json);
AssetSystem.RegisterFile(fd.SelectedFile);
}
private static void CreateMaterialFromImage(Asset asset)
{
string[] types = new[] { "color", "ao", "normal", "metallic", "rough", "diff", "diffuse", "nrm", "spec", "selfillum", "mask" };
var assetName = asset.Name;
foreach (var t in types)
{
if (assetName.EndsWith($"_{t}"))
assetName = assetName.Substring(0, assetName.Length - (t.Length + 1));
}
var fd = new FileDialog(null);
fd.Title = "Create Material from Image..";
fd.Directory = Path.GetDirectoryName(asset.AbsolutePath);
fd.DefaultSuffix = ".vmat";
fd.SelectFile($"{assetName}.vmat");
fd.SetFindFile();
fd.SetModeSave();
fd.SetNameFilter("Material File (*.vmat)");
if (!fd.Execute())
return;
var assetPath = Path.GetDirectoryName(asset.AbsolutePath).NormalizeFilename(false);
var assetPeers = AssetSystem.All
.Where(x => x.AssetType == AssetType.ImageFile)
.Where(x => x.AbsolutePath.StartsWith(assetPath))
.ToArray();
var assetPeersWithSameBaseName = assetPeers
.Where(x => x.Name == assetName || x.Name.StartsWith(assetName + "_"))
.ToArray();
if (assetPeersWithSameBaseName.Length > 0)
{
assetPeers = assetPeersWithSameBaseName;
}
string texColor = assetPeers.Where(x => x.Name.Contains("_color") || x.Name.Contains("_diff")).Select(x => x.RelativePath).FirstOrDefault();
texColor ??= asset.RelativePath;
string texNormal = assetPeers.Where(x => x.Name.Contains("_nrm") || x.Name.Contains("_normal") || x.Name.Contains("_amb")).Select(x => x.RelativePath).FirstOrDefault() ?? "materials/default/default_normal.tga";
string texAo = assetPeers.Where(x => x.Name.Contains("_ao") || x.Name.Contains("_occ") || x.Name.Contains("_amb")).Select(x => x.RelativePath).FirstOrDefault() ?? "materials/default/default_ao.tga";
string texRough = assetPeers.Where(x => x.Name.Contains("_rough")).Select(x => x.RelativePath).FirstOrDefault() ?? "materials/default/default_rough.tga";
string texMetallic = assetPeers.Where(x => x.Name.Contains("_metallic")).Select(x => x.RelativePath).FirstOrDefault();
if (texMetallic != null)
{
texMetallic = $"\n\tF_METALNESS_TEXTURE 1\n\tF_SPECULAR 1\n\tTextureMetalness \"{texMetallic}\"";
}
string texSelfIllum = assetPeers.Where(x => x.Name.Contains("_selfillum")).Select(x => x.RelativePath).FirstOrDefault();
if (texSelfIllum != null)
{
texSelfIllum = $"\n\tF_SELF_ILLUM 1\n\tTextureSelfIllumMask \"{texSelfIllum}\"";
}
string tintMask = assetPeers.Where(x => x.Name.Contains("_mask")).Select(x => x.RelativePath).FirstOrDefault();
if (tintMask != null)
{
tintMask = $"\n\tF_TINT_MASK 1\n\tTextureTintMask \"{tintMask}\"";
}
var file = $@"
Layer0
{{
shader ""shaders/complex.shader_c""
TextureColor ""{texColor}""
TextureAmbientOcclusion ""{texAo}""
TextureNormal ""{texNormal}""
TextureRoughness ""{texRough}""{texMetallic}{texSelfIllum}{tintMask}
}}
";
File.WriteAllText(fd.SelectedFile, file);
AssetSystem.RegisterFile(fd.SelectedFile);
}
private static async void CreateSpriteFromImage(Asset asset)
{
var assetName = asset.Name;
var fd = new FileDialog(null);
fd.Title = "Create Sprite from Image..";
fd.Directory = Path.GetDirectoryName(asset.AbsolutePath);
fd.DefaultSuffix = ".sprite";
fd.SelectFile($"{assetName}.sprite");
fd.SetFindFile();
fd.SetModeSave();
fd.SetNameFilter("Sprite File (*.sprite)");
if (!fd.Execute())
return;
var path = Path.ChangeExtension(asset.Path, Path.GetExtension(asset.AbsolutePath));
var sprite = Sprite.FromTexture(Texture.Load(path));
var json = sprite.Serialize().ToJsonString();
File.WriteAllText(fd.SelectedFile, json);
var resultAsset = AssetSystem.RegisterFile(fd.SelectedFile);
while (!resultAsset.IsCompiledAndUpToDate)
{
await Task.Delay(10);
}
}
private static void CreateMaterialFromShader(Asset asset)
{
var assetName = asset.Name;
var fd = new FileDialog(null);
fd.Title = "Create Material from Shader..";
fd.Directory = Path.GetDirectoryName(asset.AbsolutePath);
fd.DefaultSuffix = ".vmat";
fd.SelectFile($"{assetName}.vmat");
fd.SetFindFile();
fd.SetModeSave();
fd.SetNameFilter("Material File (*.vmat)");
if (!fd.Execute())
return;
var shaderPath = asset.GetCompiledFile();
var file = $@"
Layer0
{{
shader ""{shaderPath}""
}}
";
File.WriteAllText(fd.SelectedFile, file);
AssetSystem.RegisterFile(fd.SelectedFile);
}
private static void CreateModelFromMesh(Asset asset)
{
var targetPath = EditorUtility.SaveFileDialog("Create Model..", "vmdl", Path.ChangeExtension(asset.AbsolutePath, "vmdl"));
if (targetPath == null)
return;
EditorUtility.CreateModelFromMeshFile(asset, targetPath);
}
}