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:
@@ -18,5 +18,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Krafs.Rimworld.Ref" Version="1.6.4566" />
|
||||
<PackageReference Include="Lib.Harmony" Version="2.4.1" />
|
||||
<PackageReference Include="StableDiffusionNet" Version="1.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user