Add Stable Diffusion prompt generation feature to AIImages. Update UI to include prompt section with copy functionality and localized strings for English and Russian. Update AIImages.dll to include StableDiffusionNet package.

This commit is contained in:
Leonid Pershin
2025-10-26 17:38:48 +03:00
parent 710317e147
commit 990c8695b7
25 changed files with 207 additions and 1 deletions

View File

@@ -37,9 +37,10 @@ namespace AIImages
this.preventCameraMotion = false; // Не блокируем управление камерой
}
public override Vector2 InitialSize => new Vector2(700f, 600f);
public override Vector2 InitialSize => new Vector2(700f, 700f);
private Vector2 scrollPosition = Vector2.zero;
private float copiedMessageTime = 0f;
/// <summary>
/// Обновляет текущую пешку в окне
@@ -71,6 +72,12 @@ namespace AIImages
{
pawn = selectedPawn;
}
// Уменьшаем таймер сообщения о копировании
if (copiedMessageTime > 0f)
{
copiedMessageTime -= Time.deltaTime;
}
}
/// <summary>
@@ -209,6 +216,142 @@ namespace AIImages
sb.AppendLine();
}
/// <summary>
/// Генерирует промпт для Stable Diffusion на основе внешности персонажа
/// </summary>
private string GenerateStableDiffusionPrompt()
{
if (pawn?.story == null)
return "portrait of a person";
StringBuilder prompt = new StringBuilder("portrait of a ");
prompt.Append(GetAgeAndGenderDescription());
prompt.Append(GetBodyTypeDescription());
prompt.Append(GetSkinToneDescription());
prompt.Append(GetHairDescription());
prompt.Append(GetApparelPromptDescription());
prompt.Append(
"realistic, detailed, high quality, professional lighting, 8k, photorealistic"
);
return prompt.ToString();
}
private string GetAgeAndGenderDescription()
{
string ageGroup = pawn.ageTracker.AgeBiologicalYears switch
{
< 18 => "young",
< 30 => "young adult",
< 50 => "middle-aged",
_ => "mature",
};
return $"{ageGroup} {pawn.gender.GetLabel()}, ";
}
private string GetBodyTypeDescription()
{
if (pawn.story.bodyType == null)
return "";
string bodyDesc = pawn.story.bodyType.defName.ToLower() switch
{
"thin" => "slender build",
"hulk" => "muscular build",
"fat" => "heavyset build",
_ => "average build",
};
return $"{bodyDesc}, ";
}
private string GetSkinToneDescription()
{
if (pawn.story.SkinColor == null)
return "";
float brightness =
(pawn.story.SkinColor.r + pawn.story.SkinColor.g + pawn.story.SkinColor.b) / 3f;
string skinTone = brightness switch
{
>= 0.8f => "fair skin",
>= 0.6f => "light skin",
>= 0.4f => "olive skin",
>= 0.2f => "brown skin",
_ => "dark skin",
};
return $"{skinTone}, ";
}
private string GetHairDescription()
{
if (pawn.story.hairDef == null)
return "";
string result = $"{pawn.story.hairDef.label.ToLower()} hair";
if (pawn.story.HairColor != null)
{
result += $", {GetColorDescription(pawn.story.HairColor)} hair color";
}
return result + ", ";
}
private string GetApparelPromptDescription()
{
if (pawn.apparel?.WornApparel == null || !pawn.apparel.WornApparel.Any())
return "";
List<string> items = pawn
.apparel.WornApparel.Take(3)
.Select(a =>
a.Stuff != null
? $"{a.Stuff.label.ToLower()} {a.def.label.ToLower()}"
: a.def.label.ToLower()
)
.ToList();
return $"wearing {string.Join(", ", items)}, ";
}
/// <summary>
/// Получает текстовое описание цвета
/// </summary>
private string GetColorDescription(Color color)
{
// Определяем доминирующий цвет
float max = Mathf.Max(color.r, color.g, color.b);
float min = Mathf.Min(color.r, color.g, color.b);
float diff = max - min;
if (diff < 0.1f)
{
// Оттенки серого
return max switch
{
> 0.8f => "white",
> 0.6f => "light gray",
> 0.4f => "gray",
> 0.2f => "dark gray",
_ => "black",
};
}
// Цветные
const float epsilon = 0.001f;
if (Mathf.Abs(color.r - max) < epsilon)
{
return color.g > color.b ? "orange" : "red";
}
else if (Mathf.Abs(color.g - max) < epsilon)
{
return color.r > color.b ? "yellow" : "green";
}
else
{
return color.r > color.g ? "purple" : "blue";
}
}
public override void DoWindowContents(Rect inRect)
{
float curY = 0f;
@@ -282,6 +425,47 @@ namespace AIImages
new Rect(20f, contentY, scrollViewRect.width - 30f, apparelHeight),
apparelText
);
contentY += apparelHeight + 20f;
// Разделитель
Widgets.DrawLineHorizontal(10f, contentY, scrollViewRect.width - 20f);
contentY += 15f;
// Секция "Stable Diffusion Промпт"
Text.Font = GameFont.Medium;
Widgets.Label(
new Rect(10f, contentY, scrollViewRect.width - 20f, 30f),
"AIImages.Prompt.SectionTitle".Translate()
);
contentY += 35f;
// Промпт текст
Text.Font = GameFont.Small;
string promptText = GenerateStableDiffusionPrompt();
float promptHeight = Text.CalcHeight(promptText, scrollViewRect.width - 30f);
Widgets.Label(
new Rect(20f, contentY, scrollViewRect.width - 30f, promptHeight),
promptText
);
contentY += promptHeight + 10f;
// Кнопка копирования
Rect copyButtonRect = new Rect(20f, contentY, 150f, 30f);
if (Widgets.ButtonText(copyButtonRect, "AIImages.Prompt.CopyButton".Translate()))
{
GUIUtility.systemCopyBuffer = promptText;
copiedMessageTime = 2f; // Показываем сообщение на 2 секунды
}
// Сообщение о копировании
if (copiedMessageTime > 0f)
{
Rect copiedRect = new Rect(copyButtonRect.xMax + 10f, contentY, 100f, 30f);
GUI.color = new Color(0f, 1f, 0f, copiedMessageTime / 2f); // Затухающий зеленый
Widgets.Label(copiedRect, "AIImages.Prompt.Copied".Translate());
GUI.color = Color.white;
}
Widgets.EndScrollView();
}
@@ -310,6 +494,19 @@ namespace AIImages
string apparelText = GetApparelDescription();
height += Text.CalcHeight(apparelText, 640f) + 20f;
// Разделитель
height += 15f;
// Заголовок "Промпт"
height += 35f;
// Текст промпта
string promptText = GenerateStableDiffusionPrompt();
height += Text.CalcHeight(promptText, 640f) + 10f;
// Кнопка и отступ
height += 30f + 20f;
return height;
}
}