diff --git a/Assemblies/AIImages.dll b/Assemblies/AIImages.dll index 35453bd..1045dbb 100644 Binary files a/Assemblies/AIImages.dll and b/Assemblies/AIImages.dll differ diff --git a/Source/AIImages/Models/PawnAppearanceData.cs b/Source/AIImages/Models/PawnAppearanceData.cs index 41c3e91..60e5664 100644 --- a/Source/AIImages/Models/PawnAppearanceData.cs +++ b/Source/AIImages/Models/PawnAppearanceData.cs @@ -16,6 +16,7 @@ namespace AIImages.Models public string BodyType { get; set; } public Color SkinColor { get; set; } public string HairStyle { get; set; } + public string HairDefName { get; set; } public Color HairColor { get; set; } public List Traits { get; set; } public List Apparel { get; set; } @@ -33,7 +34,9 @@ namespace AIImages.Models public class ApparelData { public string Label { get; set; } + public string DefName { get; set; } public string Material { get; set; } + public string MaterialDefName { get; set; } public QualityCategory? Quality { get; set; } public Color Color { get; set; } public string LayerType { get; set; } diff --git a/Source/AIImages/Models/StableDiffusionSettings.cs b/Source/AIImages/Models/StableDiffusionSettings.cs index 0b684de..634d918 100644 --- a/Source/AIImages/Models/StableDiffusionSettings.cs +++ b/Source/AIImages/Models/StableDiffusionSettings.cs @@ -15,7 +15,6 @@ namespace AIImages.Models public int Seed { get; set; } public string Model { get; set; } public ArtStyle ArtStyle { get; set; } - public ShotType ShotType { get; set; } public StableDiffusionSettings() { @@ -27,7 +26,6 @@ namespace AIImages.Models Sampler = "Euler a"; Seed = -1; // Случайный seed ArtStyle = ArtStyle.Realistic; - ShotType = ShotType.Portrait; PositivePrompt = ""; NegativePrompt = "ugly, deformed, low quality, blurry, bad anatomy, worst quality"; } @@ -38,6 +36,7 @@ namespace AIImages.Models /// public enum ArtStyle { + None, // Без стиля Realistic, SemiRealistic, Anime, @@ -47,16 +46,4 @@ namespace AIImages.Models Sketch, CellShaded, } - - /// - /// Тип кадра/композиции - /// - public enum ShotType - { - Portrait, // Портрет (голова и плечи) - HalfBody, // Половина тела - FullBody, // Полное тело - CloseUp, // Крупный план - ThreeQuarter, // Три четверти - } } diff --git a/Source/AIImages/Services/AdvancedPromptGenerator.cs b/Source/AIImages/Services/AdvancedPromptGenerator.cs index cda4066..b927114 100644 --- a/Source/AIImages/Services/AdvancedPromptGenerator.cs +++ b/Source/AIImages/Services/AdvancedPromptGenerator.cs @@ -39,6 +39,7 @@ namespace AIImages.Services string > { + { ArtStyle.None, "" }, { ArtStyle.Realistic, "photorealistic, hyperrealistic, realistic photo, photography" }, { ArtStyle.SemiRealistic, "semi-realistic, detailed illustration, realistic art" }, { ArtStyle.Anime, "anime style, manga style, anime character" }, @@ -52,18 +53,6 @@ namespace AIImages.Services { ArtStyle.CellShaded, "cell shaded, flat colors, toon shading, stylized" }, }; - private static readonly Dictionary ShotTypePrompts = new Dictionary< - ShotType, - string - > - { - { ShotType.Portrait, "portrait, head and shoulders" }, - { ShotType.HalfBody, "half body shot, waist up" }, - { ShotType.FullBody, "full body, full length" }, - { ShotType.CloseUp, "close up, face focus, detailed face" }, - { ShotType.ThreeQuarter, "three-quarter view, 3/4 view" }, - }; - public string GeneratePositivePrompt( PawnAppearanceData appearanceData, StableDiffusionSettings settings @@ -75,18 +64,17 @@ namespace AIImages.Services StringBuilder prompt = new StringBuilder(); // 1. Художественный стиль - if (ArtStylePrompts.TryGetValue(settings.ArtStyle, out string stylePrompt)) + if ( + ArtStylePrompts.TryGetValue(settings.ArtStyle, out string stylePrompt) + && !string.IsNullOrEmpty(stylePrompt) + ) { prompt.Append(stylePrompt); prompt.Append(", "); } - // 2. Тип кадра - if (ShotTypePrompts.TryGetValue(settings.ShotType, out string shotPrompt)) - { - prompt.Append(shotPrompt); - prompt.Append(" of "); - } + // 2. Тип кадра - автоматически добавляем "portrait" для генерации персонажей + prompt.Append("portrait, head and shoulders of "); // 3. Базовое описание (возраст и пол) prompt.Append(GetAgeAndGenderDescription(appearanceData)); @@ -157,16 +145,18 @@ namespace AIImages.Services ); // Специфичные для стиля негативы - if ( - settings.ArtStyle == ArtStyle.Realistic - || settings.ArtStyle == ArtStyle.SemiRealistic - ) + switch (settings.ArtStyle) { - negativePrompt.Append("cartoon, anime, painting, drawing, illustration, "); - } - else if (settings.ArtStyle == ArtStyle.Anime) - { - negativePrompt.Append("realistic, photo, photography, 3d, "); + case ArtStyle.Realistic: + case ArtStyle.SemiRealistic: + negativePrompt.Append("cartoon, anime, painting, drawing, illustration, "); + break; + case ArtStyle.Anime: + negativePrompt.Append("realistic, photo, photography, 3d, "); + break; + case ArtStyle.None: + // Без дополнительных негативных промптов для стиля None + break; } // Пользовательский негативный промпт @@ -244,7 +234,7 @@ namespace AIImages.Services private string GetHairDescription(PawnAppearanceData data) { - if (string.IsNullOrEmpty(data.HairStyle)) + if (string.IsNullOrEmpty(data.HairDefName)) return ""; StringBuilder hair = new StringBuilder(); @@ -254,10 +244,8 @@ namespace AIImages.Services hair.Append(hairColor); hair.Append(" "); - // Стиль прически (упрощаем сложные названия) - string style = data - .HairStyle.ToLower() - .Replace("_", " ") + // Стиль прически - используем DefName для английского названия + string style = CleanDefName(data.HairDefName) .Replace("shaved", "very short") .Replace("mohawk", "mohawk hairstyle"); @@ -310,15 +298,15 @@ namespace AIImages.Services itemDesc.Append(" "); } - // Материал - if (!string.IsNullOrEmpty(item.Material)) + // Материал - используем DefName для английского названия + if (!string.IsNullOrEmpty(item.MaterialDefName)) { - itemDesc.Append(item.Material.ToLower()); + itemDesc.Append(CleanDefName(item.MaterialDefName)); itemDesc.Append(" "); } - // Название предмета - itemDesc.Append(item.Label.ToLower()); + // Название предмета - используем DefName для английского названия + itemDesc.Append(CleanDefName(item.DefName)); items.Add(itemDesc.ToString()); } @@ -332,22 +320,56 @@ namespace AIImages.Services { var baseTags = "highly detailed, professional, masterpiece, best quality"; - if (style == ArtStyle.Realistic || style == ArtStyle.SemiRealistic) + switch (style) { - return $"{baseTags}, professional photography, 8k uhd, dslr, high quality, sharp focus"; - } - else if (style == ArtStyle.Anime) - { - return $"{baseTags}, anime masterpiece, high resolution, vibrant colors"; - } - else if (style == ArtStyle.ConceptArt) - { - return $"{baseTags}, trending on artstation, professional digital art"; - } - else - { - return baseTags; + case ArtStyle.None: + return baseTags; + case ArtStyle.Realistic: + case ArtStyle.SemiRealistic: + return $"{baseTags}, professional photography, 8k uhd, dslr, high quality, sharp focus"; + case ArtStyle.Anime: + return $"{baseTags}, anime masterpiece, high resolution, vibrant colors"; + case ArtStyle.ConceptArt: + return $"{baseTags}, trending on artstation, professional digital art"; + default: + return baseTags; } } + + /// + /// Преобразует defName в читаемый английский текст для промпта + /// Пример: "Apparel_Pants" -> "pants", "Synthread" -> "synthread" + /// + private string CleanDefName(string defName) + { + if (string.IsNullOrEmpty(defName)) + return ""; + + string cleaned = defName; + + // Убираем распространенные префиксы RimWorld + cleaned = System.Text.RegularExpressions.Regex.Replace( + cleaned, + "^(Apparel_|Armor_|Weapon_|Thing_)", + "", + System.Text.RegularExpressions.RegexOptions.IgnoreCase + ); + + // Разделяем CamelCase на слова + cleaned = System.Text.RegularExpressions.Regex.Replace( + cleaned, + "([a-z])([A-Z])", + "$1 $2" + ); + + // Заменяем подчеркивания на пробелы + cleaned = cleaned.Replace("_", " "); + + // Убираем множественные пробелы + cleaned = System.Text.RegularExpressions.Regex.Replace(cleaned, @"\s+", " "); + + // Приводим к нижнему регистру и убираем лишние пробелы + return cleaned.Trim().ToLower(); + } } } diff --git a/Source/AIImages/Services/PawnDescriptionService.cs b/Source/AIImages/Services/PawnDescriptionService.cs index 2dace17..18492d8 100644 --- a/Source/AIImages/Services/PawnDescriptionService.cs +++ b/Source/AIImages/Services/PawnDescriptionService.cs @@ -25,6 +25,7 @@ namespace AIImages.Services BodyType = pawn.story.bodyType?.defName, SkinColor = pawn.story.SkinColor, HairStyle = pawn.story.hairDef?.label, + HairDefName = pawn.story.hairDef?.defName, HairColor = pawn.story.HairColor, }; @@ -42,7 +43,9 @@ namespace AIImages.Services var apparelData = new ApparelData { Label = apparel.def.label, + DefName = apparel.def.defName, Material = apparel.Stuff?.label, + MaterialDefName = apparel.Stuff?.defName, Color = apparel.DrawColor, LayerType = apparel.def.apparel?.LastLayer.ToString(), Durability = apparel.HitPoints, diff --git a/Source/AIImages/Settings/AIImagesModSettings.cs b/Source/AIImages/Settings/AIImagesModSettings.cs index 621726e..388b62f 100644 --- a/Source/AIImages/Settings/AIImagesModSettings.cs +++ b/Source/AIImages/Settings/AIImagesModSettings.cs @@ -28,7 +28,6 @@ namespace AIImages.Settings // Художественный стиль public ArtStyle artStyle = ArtStyle.Realistic; - public ShotType shotType = ShotType.Portrait; // Путь для сохранения public string savePath = "AIImages/Generated"; @@ -58,7 +57,6 @@ namespace AIImages.Settings ); Scribe_Values.Look(ref artStyle, "artStyle", ArtStyle.Realistic); - Scribe_Values.Look(ref shotType, "shotType", ShotType.Portrait); Scribe_Values.Look(ref savePath, "savePath", "AIImages/Generated"); @@ -84,7 +82,6 @@ namespace AIImages.Settings Seed = seed, Model = selectedModel, ArtStyle = artStyle, - ShotType = shotType, PositivePrompt = basePositivePrompt, NegativePrompt = baseNegativePrompt, }; diff --git a/Source/AIImages/UI/AIImagesSettingsUI.cs b/Source/AIImages/UI/AIImagesSettingsUI.cs index f09e81a..6c7cd7f 100644 --- a/Source/AIImages/UI/AIImagesSettingsUI.cs +++ b/Source/AIImages/UI/AIImagesSettingsUI.cs @@ -88,25 +88,6 @@ namespace AIImages Find.WindowStack.Add(new FloatMenu(styleOptions)); } - // Shot Type - if ( - listingStandard.ButtonTextLabeled( - "AIImages.Settings.ShotType".Translate(), - settings.shotType.ToString() - ) - ) - { - List shotOptions = new List(); - foreach (ShotType shot in Enum.GetValues(typeof(ShotType))) - { - ShotType localShot = shot; - shotOptions.Add( - new FloatMenuOption(shot.ToString(), () => settings.shotType = localShot) - ); - } - Find.WindowStack.Add(new FloatMenu(shotOptions)); - } - listingStandard.Gap(8f); // Steps diff --git a/Source/AIImages/Window_AIImage.cs b/Source/AIImages/Window_AIImage.cs index 901177e..8476fdd 100644 --- a/Source/AIImages/Window_AIImage.cs +++ b/Source/AIImages/Window_AIImage.cs @@ -269,8 +269,8 @@ namespace AIImages { float imageSize = Mathf.Min(rect.width, 400f); Rect imageRect = new Rect( - (rect.width - imageSize) / 2f, - curY, + rect.x + (rect.width - imageSize) / 2f, + rect.y + curY, imageSize, imageSize ); @@ -282,8 +282,8 @@ namespace AIImages // Placeholder для изображения float placeholderSize = Mathf.Min(rect.width, 300f); Rect placeholderRect = new Rect( - (rect.width - placeholderSize) / 2f, - curY, + rect.x + (rect.width - placeholderSize) / 2f, + rect.y + curY, placeholderSize, placeholderSize ); @@ -299,7 +299,10 @@ namespace AIImages { Text.Font = GameFont.Small; float statusHeight = Text.CalcHeight(generationStatus, rect.width); - Widgets.Label(new Rect(0f, curY, rect.width, statusHeight), generationStatus); + Widgets.Label( + new Rect(rect.x, rect.y + curY, rect.width, statusHeight), + generationStatus + ); curY += statusHeight + 10f; } @@ -307,7 +310,7 @@ namespace AIImages Text.Font = GameFont.Small; if ( Widgets.ButtonText( - new Rect(0f, curY, rect.width, 35f), + new Rect(rect.x, rect.y + curY, rect.width, 35f), isGenerating ? "AIImages.Generation.Generating".Translate() : "AIImages.Generation.Generate".Translate() @@ -321,7 +324,7 @@ namespace AIImages // Промпт секция Text.Font = GameFont.Medium; Widgets.Label( - new Rect(0f, curY, rect.width, 30f), + new Rect(rect.x, rect.y + curY, rect.width, 30f), "AIImages.Prompt.SectionTitle".Translate() ); curY += 35f; @@ -334,7 +337,7 @@ namespace AIImages ); float promptHeight = Mathf.Min(Text.CalcHeight(promptText, rect.width), 150f); - Rect promptRect = new Rect(0f, curY, rect.width, promptHeight); + Rect promptRect = new Rect(rect.x, rect.y + curY, rect.width, promptHeight); // Рисуем промпт в скроллируемой области если он длинный Widgets.DrawBoxSolid(promptRect, new Color(0.1f, 0.1f, 0.1f, 0.5f)); @@ -344,7 +347,7 @@ namespace AIImages // Кнопка копирования промпта if ( Widgets.ButtonText( - new Rect(0f, curY, rect.width / 2f - 5f, 30f), + new Rect(rect.x, rect.y + curY, rect.width / 2f - 5f, 30f), "AIImages.Prompt.CopyButton".Translate() ) ) @@ -356,7 +359,12 @@ namespace AIImages // Кнопка обновления данных if ( Widgets.ButtonText( - new Rect(rect.width / 2f + 5f, curY, rect.width / 2f - 5f, 30f), + new Rect( + rect.x + rect.width / 2f + 5f, + rect.y + curY, + rect.width / 2f - 5f, + 30f + ), "AIImages.Window.Refresh".Translate() ) ) @@ -370,7 +378,7 @@ namespace AIImages curY += 35f; GUI.color = new Color(0f, 1f, 0f, copiedMessageTime / 2f); Widgets.Label( - new Rect(0f, curY, rect.width, 25f), + new Rect(rect.x, rect.y + curY, rect.width, 25f), "AIImages.Prompt.Copied".Translate() ); GUI.color = Color.white;