Initial Dansori character workspace
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Character_Builder;
|
||||
|
||||
/// <summary>
|
||||
/// Alpha-based analysis for auto-alignment: opaque bounding box + neck anchors
|
||||
/// (top-edge center for a headless body, bottom-edge center for a head).
|
||||
/// </summary>
|
||||
public static class AlphaTools
|
||||
{
|
||||
public class Analysis
|
||||
{
|
||||
public int W, H;
|
||||
public int MinX, MinY, MaxX, MaxY;
|
||||
public double TopCenterX; // center X of opaque pixels along the top band
|
||||
public double BottomCenterX; // center X of opaque pixels along the bottom band
|
||||
public int AxisX; // torso central axis (robust neck X; ignores raised arms)
|
||||
public int NeckTop; // topmost row of the central column (robust neck Y)
|
||||
public int BH => MaxY - MinY + 1;
|
||||
public int BW => MaxX - MinX + 1;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, Analysis?> _cache = new(StringComparer.OrdinalIgnoreCase);
|
||||
private const byte AlphaThreshold = 24;
|
||||
|
||||
public static Analysis? Analyze(string path)
|
||||
{
|
||||
if (_cache.TryGetValue(path, out var cached)) return cached;
|
||||
Analysis? a = null;
|
||||
try { a = Compute(path); } catch { a = null; }
|
||||
_cache[path] = a;
|
||||
return a;
|
||||
}
|
||||
|
||||
private static Analysis? Compute(string path)
|
||||
{
|
||||
var src = AssetScanner.LoadPart(path);
|
||||
if (src == null) return null;
|
||||
BitmapSource conv = src.Format == PixelFormats.Bgra32 ? src : new FormatConvertedBitmap(src, PixelFormats.Bgra32, null, 0);
|
||||
int w = conv.PixelWidth, h = conv.PixelHeight;
|
||||
if (w <= 0 || h <= 0) return null;
|
||||
int stride = w * 4;
|
||||
var px = new byte[h * stride];
|
||||
conv.CopyPixels(px, stride, 0);
|
||||
|
||||
int minX = int.MaxValue, minY = int.MaxValue, maxX = -1, maxY = -1;
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int row = y * stride;
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
if (px[row + x * 4 + 3] >= AlphaThreshold)
|
||||
{
|
||||
if (x < minX) minX = x;
|
||||
if (x > maxX) maxX = x;
|
||||
if (y < minY) minY = y;
|
||||
if (y > maxY) maxY = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (maxX < 0) return null; // fully transparent
|
||||
|
||||
int bh = maxY - minY + 1;
|
||||
int band = Math.Max(1, (int)(bh * 0.06));
|
||||
|
||||
double topSum = 0; long topCount = 0;
|
||||
for (int y = minY; y < Math.Min(h, minY + band); y++)
|
||||
{
|
||||
int row = y * stride;
|
||||
for (int x = minX; x <= maxX; x++)
|
||||
if (px[row + x * 4 + 3] >= AlphaThreshold) { topSum += x; topCount++; }
|
||||
}
|
||||
double bottomSum = 0; long bottomCount = 0;
|
||||
for (int y = Math.Max(0, maxY - band + 1); y <= maxY; y++)
|
||||
{
|
||||
int row = y * stride;
|
||||
for (int x = minX; x <= maxX; x++)
|
||||
if (px[row + x * 4 + 3] >= AlphaThreshold) { bottomSum += x; bottomCount++; }
|
||||
}
|
||||
|
||||
// Torso central axis: median opaque column in the mid-lower body (waist/legs are centered
|
||||
// and free of raised arms), giving a neck-X that is stable across gesture poses.
|
||||
int ta0 = minY + (int)(bh * 0.40), ta1 = Math.Min(maxY, minY + (int)(bh * 0.85));
|
||||
var colCount = new int[w];
|
||||
long axisTotal = 0;
|
||||
for (int y = ta0; y <= ta1; y++)
|
||||
{
|
||||
int row = y * stride;
|
||||
for (int x = minX; x <= maxX; x++)
|
||||
if (px[row + x * 4 + 3] >= AlphaThreshold) { colCount[x]++; axisTotal++; }
|
||||
}
|
||||
int axisX = (minX + maxX) / 2;
|
||||
if (axisTotal > 0)
|
||||
{
|
||||
long half = axisTotal / 2, acc = 0;
|
||||
for (int x = 0; x < w; x++) { acc += colCount[x]; if (acc >= half) { axisX = x; break; } }
|
||||
}
|
||||
|
||||
// Neck top: first row (from the top) whose central column — the opaque run around the
|
||||
// axis — is solid (>=15px). Skips raised hands/arms that sit above the real neck.
|
||||
int neckTop = minY;
|
||||
for (int y = minY; y < minY + (int)(bh * 0.5); y++)
|
||||
{
|
||||
if (CentralRunWidth(px, stride, y, w, axisX) >= 15) { neckTop = y; break; }
|
||||
}
|
||||
|
||||
// Precise neck via the bare-skin neck stump: the head's neck and the body's neck stump are
|
||||
// the same anatomical part, so matching this locks the assembly. The stump is the central
|
||||
// skin column at the top of a headless body; detecting it here gives a neck centre/top that
|
||||
// is robust to raised arms, open jackets and asymmetric poses. Falls back to the alpha
|
||||
// estimate above when no skin stump is present.
|
||||
int bboxW = maxX - minX + 1, seedX = (minX + maxX) / 2;
|
||||
for (int y = minY; y < minY + (int)(bh * 0.5); y++)
|
||||
{
|
||||
var (lo, hi, rw) = CentralSkinRun(px, stride, y, w, seedX);
|
||||
if (rw >= 30 && rw <= bboxW / 2) { axisX = (lo + hi) / 2; neckTop = y; break; }
|
||||
}
|
||||
|
||||
double cx = (minX + maxX) / 2.0;
|
||||
return new Analysis
|
||||
{
|
||||
W = w,
|
||||
H = h,
|
||||
MinX = minX,
|
||||
MinY = minY,
|
||||
MaxX = maxX,
|
||||
MaxY = maxY,
|
||||
AxisX = axisX,
|
||||
NeckTop = neckTop,
|
||||
TopCenterX = topCount > 0 ? topSum / topCount : cx,
|
||||
BottomCenterX = bottomCount > 0 ? bottomSum / bottomCount : cx,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>Width of the opaque run that straddles <paramref name="axisX"/> on row y (0 if none).</summary>
|
||||
private static int CentralRunWidth(byte[] px, int stride, int y, int w, int axisX)
|
||||
{
|
||||
int row = y * stride, seed = -1;
|
||||
for (int d = 0; d <= 8 && seed < 0; d++)
|
||||
{
|
||||
if (axisX + d < w && px[row + (axisX + d) * 4 + 3] >= AlphaThreshold) seed = axisX + d;
|
||||
else if (axisX - d >= 0 && px[row + (axisX - d) * 4 + 3] >= AlphaThreshold) seed = axisX - d;
|
||||
}
|
||||
if (seed < 0) return 0;
|
||||
int lo = seed, hi = seed;
|
||||
while (lo - 1 >= 0 && px[row + (lo - 1) * 4 + 3] >= AlphaThreshold) lo--;
|
||||
while (hi + 1 < w && px[row + (hi + 1) * 4 + 3] >= AlphaThreshold) hi++;
|
||||
return hi - lo + 1;
|
||||
}
|
||||
|
||||
private static bool IsSkin(byte b, byte g, byte r)
|
||||
=> r > 150 && r >= g && g >= b - 8 && (r - b) >= 15 && (r - b) <= 130 && (g - b) <= 70;
|
||||
|
||||
/// <summary>The bare-skin run straddling <paramref name="cx"/> on row y (searches ±60px for the seed).</summary>
|
||||
private static (int lo, int hi, int w) CentralSkinRun(byte[] px, int stride, int y, int w, int cx)
|
||||
{
|
||||
int row = y * stride, seed = -1;
|
||||
for (int d = 0; d <= 60 && seed < 0; d++)
|
||||
{
|
||||
int xr = cx + d, xl = cx - d;
|
||||
if (xr < w && IsSkin(px[row + xr * 4], px[row + xr * 4 + 1], px[row + xr * 4 + 2])) seed = xr;
|
||||
else if (xl >= 0 && IsSkin(px[row + xl * 4], px[row + xl * 4 + 1], px[row + xl * 4 + 2])) seed = xl;
|
||||
}
|
||||
if (seed < 0) return (0, 0, 0);
|
||||
int lo = seed, hi = seed;
|
||||
while (lo - 1 >= 0 && IsSkin(px[row + (lo - 1) * 4], px[row + (lo - 1) * 4 + 1], px[row + (lo - 1) * 4 + 2])) lo--;
|
||||
while (hi + 1 < w && IsSkin(px[row + (hi + 1) * 4], px[row + (hi + 1) * 4 + 1], px[row + (hi + 1) * 4 + 2])) hi++;
|
||||
return (lo, hi, hi - lo + 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
<Application x:Class="Character_Builder.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<SolidColorBrush x:Key="Bg" Color="#14151A"/>
|
||||
<SolidColorBrush x:Key="Panel" Color="#1E2028"/>
|
||||
<SolidColorBrush x:Key="Panel2" Color="#262933"/>
|
||||
<SolidColorBrush x:Key="Stroke" Color="#343845"/>
|
||||
<SolidColorBrush x:Key="Accent" Color="#4CC2FF"/>
|
||||
<SolidColorBrush x:Key="AccentDk" Color="#04222F"/>
|
||||
<SolidColorBrush x:Key="Text" Color="#EDEFF2"/>
|
||||
<SolidColorBrush x:Key="Dim" Color="#A8ADB8"/>
|
||||
<SolidColorBrush x:Key="Mute" Color="#6E7480"/>
|
||||
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource Text}"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI Variable, Segoe UI"/>
|
||||
<Setter Property="FontSize" Value="18"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="Label" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{StaticResource Dim}"/>
|
||||
<Setter Property="FontSize" Value="17"/>
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="Margin" Value="0,16,0,6"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="Card" TargetType="Border">
|
||||
<Setter Property="Background" Value="{StaticResource Panel}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Stroke}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CornerRadius" Value="12"/>
|
||||
</Style>
|
||||
|
||||
<!-- Button (subtle) -->
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Foreground" Value="{StaticResource Text}"/>
|
||||
<Setter Property="Background" Value="{StaticResource Panel2}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Stroke}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="Height" Value="46"/>
|
||||
<Setter Property="Padding" Value="18,0"/>
|
||||
<Setter Property="FontSize" Value="19"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI Variable, Segoe UI"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="b" Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="8" Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="b" Property="BorderBrush" Value="{StaticResource Accent}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="b" Property="Opacity" Value="0.75"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="b" Property="Opacity" Value="0.4"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<!-- Window caption (title bar) buttons -->
|
||||
<Style x:Key="CaptionButton" TargetType="Button">
|
||||
<Setter Property="Width" Value="50"/>
|
||||
<Setter Property="Height" Value="36"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource Dim}"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="FontFamily" Value="Segoe MDL2 Assets"/>
|
||||
<Setter Property="FontSize" Value="10"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="cb" Background="{TemplateBinding Background}" CornerRadius="6">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="cb" Property="Background" Value="{StaticResource Panel2}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource Text}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="CaptionClose" TargetType="Button" BasedOn="{StaticResource CaptionButton}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="cb" Background="{TemplateBinding Background}" CornerRadius="6">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="cb" Property="Background" Value="#E23B3B"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="AccentButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Background" Value="{StaticResource Accent}"/>
|
||||
<Setter Property="Foreground" Value="{StaticResource AccentDk}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
</Style>
|
||||
|
||||
<!-- Dark ComboBox: readable light text on both the closed box and the dropdown -->
|
||||
<Style TargetType="ComboBoxItem">
|
||||
<Setter Property="Foreground" Value="{StaticResource Text}"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI Variable, Segoe UI"/>
|
||||
<Setter Property="FontSize" Value="19"/>
|
||||
<Setter Property="Padding" Value="12,8"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ComboBoxItem">
|
||||
<Border x:Name="ib" Background="Transparent" CornerRadius="6"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsHighlighted" Value="True">
|
||||
<Setter TargetName="ib" Property="Background" Value="#12344A"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="ib" Property="Background" Value="#173B52"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ComboToggle" TargetType="ToggleButton">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ToggleButton">
|
||||
<Border Background="Transparent"/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ComboBox">
|
||||
<Setter Property="Foreground" Value="{StaticResource Text}"/>
|
||||
<Setter Property="Background" Value="{StaticResource Panel2}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Stroke}"/>
|
||||
<Setter Property="Height" Value="44"/>
|
||||
<Setter Property="Padding" Value="12,0"/>
|
||||
<Setter Property="FontSize" Value="19"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI Variable, Segoe UI"/>
|
||||
<Setter Property="SnapsToDevicePixels" Value="True"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ComboBox">
|
||||
<Grid>
|
||||
<Border x:Name="box" Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="1"
|
||||
CornerRadius="8"/>
|
||||
<ToggleButton Style="{StaticResource ComboToggle}"
|
||||
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"/>
|
||||
<ContentPresenter x:Name="sel" Margin="10,0,30,0"
|
||||
Content="{TemplateBinding SelectionBoxItem}"
|
||||
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
|
||||
IsHitTestVisible="False"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||
TextElement.Foreground="{StaticResource Text}"/>
|
||||
<Path x:Name="arrow" Data="M0,0 L8,0 L4,5 Z" Fill="{StaticResource Dim}"
|
||||
HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,12,0"
|
||||
IsHitTestVisible="False"/>
|
||||
<Popup x:Name="PART_Popup" Placement="Bottom" AllowsTransparency="True"
|
||||
IsOpen="{TemplateBinding IsDropDownOpen}" Focusable="False"
|
||||
PopupAnimation="Slide">
|
||||
<Border Background="{StaticResource Panel}" BorderBrush="{StaticResource Stroke}"
|
||||
BorderThickness="1" CornerRadius="8" Margin="0,4,0,0"
|
||||
MinWidth="{Binding ActualWidth, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Padding="4">
|
||||
<ScrollViewer MaxHeight="320" VerticalScrollBarVisibility="Auto">
|
||||
<ItemsPresenter/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Popup>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="box" Property="BorderBrush" Value="{StaticResource Accent}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="box" Property="Opacity" Value="0.5"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="CheckBox">
|
||||
<Setter Property="Foreground" Value="{StaticResource Dim}"/>
|
||||
<Setter Property="FontSize" Value="19"/>
|
||||
<Setter Property="Margin" Value="0,5"/>
|
||||
<Setter Property="FontFamily" Value="Segoe UI Variable, Segoe UI"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Character_Builder;
|
||||
|
||||
public partial class App : Application
|
||||
{
|
||||
public App()
|
||||
{
|
||||
DispatcherUnhandledException += (_, e) =>
|
||||
{
|
||||
MessageBox.Show(e.Exception.Message, "오류", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
e.Handled = true;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Character_Builder;
|
||||
|
||||
/// <summary>Finds the Characters_Build_Docs root, lists characters, and scans their part PNGs.</summary>
|
||||
public static class AssetScanner
|
||||
{
|
||||
/// <summary>Walk up from the exe to find the "Characters_Build_Docs" folder.</summary>
|
||||
public static string? FindRoot()
|
||||
{
|
||||
var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
for (int i = 0; i < 10 && dir != null; i++)
|
||||
{
|
||||
if (string.Equals(dir.Name, "Characters_Build_Docs", StringComparison.OrdinalIgnoreCase))
|
||||
return dir.FullName;
|
||||
// also: a child named Characters_Build_Docs
|
||||
var child = Path.Combine(dir.FullName, "Characters_Build_Docs");
|
||||
if (Directory.Exists(child)) return child;
|
||||
dir = dir.Parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Character = a subfolder that contains a Reference/ folder.</summary>
|
||||
public static List<CharacterInfo> GetCharacters(string root)
|
||||
{
|
||||
var list = new List<CharacterInfo>();
|
||||
if (!Directory.Exists(root)) return list;
|
||||
foreach (var d in Directory.GetDirectories(root))
|
||||
{
|
||||
var refDir = Path.Combine(d, "Reference");
|
||||
if (!Directory.Exists(refDir)) continue;
|
||||
var name = Path.GetFileName(d);
|
||||
var sheet = PickSheet(refDir, name);
|
||||
list.Add(new CharacterInfo
|
||||
{
|
||||
Name = name,
|
||||
FolderPath = d,
|
||||
SheetPath = sheet,
|
||||
Thumb = LoadBitmap(sheet)
|
||||
});
|
||||
}
|
||||
return list.OrderBy(c => c.Name).ToList();
|
||||
}
|
||||
|
||||
private static string? PickSheet(string refDir, string charName)
|
||||
{
|
||||
var sheets = Directory.GetFiles(refDir, "*_sheet.png");
|
||||
if (sheets.Length == 0) return Directory.GetFiles(refDir, "*.png").FirstOrDefault();
|
||||
bool duo = charName.Contains("and", StringComparison.OrdinalIgnoreCase)
|
||||
|| charName.Contains("Duo", StringComparison.OrdinalIgnoreCase)
|
||||
|| charName.Contains("&");
|
||||
// duo -> prefer combined; single -> prefer non-combined
|
||||
var ordered = sheets.OrderBy(s =>
|
||||
{
|
||||
bool combined = Path.GetFileName(s).Contains("combined", StringComparison.OrdinalIgnoreCase);
|
||||
return duo ? (combined ? 0 : 1) : (combined ? 1 : 0);
|
||||
}).ThenBy(s => s);
|
||||
return ordered.First();
|
||||
}
|
||||
|
||||
/// <summary>Scan every PNG under the character's Images/ folders.</summary>
|
||||
public static List<PartItem> GetParts(string charFolder)
|
||||
{
|
||||
var result = new List<PartItem>();
|
||||
if (!Directory.Exists(charFolder)) return result;
|
||||
var pngs = Directory.GetFiles(charFolder, "*.png", SearchOption.AllDirectories)
|
||||
.Where(p => p.Replace('\\', '/').Contains("/Images/"))
|
||||
.OrderBy(p => p);
|
||||
foreach (var p in pngs)
|
||||
{
|
||||
var fn = Path.GetFileName(p);
|
||||
var stem = Path.GetFileNameWithoutExtension(fn);
|
||||
var item = new PartItem { FileName = fn, FilePath = p, Display = stem };
|
||||
var low = stem.ToLowerInvariant();
|
||||
|
||||
if (low.Contains("_hairmask_"))
|
||||
{
|
||||
item.Shape = AfterToken(stem, "hairmask");
|
||||
}
|
||||
else if (low.Contains("_head_"))
|
||||
{
|
||||
var parts = stem.Split('_');
|
||||
int hi = Array.FindIndex(parts, t => t.Equals("head", StringComparison.OrdinalIgnoreCase));
|
||||
if (hi >= 0 && hi + 1 < parts.Length)
|
||||
{
|
||||
item.Shape = parts[hi + 1];
|
||||
item.Expr = (hi + 2 < parts.Length) ? string.Join("_", parts.Skip(hi + 2)) : "(base)";
|
||||
}
|
||||
}
|
||||
result.Add(item);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string AfterToken(string stem, string token)
|
||||
{
|
||||
var parts = stem.Split('_');
|
||||
int i = Array.FindIndex(parts, t => t.Equals(token, StringComparison.OrdinalIgnoreCase));
|
||||
return (i >= 0 && i + 1 < parts.Length) ? string.Join("_", parts.Skip(i + 1)) : "";
|
||||
}
|
||||
|
||||
public static BitmapImage? LoadBitmap(string? path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path) || !File.Exists(path)) return null;
|
||||
try
|
||||
{
|
||||
var b = new BitmapImage();
|
||||
b.BeginInit();
|
||||
b.CacheOption = BitmapCacheOption.OnLoad;
|
||||
b.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
|
||||
b.UriSource = new Uri(path);
|
||||
b.EndInit();
|
||||
b.Freeze();
|
||||
return b;
|
||||
}
|
||||
catch { return null; }
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, BitmapSource?> _partCache = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>Load a part PNG with its baked-in background keyed out to transparency (cached).</summary>
|
||||
public static BitmapSource? LoadPart(string? path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path)) return null;
|
||||
if (_partCache.TryGetValue(path, out var cached)) return cached;
|
||||
var raw = LoadBitmap(path);
|
||||
BitmapSource? keyed = raw;
|
||||
bool isHead = Path.GetFileName(path).ToLowerInvariant().Contains("_head_");
|
||||
try { if (raw != null) keyed = BgKey.KeyOut(raw, isHead); } catch { keyed = raw; }
|
||||
_partCache[path] = keyed;
|
||||
return keyed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Character_Builder;
|
||||
|
||||
/// <summary>
|
||||
/// Many part PNGs are exported as opaque RGB with a "fake transparency" checkerboard
|
||||
/// (two light grays ~#FEFEFE / ~#F1F1F1) baked into the pixels. That opaque background
|
||||
/// hides layers below it and breaks alpha-based neck detection.
|
||||
///
|
||||
/// KeyOut() removes the background by flood-filling from the image borders, so only the
|
||||
/// background that is *connected to the edge* is cleared — interior white clothing (crop
|
||||
/// top, hoodie) is kept. If the source already has real transparency, it is returned as-is.
|
||||
/// </summary>
|
||||
public static class BgKey
|
||||
{
|
||||
// A pixel counts as background when it is light and near-neutral (low saturation).
|
||||
private const int BgMinBrightness = 208; // min channel value
|
||||
private const int BgMaxSpread = 26; // max(channel) - min(channel)
|
||||
|
||||
// Boundary feather: soften light fringe pixels that touch cleared background.
|
||||
private const int FeatherLo = 200; // fully opaque below this brightness
|
||||
private const int FeatherHi = 250; // fully transparent at/above this brightness
|
||||
|
||||
public static BitmapSource KeyOut(BitmapSource src, bool isHead = false)
|
||||
{
|
||||
BitmapSource conv = src.Format == PixelFormats.Bgra32
|
||||
? src
|
||||
: new FormatConvertedBitmap(src, PixelFormats.Bgra32, null, 0);
|
||||
|
||||
int w = conv.PixelWidth, h = conv.PixelHeight;
|
||||
if (w <= 0 || h <= 0) return src;
|
||||
|
||||
int stride = w * 4;
|
||||
var px = new byte[h * stride];
|
||||
conv.CopyPixels(px, stride, 0);
|
||||
|
||||
// Already has real transparency? Leave it alone.
|
||||
long transparent = 0;
|
||||
for (int i = 3; i < px.Length; i += 4)
|
||||
if (px[i] < 16) { if (++transparent > (long)w * h / 50) break; }
|
||||
if (transparent > (long)w * h / 50) return src;
|
||||
|
||||
var seen = new bool[w * h];
|
||||
var stack = new int[w * h];
|
||||
int sp = 0;
|
||||
|
||||
// Seed the flood fill from every border pixel that looks like background.
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
TryPush(px, stride, seen, stack, ref sp, x, 0, w);
|
||||
TryPush(px, stride, seen, stack, ref sp, x, h - 1, w);
|
||||
}
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
TryPush(px, stride, seen, stack, ref sp, 0, y, w);
|
||||
TryPush(px, stride, seen, stack, ref sp, w - 1, y, w);
|
||||
}
|
||||
|
||||
// Flood fill (4-connected) over border-connected background; clear its alpha.
|
||||
while (sp > 0)
|
||||
{
|
||||
int idx = stack[--sp];
|
||||
int x = idx % w, y = idx / w;
|
||||
px[idx * 4 + 3] = 0; // transparent
|
||||
|
||||
TryPush(px, stride, seen, stack, ref sp, x - 1, y, w);
|
||||
TryPush(px, stride, seen, stack, ref sp, x + 1, y, w);
|
||||
TryPush(px, stride, seen, stack, ref sp, x, y - 1, w);
|
||||
TryPush(px, stride, seen, stack, ref sp, x, y + 1, w);
|
||||
}
|
||||
|
||||
// The baked "transparency" is a two-tone light-gray checkerboard. Loops/rings (necklace,
|
||||
// bracelet, headphone band) enclose background pockets the border fill can't reach; remove
|
||||
// them by detecting the checker pattern (both tones present, no dark object pixels nearby).
|
||||
RemoveCheckerPockets(px, w, h, stride);
|
||||
|
||||
// Head portraits include a white shirt/shoulders below the neck; that interior white
|
||||
// is not border-connected, so it survives the flood fill and looks like an un-keyed
|
||||
// white patch once the head is layered on a body. Drop light interior pixels in the
|
||||
// lower part of the head silhouette (below the face, so eyes/teeth are untouched).
|
||||
if (isHead) DropHeadShirt(px, w, h, stride);
|
||||
|
||||
Feather(px, w, h, stride);
|
||||
|
||||
var wb = new WriteableBitmap(w, h, 96, 96, PixelFormats.Bgra32, null);
|
||||
wb.WritePixels(new Int32Rect(0, 0, w, h), px, stride, 0);
|
||||
wb.Freeze();
|
||||
return wb;
|
||||
}
|
||||
|
||||
private static void TryPush(byte[] px, int stride, bool[] seen, int[] stack, ref int sp, int x, int y, int w)
|
||||
{
|
||||
int h = px.Length / stride;
|
||||
if (x < 0 || y < 0 || x >= w || y >= h) return;
|
||||
int idx = y * w + x;
|
||||
if (seen[idx]) return;
|
||||
int p = y * stride + x * 4;
|
||||
if (!IsBg(px[p], px[p + 1], px[p + 2])) return;
|
||||
seen[idx] = true;
|
||||
stack[sp++] = idx;
|
||||
}
|
||||
|
||||
private static bool IsBg(byte b, byte g, byte r)
|
||||
{
|
||||
int mn = Math.Min(r, Math.Min(g, b));
|
||||
int mx = Math.Max(r, Math.Max(g, b));
|
||||
return mn >= BgMinBrightness && (mx - mn) <= BgMaxSpread;
|
||||
}
|
||||
|
||||
/// <summary>Remove enclosed checkerboard-background pockets while keeping solid light objects.</summary>
|
||||
private static void RemoveCheckerPockets(byte[] px, int w, int h, int stride)
|
||||
{
|
||||
int W1 = w + 1;
|
||||
var li = new int[(h + 1) * W1]; // integral of "light" checker tone (~254)
|
||||
var mi = new int[(h + 1) * W1]; // integral of "mid" checker tone (~241)
|
||||
var di = new int[(h + 1) * W1]; // integral of dark (object) pixels
|
||||
var neutral = new bool[w * h];
|
||||
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int row = y * stride, cur = (y + 1) * W1, prev = y * W1;
|
||||
int la = 0, ma = 0, da = 0;
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int p = row + x * 4; int b = px[p], g = px[p + 1], r = px[p + 2];
|
||||
int mn = Math.Min(r, Math.Min(g, b)), mx = Math.Max(r, Math.Max(g, b));
|
||||
double v = (r + g + b) / 3.0;
|
||||
bool nt = mn >= 224 && (mx - mn) <= 14;
|
||||
neutral[y * w + x] = nt;
|
||||
if (nt && v >= 249) la++;
|
||||
if (nt && v >= 234 && v <= 246) ma++;
|
||||
if (v < 205) da++;
|
||||
li[cur + x + 1] = li[prev + x + 1] + la;
|
||||
mi[cur + x + 1] = mi[prev + x + 1] + ma;
|
||||
di[cur + x + 1] = di[prev + x + 1] + da;
|
||||
}
|
||||
}
|
||||
|
||||
const int R = 18;
|
||||
int area = (2 * R + 1) * (2 * R + 1);
|
||||
int need = (int)(0.06 * area);
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int row = y * stride;
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int p = row + x * 4;
|
||||
if (px[p + 3] == 0 || !neutral[y * w + x]) continue;
|
||||
int y0 = Math.Max(0, y - R), y1 = Math.Min(h, y + R + 1);
|
||||
int x0 = Math.Max(0, x - R), x1 = Math.Min(w, x + R + 1);
|
||||
if (Win(di, W1, y0, y1, x0, x1) > 2) continue; // an object edge is near → keep
|
||||
if (Win(li, W1, y0, y1, x0, x1) >= need && Win(mi, W1, y0, y1, x0, x1) >= need)
|
||||
px[p + 3] = 0; // both checker tones present → background
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int Win(int[] I, int W1, int y0, int y1, int x0, int x1)
|
||||
=> I[y1 * W1 + x1] - I[y0 * W1 + x1] - I[y1 * W1 + x0] + I[y0 * W1 + x0];
|
||||
|
||||
/// <summary>Clear light, low-saturation interior pixels (the shirt) in the lower head region.</summary>
|
||||
private static void DropHeadShirt(byte[] px, int w, int h, int stride)
|
||||
{
|
||||
int minY = int.MaxValue, maxY = -1;
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int row = y * stride;
|
||||
for (int x = 0; x < w; x++)
|
||||
if (px[row + x * 4 + 3] >= 24) { if (y < minY) minY = y; if (y > maxY) maxY = y; break; }
|
||||
}
|
||||
if (maxY < 0) return;
|
||||
int yThr = minY + (int)((maxY - minY) * 0.58); // below the face
|
||||
|
||||
for (int y = yThr; y < h; y++)
|
||||
{
|
||||
int row = y * stride;
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int p = row + x * 4;
|
||||
if (px[p + 3] == 0) continue;
|
||||
int b = px[p], g = px[p + 1], r = px[p + 2];
|
||||
int mn = Math.Min(r, Math.Min(g, b));
|
||||
int mx = Math.Max(r, Math.Max(g, b));
|
||||
if (mn >= 222 && (mx - mn) <= 16) px[p + 3] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Soften light, low-saturation pixels that border a cleared area (anti-halo).</summary>
|
||||
private static void Feather(byte[] px, int w, int h, int stride)
|
||||
{
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
int row = y * stride;
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
int p = row + x * 4;
|
||||
if (px[p + 3] == 0) continue;
|
||||
|
||||
bool touchesCleared =
|
||||
(x > 0 && px[p - 4 + 3] == 0) ||
|
||||
(x < w - 1 && px[p + 4 + 3] == 0) ||
|
||||
(y > 0 && px[p - stride + 3] == 0) ||
|
||||
(y < h - 1 && px[p + stride + 3] == 0);
|
||||
if (!touchesCleared) continue;
|
||||
|
||||
int b = px[p], g = px[p + 1], r = px[p + 2];
|
||||
int mn = Math.Min(r, Math.Min(g, b));
|
||||
int mx = Math.Max(r, Math.Max(g, b));
|
||||
if (mn < FeatherLo || (mx - mn) > BgMaxSpread) continue;
|
||||
|
||||
int t = Math.Min(255, Math.Max(0, (mn - FeatherLo) * 255 / (FeatherHi - FeatherLo)));
|
||||
px[p + 3] = (byte)(255 - t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Character_Builder;
|
||||
|
||||
/// <summary>Reads/writes a composition as a human-readable .md file.</summary>
|
||||
public static class BuildMd
|
||||
{
|
||||
public static void Save(string path, BuildDefinition b)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine($"# Character Build — {b.Name}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("> Saved by Character_Builder. Layers are composited: body + head(expression) + hair tint + accessories.");
|
||||
sb.AppendLine("> Coordinate convention (for the Dansori app): stage 520x680. Each layer image is drawn Height=680 (Stretch=Uniform),");
|
||||
sb.AppendLine("> centered in the stage, scaled about its center by 'scale', then translated by (x, y) pixels. transform = x, y, scale.");
|
||||
sb.AppendLine("> The app can either reuse these transforms directly (same convention) or re-run alpha neck-alignment and treat these as deltas.");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"name: {b.Name}");
|
||||
sb.AppendLine($"character: {b.Character}");
|
||||
sb.AppendLine($"body: {b.BodyFile}");
|
||||
sb.AppendLine($"body.transform: {F(b.BodyOffsetX)}, {F(b.BodyOffsetY)}, {F(b.BodyScale)}");
|
||||
sb.AppendLine($"head: {b.HeadFile}");
|
||||
sb.AppendLine($"expression: {b.ExpressionFile}");
|
||||
sb.AppendLine($"hairmask: {b.HairmaskFile}");
|
||||
sb.AppendLine($"hairColor: {b.HairColor}");
|
||||
sb.AppendLine($"head.transform: {F(b.HeadOffsetX)}, {F(b.HeadOffsetY)}, {F(b.HeadScale)}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("## accessories");
|
||||
foreach (var a in b.Accessories)
|
||||
sb.AppendLine($"- {a.FileName} | {F(a.OffsetX)}, {F(a.OffsetY)}, {F(a.Scale)}");
|
||||
File.WriteAllText(path, sb.ToString(), new UTF8Encoding(false));
|
||||
}
|
||||
|
||||
public static BuildDefinition Load(string path)
|
||||
{
|
||||
var b = new BuildDefinition();
|
||||
bool inAcc = false;
|
||||
foreach (var raw in File.ReadAllLines(path))
|
||||
{
|
||||
var line = raw.Trim();
|
||||
if (line.Length == 0) continue;
|
||||
if (line.StartsWith("## accessories", StringComparison.OrdinalIgnoreCase)) { inAcc = true; continue; }
|
||||
if (line.StartsWith("#")) continue;
|
||||
if (line.StartsWith(">")) continue;
|
||||
|
||||
if (inAcc && line.StartsWith("-"))
|
||||
{
|
||||
var body = line.TrimStart('-', ' ');
|
||||
var parts = body.Split('|');
|
||||
var a = new AccessoryLayer { FileName = parts[0].Trim() };
|
||||
if (parts.Length > 1) ApplyTransform(parts[1], (x, y, s) => { a.OffsetX = x; a.OffsetY = y; a.Scale = s; });
|
||||
if (!string.IsNullOrEmpty(a.FileName)) b.Accessories.Add(a);
|
||||
continue;
|
||||
}
|
||||
|
||||
int c = line.IndexOf(':');
|
||||
if (c < 0) continue;
|
||||
var key = line.Substring(0, c).Trim().ToLowerInvariant();
|
||||
var val = line.Substring(c + 1).Trim();
|
||||
switch (key)
|
||||
{
|
||||
case "name": b.Name = val; break;
|
||||
case "character": b.Character = val; break;
|
||||
case "body": b.BodyFile = Empty(val); break;
|
||||
case "body.transform":
|
||||
ApplyTransform(val, (x, y, s) => { b.BodyOffsetX = x; b.BodyOffsetY = y; b.BodyScale = s; });
|
||||
break;
|
||||
case "head": b.HeadFile = Empty(val); break;
|
||||
case "expression": b.ExpressionFile = Empty(val); break;
|
||||
case "hairmask": b.HairmaskFile = Empty(val); break;
|
||||
case "haircolor": b.HairColor = Empty(val); break;
|
||||
case "head.transform":
|
||||
ApplyTransform(val, (x, y, s) => { b.HeadOffsetX = x; b.HeadOffsetY = y; b.HeadScale = s; });
|
||||
break;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
private static string? Empty(string v) => string.IsNullOrWhiteSpace(v) ? null : v;
|
||||
|
||||
private static void ApplyTransform(string s, Action<double, double, double> set)
|
||||
{
|
||||
var t = s.Split(',');
|
||||
double x = t.Length > 0 ? D(t[0]) : 0;
|
||||
double y = t.Length > 1 ? D(t[1]) : 0;
|
||||
double sc = t.Length > 2 ? D(t[2]) : 1;
|
||||
set(x, y, sc);
|
||||
}
|
||||
|
||||
private static double D(string s) =>
|
||||
double.TryParse(s.Trim(), NumberStyles.Any, CultureInfo.InvariantCulture, out var v) ? v : 0;
|
||||
|
||||
private static string F(double v) => v.ToString("0.###", CultureInfo.InvariantCulture);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UseWPF>true</UseWPF>
|
||||
<RootNamespace>Character_Builder</RootNamespace>
|
||||
<AssemblyName>Character_Builder</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,194 @@
|
||||
<Window x:Class="Character_Builder.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework"
|
||||
Title="Dansori · Character Builder" Height="1040" Width="1560"
|
||||
MinHeight="760" MinWidth="1320"
|
||||
WindowStyle="None" WindowState="Maximized" WindowStartupLocation="CenterScreen"
|
||||
Background="Transparent"
|
||||
FontFamily="Segoe UI Variable, Segoe UI">
|
||||
|
||||
<shell:WindowChrome.WindowChrome>
|
||||
<shell:WindowChrome CaptionHeight="52" ResizeBorderThickness="6"
|
||||
GlassFrameThickness="0" CornerRadius="0" UseAeroCaptionButtons="False"/>
|
||||
</shell:WindowChrome.WindowChrome>
|
||||
|
||||
<Border x:Name="RootBorder" Background="{StaticResource Bg}"
|
||||
BorderBrush="{StaticResource Stroke}" BorderThickness="1">
|
||||
<Grid Margin="16">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- ===== TOP BAR ===== -->
|
||||
<Grid Grid.Row="0" Margin="4,0,4,14">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Border Width="12" Height="12" CornerRadius="6" Background="{StaticResource Accent}" Margin="0,0,12,0"/>
|
||||
<TextBlock Text="Character Builder" FontSize="28" FontWeight="Bold"/>
|
||||
<TextBlock Text="Dansori 캐릭터 조합기" Foreground="{StaticResource Mute}"
|
||||
FontSize="18" VerticalAlignment="Bottom" Margin="12,0,0,4"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right"
|
||||
shell:WindowChrome.IsHitTestVisibleInChrome="True">
|
||||
<Button x:Name="BtnRoot" Content="📁 루트" Click="Root_Click" Margin="0,0,8,0"/>
|
||||
<Button x:Name="BtnLoad" Content="↥ 불러오기" Click="Load_Click" Margin="0,0,8,0"/>
|
||||
<Button x:Name="BtnReset" Content="⟲ 초기화" Click="Reset_Click" Margin="0,0,8,0"/>
|
||||
<Button x:Name="BtnSave" Content="💾 저장" Click="Save_Click" Style="{StaticResource AccentButton}"/>
|
||||
<Border Width="1" Background="{StaticResource Stroke}" Margin="12,4,10,4"/>
|
||||
<Button x:Name="BtnMin" Style="{StaticResource CaptionButton}" Click="Min_Click">
|
||||
<Path Data="M0,5 H11" Stroke="{StaticResource Dim}" StrokeThickness="1.3"/>
|
||||
</Button>
|
||||
<Button x:Name="BtnMax" Style="{StaticResource CaptionButton}" Click="Max_Click" Margin="2,0">
|
||||
<Path Data="M0,0 H10 V10 H0 Z" Stroke="{StaticResource Dim}" StrokeThickness="1.3" Fill="Transparent"/>
|
||||
</Button>
|
||||
<Button x:Name="BtnClose" Style="{StaticResource CaptionClose}" Click="Close_Click">
|
||||
<Path Data="M0,0 L10,10 M0,10 L10,0" Stroke="{StaticResource Dim}" StrokeThickness="1.3"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- ===== MAIN ===== -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="300"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="440"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Character list -->
|
||||
<Border Grid.Column="0" Style="{StaticResource Card}" Margin="0,0,14,0">
|
||||
<DockPanel Margin="12">
|
||||
<TextBlock DockPanel.Dock="Top" Text="캐릭터" FontWeight="Bold" FontSize="21" Margin="2,2,0,12"/>
|
||||
<ListBox x:Name="CharList" Background="Transparent" BorderThickness="0"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
SelectionChanged="CharList_SelectionChanged">
|
||||
<ListBox.ItemContainerStyle>
|
||||
<Style TargetType="ListBoxItem">
|
||||
<Setter Property="Margin" Value="0,0,0,8"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="bd" CornerRadius="10" Background="{StaticResource Panel2}"
|
||||
BorderBrush="{StaticResource Stroke}" BorderThickness="1" Padding="8">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="bd" Property="BorderBrush" Value="{StaticResource Accent}"/>
|
||||
<Setter TargetName="bd" Property="Background" Value="#12344A"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="bd" Property="BorderBrush" Value="{StaticResource Accent}"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel>
|
||||
<Border CornerRadius="8" Height="150" ClipToBounds="True" Background="#0E0F14">
|
||||
<Image Source="{Binding Thumb}" Stretch="Uniform" Margin="4"/>
|
||||
</Border>
|
||||
<TextBlock Text="{Binding Name}" FontWeight="SemiBold" FontSize="19"
|
||||
Margin="2,9,0,2" TextTrimming="CharacterEllipsis"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Preview -->
|
||||
<Border Grid.Column="1" Style="{StaticResource Card}" Margin="0,0,14,0">
|
||||
<DockPanel Margin="14">
|
||||
<Grid DockPanel.Dock="Top" Margin="2,0,2,10">
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TxtCharName" Text="캐릭터를 선택하세요" FontSize="22" FontWeight="Bold"/>
|
||||
</StackPanel>
|
||||
<Button x:Name="BtnPlay" Content="▶ 제스처 미리보기" Click="Play_Click"
|
||||
HorizontalAlignment="Right" Width="210"/>
|
||||
</Grid>
|
||||
<Border Background="#0E0F14" CornerRadius="10" BorderBrush="{StaticResource Stroke}" BorderThickness="1">
|
||||
<Grid>
|
||||
<TextBlock x:Name="PreviewHint" Text="왼쪽에서 캐릭터를 고른 뒤, 오른쪽에서 파츠를 조합하세요."
|
||||
Foreground="{StaticResource Mute}" HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" TextWrapping="Wrap" TextAlignment="Center" MaxWidth="320"/>
|
||||
<Viewbox Stretch="Uniform" Margin="16">
|
||||
<Grid x:Name="StageHost" Width="540" Height="680"
|
||||
Background="Transparent" Cursor="SizeAll"
|
||||
MouseLeftButtonDown="Stage_MouseDown"
|
||||
MouseMove="Stage_MouseMove"
|
||||
MouseLeftButtonUp="Stage_MouseUp">
|
||||
<Grid x:Name="Stage" Width="540" Height="680"/>
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Controls -->
|
||||
<Border Grid.Column="2" Style="{StaticResource Card}">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto" Padding="14,10,14,14">
|
||||
<StackPanel>
|
||||
<TextBlock Text="조합" FontWeight="Bold" FontSize="21" Margin="2,2,0,4"/>
|
||||
|
||||
<TextBlock Text="바디 / 제스처" Style="{StaticResource Label}"/>
|
||||
<ComboBox x:Name="CmbBody" SelectionChanged="Body_Changed"/>
|
||||
|
||||
<TextBlock Text="헤어 모양" Style="{StaticResource Label}"/>
|
||||
<ComboBox x:Name="CmbShape" SelectionChanged="Shape_Changed"/>
|
||||
|
||||
<TextBlock Text="표정" Style="{StaticResource Label}"/>
|
||||
<ComboBox x:Name="CmbExpr" SelectionChanged="Expr_Changed"/>
|
||||
|
||||
<TextBlock Text="헤어 색상 (코드 틴트)" Style="{StaticResource Label}"/>
|
||||
<WrapPanel x:Name="SwatchPanel"/>
|
||||
|
||||
<TextBlock Text="악세서리" Style="{StaticResource Label}"/>
|
||||
<Border Background="{StaticResource Panel2}" BorderBrush="{StaticResource Stroke}"
|
||||
BorderThickness="1" CornerRadius="8" Padding="10,6" MinHeight="40" MaxHeight="230">
|
||||
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||
<StackPanel x:Name="AccPanel"/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
<TextBlock Text="레이어 위치/크기 조정" Style="{StaticResource Label}"/>
|
||||
<ComboBox x:Name="CmbLayer" SelectionChanged="Layer_Changed"/>
|
||||
<Grid Margin="0,8,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="34"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="X" Foreground="{StaticResource Dim}" VerticalAlignment="Center"/>
|
||||
<Slider x:Name="SldX" Grid.Row="0" Grid.Column="1" Minimum="-400" Maximum="400" ValueChanged="LayerXform_Changed"/>
|
||||
<TextBlock Grid.Row="1" Grid.Column="0" Text="Y" Foreground="{StaticResource Dim}" VerticalAlignment="Center"/>
|
||||
<Slider x:Name="SldY" Grid.Row="1" Grid.Column="1" Minimum="-400" Maximum="400" ValueChanged="LayerXform_Changed"/>
|
||||
<TextBlock Grid.Row="2" Grid.Column="0" Text="⤢" Foreground="{StaticResource Dim}" VerticalAlignment="Center"/>
|
||||
<Slider x:Name="SldScale" Grid.Row="2" Grid.Column="1" Minimum="0.1" Maximum="1.6" ValueChanged="LayerXform_Changed"/>
|
||||
</Grid>
|
||||
<Button Content="🎯 머리 자동 정렬" Click="AutoAlign_Click" Margin="0,10,0,0"/>
|
||||
<TextBlock Text="미리보기에서 드래그하면 위 목록에서 선택한 레이어가 이동합니다."
|
||||
Foreground="{StaticResource Mute}" FontSize="16" TextWrapping="Wrap" Margin="2,10,0,0"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- ===== STATUS ===== -->
|
||||
<TextBlock x:Name="TxtStatus" Grid.Row="2" Margin="4,10,4,0"
|
||||
Foreground="{StaticResource Mute}" FontSize="17" Text="준비"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -0,0 +1,693 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Character_Builder;
|
||||
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private const double StageH = 680;
|
||||
private const double StageW = 540;
|
||||
|
||||
// contain-fit: baked into each layer's transform so a wide pose isn't clipped by the stage bounds
|
||||
private double _fit = 1, _fitCX, _fitCY;
|
||||
|
||||
private string? _root;
|
||||
private List<CharacterInfo> _characters = new();
|
||||
private CharacterInfo? _char;
|
||||
private BuildDefinition _build = new();
|
||||
|
||||
private List<PartItem> _bodies = new();
|
||||
private List<PartItem> _headFrames = new(); // base + expressions
|
||||
private List<PartItem> _accessories = new();
|
||||
private List<string> _shapes = new();
|
||||
private Dictionary<string, PartItem> _hairmaskByShape = new();
|
||||
private Dictionary<string, string> _pathByName = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private bool _suppress;
|
||||
|
||||
// preview / animation
|
||||
private readonly TranslateTransform _floatT = new();
|
||||
private readonly DispatcherTimer _timer = new() { Interval = TimeSpan.FromMilliseconds(60) };
|
||||
private Image? _headImage;
|
||||
private ImageSource? _headNormal, _headBlink;
|
||||
private int _phase, _blinkRestoreAt = -1;
|
||||
private bool _playing;
|
||||
|
||||
private static readonly (string label, string? hex)[] Swatches =
|
||||
{
|
||||
("✕", null),
|
||||
// mint / teal / cyan
|
||||
("", "#B7F5E3"), ("", "#38E0C4"), ("", "#1F9E8C"), ("", "#4CC2FF"), ("", "#2C7BE5"),
|
||||
// purple / pink / red
|
||||
("", "#7C5CFF"), ("", "#B36BFF"), ("", "#FF6FD8"), ("", "#FF7B9C"), ("", "#FF5C5C"),
|
||||
// warm
|
||||
("", "#FF9F45"), ("", "#F5D06B"), ("", "#C9A227"),
|
||||
// green / natural / neutral
|
||||
("", "#6BD66B"), ("", "#3C6E47"), ("", "#8A5A2B"), ("", "#4A3B33"),
|
||||
("", "#1A1A1E"), ("", "#D8DCE3"), ("", "#F5F0FF"),
|
||||
};
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
StageHost.RenderTransform = _floatT;
|
||||
_timer.Tick += Timer_Tick;
|
||||
BuildSwatches();
|
||||
Loaded += (_, __) => { ApplyMaximizedInset(); InitRoot(); };
|
||||
StateChanged += (_, __) => ApplyMaximizedInset();
|
||||
}
|
||||
|
||||
// WindowStyle=None + maximize overflows the screen; inset so nothing is clipped.
|
||||
private void ApplyMaximizedInset()
|
||||
=> RootBorder.Padding = WindowState == WindowState.Maximized ? new Thickness(7) : new Thickness(0);
|
||||
|
||||
// ---------- window caption ----------
|
||||
private void Min_Click(object sender, RoutedEventArgs e) => WindowState = WindowState.Minimized;
|
||||
private void Max_Click(object sender, RoutedEventArgs e) =>
|
||||
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
|
||||
private void Close_Click(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
// ---------- root / characters ----------
|
||||
private void InitRoot()
|
||||
{
|
||||
_root = AssetScanner.FindRoot();
|
||||
if (_root == null)
|
||||
{
|
||||
TxtStatus.Text = "Characters_Build_Docs 루트를 찾지 못했습니다. [📁 루트] 로 폴더를 지정하세요.";
|
||||
return;
|
||||
}
|
||||
LoadCharacters();
|
||||
}
|
||||
|
||||
private void LoadCharacters()
|
||||
{
|
||||
if (_root == null) return;
|
||||
_characters = AssetScanner.GetCharacters(_root);
|
||||
CharList.ItemsSource = _characters;
|
||||
TxtStatus.Text = $"루트: {_root} · 캐릭터 {_characters.Count}개";
|
||||
}
|
||||
|
||||
private void Root_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new Microsoft.Win32.OpenFolderDialog { Title = "Characters_Build_Docs 폴더 선택" };
|
||||
if (dlg.ShowDialog() == true)
|
||||
{
|
||||
_root = dlg.FolderName;
|
||||
LoadCharacters();
|
||||
}
|
||||
}
|
||||
|
||||
private void CharList_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_suppress) return;
|
||||
if (CharList.SelectedItem is CharacterInfo ci) SelectCharacter(ci, null);
|
||||
}
|
||||
|
||||
// ---------- select + populate ----------
|
||||
private void SelectCharacter(CharacterInfo ci, BuildDefinition? preset)
|
||||
{
|
||||
_char = ci;
|
||||
StopPlay();
|
||||
|
||||
var parts = AssetScanner.GetParts(ci.FolderPath);
|
||||
_pathByName = parts.GroupBy(p => p.FileName)
|
||||
.ToDictionary(g => g.Key, g => g.First().FilePath, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
_bodies = parts.Where(p => p.FileName.ToLowerInvariant().Contains("_body_"))
|
||||
.Where(p => !IsPartialBody(p.FileName)).ToList();
|
||||
_headFrames = parts.Where(p => p.FileName.ToLowerInvariant().Contains("_head_")).ToList();
|
||||
_accessories = parts.Where(p => p.FileName.ToLowerInvariant().StartsWith("acc")).ToList();
|
||||
_shapes = _headFrames.Select(h => h.Shape).Where(s => !string.IsNullOrEmpty(s)).Distinct().OrderBy(s => s).ToList();
|
||||
_hairmaskByShape = parts.Where(p => p.FileName.ToLowerInvariant().Contains("_hairmask_"))
|
||||
.GroupBy(p => p.Shape).ToDictionary(g => g.Key, g => g.First());
|
||||
|
||||
_suppress = true;
|
||||
CmbBody.ItemsSource = _bodies;
|
||||
CmbShape.ItemsSource = _shapes;
|
||||
BuildAccessoryChecks();
|
||||
_suppress = false;
|
||||
|
||||
_build = preset ?? MakeDefaultBuild(ci);
|
||||
TxtCharName.Text = ci.Name + (preset != null ? $" · {preset.Name}" : "");
|
||||
PreviewHint.Visibility = Visibility.Collapsed;
|
||||
|
||||
ApplyBuildToUi();
|
||||
if (preset == null) AutoAlignHead(); else { UpdateFit(); RebuildStage(); }
|
||||
TxtStatus.Text = $"{ci.Name}: 바디 {_bodies.Count} · 머리프레임 {_headFrames.Count} · 악세 {_accessories.Count}";
|
||||
}
|
||||
|
||||
/// <summary>Component pieces (arms/legs/torso) aren't standalone bodies — hide them from the list.</summary>
|
||||
private static bool IsPartialBody(string fileName)
|
||||
{
|
||||
var stem = System.IO.Path.GetFileNameWithoutExtension(fileName).ToLowerInvariant();
|
||||
return stem.EndsWith("_legs") || stem.EndsWith("_torso")
|
||||
|| stem.EndsWith("_arm_l") || stem.EndsWith("_arm_r");
|
||||
}
|
||||
|
||||
private BuildDefinition MakeDefaultBuild(CharacterInfo ci)
|
||||
{
|
||||
var b = new BuildDefinition { Character = ci.Name, Name = ci.Name };
|
||||
var body = _bodies.FirstOrDefault(x => x.FileName.Contains("idle_upper")) ?? _bodies.FirstOrDefault();
|
||||
b.BodyFile = body?.FileName;
|
||||
var shape = _shapes.FirstOrDefault();
|
||||
if (shape != null)
|
||||
{
|
||||
b.HeadFile = _headFrames.FirstOrDefault(h => h.Shape == shape && h.Expr == "(base)")?.FileName
|
||||
?? _headFrames.FirstOrDefault(h => h.Shape == shape)?.FileName;
|
||||
b.ExpressionFile = _headFrames.FirstOrDefault(h => h.Shape == shape && h.Expr == "neutral")?.FileName
|
||||
?? b.HeadFile;
|
||||
if (_hairmaskByShape.TryGetValue(shape, out var hm)) b.HairmaskFile = hm.FileName;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
private void ApplyBuildToUi()
|
||||
{
|
||||
_suppress = true;
|
||||
CmbBody.SelectedItem = _bodies.FirstOrDefault(x => x.FileName == _build.BodyFile);
|
||||
|
||||
var shape = ShapeOf(_build.ExpressionFile) ?? ShapeOf(_build.HeadFile) ?? _shapes.FirstOrDefault();
|
||||
CmbShape.SelectedItem = shape;
|
||||
PopulateExpr(shape);
|
||||
CmbExpr.SelectedItem = _headFrames.FirstOrDefault(h => h.FileName == _build.ExpressionFile);
|
||||
|
||||
foreach (var cb in AccPanel.Children.OfType<CheckBox>())
|
||||
{
|
||||
var pi = cb.Tag as PartItem;
|
||||
cb.IsChecked = pi != null && _build.Accessories.Any(a => a.FileName == pi.FileName);
|
||||
}
|
||||
HighlightSwatch(_build.HairColor);
|
||||
PopulateLayerCombo();
|
||||
_suppress = false;
|
||||
}
|
||||
|
||||
private string? ShapeOf(string? fileName)
|
||||
{
|
||||
if (fileName == null) return null;
|
||||
return _headFrames.FirstOrDefault(h => h.FileName == fileName)?.Shape;
|
||||
}
|
||||
|
||||
private void PopulateExpr(string? shape)
|
||||
{
|
||||
var frames = _headFrames.Where(h => h.Shape == shape)
|
||||
.OrderBy(h => h.Expr == "(base)" ? 0 : 1).ThenBy(h => h.Expr)
|
||||
.Select(h => new PartItem { FileName = h.FileName, FilePath = h.FilePath, Shape = h.Shape, Expr = h.Expr, Display = h.Expr })
|
||||
.ToList();
|
||||
CmbExpr.ItemsSource = frames;
|
||||
}
|
||||
|
||||
// ---------- accessory checklist ----------
|
||||
private void BuildAccessoryChecks()
|
||||
{
|
||||
AccPanel.Children.Clear();
|
||||
if (_accessories.Count == 0)
|
||||
{
|
||||
AccPanel.Children.Add(new TextBlock { Text = "(악세서리 이미지 없음)", Foreground = (Brush)FindResource("Mute"), FontSize = 12 });
|
||||
return;
|
||||
}
|
||||
foreach (var a in _accessories)
|
||||
{
|
||||
var cb = new CheckBox { Content = a.Display, Tag = a };
|
||||
cb.Checked += Acc_Toggled;
|
||||
cb.Unchecked += Acc_Toggled;
|
||||
AccPanel.Children.Add(cb);
|
||||
}
|
||||
}
|
||||
|
||||
private void Acc_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_suppress) return;
|
||||
if (sender is not CheckBox cb || cb.Tag is not PartItem pi) return;
|
||||
if (cb.IsChecked == true)
|
||||
{
|
||||
if (!_build.Accessories.Any(a => a.FileName == pi.FileName))
|
||||
{
|
||||
var layer = new AccessoryLayer { FileName = pi.FileName };
|
||||
var low = pi.FileName.ToLowerInvariant();
|
||||
// head-worn items start anchored to the head; wrist/foot items keep defaults.
|
||||
// Accessory art is framed standalone (roughly centred in its own canvas), so it must
|
||||
// start well below the head's own scale or it dwarfs the head — the user then fine-tunes.
|
||||
if (low.Contains("headphone") || low.Contains("catear") || low.Contains("clubband")
|
||||
|| low.Contains("cap") || low.Contains("glasses") || low.Contains("hat") || low.Contains("ear"))
|
||||
{
|
||||
layer.OffsetX = _build.HeadOffsetX;
|
||||
layer.OffsetY = _build.HeadOffsetY;
|
||||
layer.Scale = _build.HeadScale * 0.62;
|
||||
}
|
||||
else
|
||||
{
|
||||
// props / wrist / foot items: sit near the body centre at a modest size
|
||||
layer.OffsetX = _build.BodyOffsetX;
|
||||
layer.OffsetY = _build.BodyOffsetY;
|
||||
layer.Scale = Math.Max(0.18, _build.HeadScale * 0.5);
|
||||
}
|
||||
_build.Accessories.Add(layer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_build.Accessories.RemoveAll(a => a.FileName == pi.FileName);
|
||||
}
|
||||
PopulateLayerCombo();
|
||||
RebuildStage();
|
||||
}
|
||||
|
||||
// ---------- swatches ----------
|
||||
private void BuildSwatches()
|
||||
{
|
||||
SwatchPanel.Children.Clear();
|
||||
foreach (var (label, hex) in Swatches)
|
||||
{
|
||||
var btn = new Button
|
||||
{
|
||||
Width = 38,
|
||||
Height = 38,
|
||||
Margin = new Thickness(0, 0, 10, 10),
|
||||
Padding = new Thickness(0),
|
||||
Content = label,
|
||||
Tag = hex,
|
||||
Background = hex == null ? (Brush)FindResource("Panel2") : new SolidColorBrush((Color)ColorConverter.ConvertFromString(hex)),
|
||||
Foreground = (Brush)FindResource("Text"),
|
||||
};
|
||||
btn.Click += Swatch_Click;
|
||||
SwatchPanel.Children.Add(btn);
|
||||
}
|
||||
}
|
||||
|
||||
private void Swatch_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is not Button b) return;
|
||||
_build.HairColor = b.Tag as string;
|
||||
HighlightSwatch(_build.HairColor);
|
||||
RebuildStage();
|
||||
}
|
||||
|
||||
private void HighlightSwatch(string? hex)
|
||||
{
|
||||
foreach (var b in SwatchPanel.Children.OfType<Button>())
|
||||
{
|
||||
bool sel = string.Equals((b.Tag as string) ?? "", hex ?? "", StringComparison.OrdinalIgnoreCase);
|
||||
b.BorderBrush = sel ? (Brush)FindResource("Accent") : (Brush)FindResource("Stroke");
|
||||
b.BorderThickness = new Thickness(sel ? 2 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- compose events ----------
|
||||
private void Body_Changed(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_suppress) return;
|
||||
if (CmbBody.SelectedItem is PartItem p) { _build.BodyFile = p.FileName; AutoAlignHead(); }
|
||||
}
|
||||
|
||||
private void Shape_Changed(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_suppress) return;
|
||||
if (CmbShape.SelectedItem is not string shape) return;
|
||||
_build.HeadFile = _headFrames.FirstOrDefault(h => h.Shape == shape && h.Expr == "(base)")?.FileName
|
||||
?? _headFrames.FirstOrDefault(h => h.Shape == shape)?.FileName;
|
||||
_build.HairmaskFile = _hairmaskByShape.TryGetValue(shape, out var hm) ? hm.FileName : null;
|
||||
_suppress = true;
|
||||
PopulateExpr(shape);
|
||||
var neutral = (CmbExpr.ItemsSource as IEnumerable<PartItem>)?.FirstOrDefault(x => x.Expr == "neutral")
|
||||
?? (CmbExpr.ItemsSource as IEnumerable<PartItem>)?.FirstOrDefault();
|
||||
CmbExpr.SelectedItem = neutral;
|
||||
_build.ExpressionFile = neutral?.FileName ?? _build.HeadFile;
|
||||
_suppress = false;
|
||||
AutoAlignHead();
|
||||
}
|
||||
|
||||
private void Expr_Changed(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_suppress) return;
|
||||
if (CmbExpr.SelectedItem is PartItem p) { _build.ExpressionFile = p.FileName; RebuildStage(); }
|
||||
}
|
||||
|
||||
// ---------- layer transform ----------
|
||||
// CmbLayer index: 0 = head, 1 = body, 2.. = accessories. Every layer is adjustable.
|
||||
private void PopulateLayerCombo()
|
||||
{
|
||||
_suppress = true;
|
||||
CmbLayer.Items.Clear();
|
||||
CmbLayer.Items.Add("머리 (Head)");
|
||||
CmbLayer.Items.Add("바디 (Body)");
|
||||
foreach (var a in _build.Accessories) CmbLayer.Items.Add(a.FileName);
|
||||
CmbLayer.SelectedIndex = 0;
|
||||
_suppress = false;
|
||||
LoadLayerToSliders();
|
||||
}
|
||||
|
||||
private void Layer_Changed(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (_suppress) return;
|
||||
LoadLayerToSliders();
|
||||
}
|
||||
|
||||
private (double x, double y, double s) GetLayer(int idx)
|
||||
{
|
||||
if (idx == 1) return (_build.BodyOffsetX, _build.BodyOffsetY, _build.BodyScale);
|
||||
if (idx >= 2 && idx - 2 < _build.Accessories.Count)
|
||||
{
|
||||
var a = _build.Accessories[idx - 2];
|
||||
return (a.OffsetX, a.OffsetY, a.Scale);
|
||||
}
|
||||
return (_build.HeadOffsetX, _build.HeadOffsetY, _build.HeadScale);
|
||||
}
|
||||
|
||||
private void SetLayer(int idx, double x, double y, double s)
|
||||
{
|
||||
if (idx == 1) { _build.BodyOffsetX = x; _build.BodyOffsetY = y; _build.BodyScale = s; }
|
||||
else if (idx >= 2 && idx - 2 < _build.Accessories.Count)
|
||||
{
|
||||
var a = _build.Accessories[idx - 2];
|
||||
a.OffsetX = x; a.OffsetY = y; a.Scale = s;
|
||||
}
|
||||
else { _build.HeadOffsetX = x; _build.HeadOffsetY = y; _build.HeadScale = s; }
|
||||
}
|
||||
|
||||
private void LoadLayerToSliders()
|
||||
{
|
||||
_suppress = true;
|
||||
var (x, y, s) = GetLayer(CmbLayer.SelectedIndex);
|
||||
SldX.Value = Math.Max(SldX.Minimum, Math.Min(SldX.Maximum, x));
|
||||
SldY.Value = Math.Max(SldY.Minimum, Math.Min(SldY.Maximum, y));
|
||||
SldScale.Value = Math.Max(SldScale.Minimum, Math.Min(SldScale.Maximum, s));
|
||||
_suppress = false;
|
||||
}
|
||||
|
||||
private void LayerXform_Changed(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (_suppress) return;
|
||||
SetLayer(CmbLayer.SelectedIndex, SldX.Value, SldY.Value, SldScale.Value);
|
||||
RebuildStage();
|
||||
}
|
||||
|
||||
// ---------- stage compositing ----------
|
||||
private string? ResolvePath(string? fn)
|
||||
=> (fn != null && _pathByName.TryGetValue(fn, out var p)) ? p : null;
|
||||
|
||||
private void RebuildStage()
|
||||
{
|
||||
if (Stage == null) return;
|
||||
Stage.Children.Clear();
|
||||
_headImage = null; _headNormal = null; _headBlink = null;
|
||||
if (_char == null) return;
|
||||
|
||||
// body
|
||||
var bodyPath = ResolvePath(_build.BodyFile);
|
||||
if (bodyPath != null) AddLayer(bodyPath, _build.BodyScale, _build.BodyOffsetX, _build.BodyOffsetY);
|
||||
|
||||
// head (expression frame preferred)
|
||||
var headPath = ResolvePath(_build.ExpressionFile) ?? ResolvePath(_build.HeadFile);
|
||||
if (headPath != null)
|
||||
{
|
||||
_headImage = AddLayer(headPath, _build.HeadScale, _build.HeadOffsetX, _build.HeadOffsetY);
|
||||
_headNormal = _headImage.Source;
|
||||
var shape = ShapeOf(_build.ExpressionFile) ?? ShapeOf(_build.HeadFile);
|
||||
var blink = _headFrames.FirstOrDefault(h => h.Shape == shape && h.Expr == "blink");
|
||||
if (blink != null) _headBlink = AssetScanner.LoadPart(blink.FilePath);
|
||||
|
||||
// hair tint
|
||||
var maskPath = ResolvePath(_build.HairmaskFile);
|
||||
if (maskPath != null && !string.IsNullOrEmpty(_build.HairColor))
|
||||
AddTint(maskPath, _build.HairColor!, _build.HeadScale, _build.HeadOffsetX, _build.HeadOffsetY);
|
||||
}
|
||||
|
||||
// accessories
|
||||
foreach (var a in _build.Accessories)
|
||||
{
|
||||
var ap = ResolvePath(a.FileName);
|
||||
if (ap != null) AddLayer(ap, a.Scale, a.OffsetX, a.OffsetY);
|
||||
}
|
||||
}
|
||||
|
||||
private Image AddLayer(string path, double scale, double offX, double offY)
|
||||
{
|
||||
var src = AssetScanner.LoadPart(path);
|
||||
double aspect = (src != null && src.PixelHeight > 0) ? (double)src.PixelWidth / src.PixelHeight : 1.0;
|
||||
// Bake scale AND the contain-fit into the element's SIZE (not a RenderTransform). A full-size
|
||||
// element (680*aspect) overflows the stage and the Viewbox clips it — cutting the character —
|
||||
// *before* any RenderTransform can shrink it. A pre-sized element only overflows by its
|
||||
// transparent margins, so the character is never clipped.
|
||||
double s = scale * _fit;
|
||||
var img = new Image
|
||||
{
|
||||
Source = src,
|
||||
Stretch = Stretch.Fill,
|
||||
Width = StageH * aspect * s,
|
||||
Height = StageH * s,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
RenderTransform = new TranslateTransform((offX - _fitCX) * _fit, (offY - _fitCY) * _fit),
|
||||
IsHitTestVisible = false,
|
||||
};
|
||||
Stage.Children.Add(img);
|
||||
return img;
|
||||
}
|
||||
|
||||
private void AddTint(string maskPath, string hex, double scale, double offX, double offY)
|
||||
{
|
||||
try
|
||||
{
|
||||
var mask = AssetScanner.LoadBitmap(maskPath);
|
||||
if (mask == null) return;
|
||||
double aspect = mask.PixelHeight > 0 ? (double)mask.PixelWidth / mask.PixelHeight : 1;
|
||||
double s = scale * _fit;
|
||||
var rect = new Rectangle
|
||||
{
|
||||
Height = StageH * s,
|
||||
Width = StageH * aspect * s,
|
||||
Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString(hex)) { Opacity = 0.8 },
|
||||
OpacityMask = new ImageBrush(mask) { Stretch = Stretch.Uniform },
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
RenderTransform = new TranslateTransform((offX - _fitCX) * _fit, (offY - _fitCY) * _fit),
|
||||
IsHitTestVisible = false,
|
||||
};
|
||||
Stage.Children.Add(rect);
|
||||
}
|
||||
catch { /* tint is best-effort */ }
|
||||
}
|
||||
|
||||
// ---------- drag to move the selected layer ----------
|
||||
private bool _dragging;
|
||||
private Point _dragStart;
|
||||
private double _dragBaseX, _dragBaseY;
|
||||
|
||||
private void Stage_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
if (_char == null) return;
|
||||
(_dragBaseX, _dragBaseY) = GetSelectedOffset();
|
||||
_dragStart = e.GetPosition(Stage);
|
||||
_dragging = true;
|
||||
StageHost.CaptureMouse();
|
||||
}
|
||||
|
||||
private void Stage_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
|
||||
{
|
||||
if (!_dragging) return;
|
||||
var p = e.GetPosition(Stage);
|
||||
double f = _fit == 0 ? 1 : _fit; // layers are drawn at offset*_fit, so undo it for the delta
|
||||
SetSelectedOffset(_dragBaseX + (p.X - _dragStart.X) / f, _dragBaseY + (p.Y - _dragStart.Y) / f);
|
||||
}
|
||||
|
||||
private void Stage_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_dragging) return;
|
||||
_dragging = false;
|
||||
StageHost.ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
/// <summary>Offset of the layer currently chosen in CmbLayer (head/body/accessory).</summary>
|
||||
private (double x, double y) GetSelectedOffset()
|
||||
{
|
||||
var (x, y, _) = GetLayer(CmbLayer.SelectedIndex);
|
||||
return (x, y);
|
||||
}
|
||||
|
||||
private void SetSelectedOffset(double x, double y)
|
||||
{
|
||||
var (_, _, s) = GetLayer(CmbLayer.SelectedIndex);
|
||||
SetLayer(CmbLayer.SelectedIndex, Math.Round(x), Math.Round(y), s);
|
||||
_suppress = true;
|
||||
SldX.Value = Math.Max(SldX.Minimum, Math.Min(SldX.Maximum, Math.Round(x)));
|
||||
SldY.Value = Math.Max(SldY.Minimum, Math.Min(SldY.Maximum, Math.Round(y)));
|
||||
_suppress = false;
|
||||
RebuildStage();
|
||||
}
|
||||
|
||||
// ---------- auto-align (alpha neck detection) ----------
|
||||
private void AutoAlign_Click(object sender, RoutedEventArgs e) => AutoAlignHead();
|
||||
|
||||
/// <summary>
|
||||
/// Fit the whole figure: a headless body drawn full-height leaves no room for the head, so
|
||||
/// scale the body down and drop it, then sit the head on top with a visible neck.
|
||||
/// </summary>
|
||||
private void AutoAlignHead()
|
||||
{
|
||||
var bodyPath = ResolvePath(_build.BodyFile);
|
||||
var headPath = ResolvePath(_build.ExpressionFile) ?? ResolvePath(_build.HeadFile);
|
||||
if (bodyPath == null || headPath == null) { RebuildStage(); return; }
|
||||
|
||||
var ab = AlphaTools.Analyze(bodyPath);
|
||||
var ah = AlphaTools.Analyze(headPath);
|
||||
if (ab == null || ah == null) { RebuildStage(); return; }
|
||||
|
||||
const double chin = 0.58;
|
||||
|
||||
// Head:body height ratio — waist-up poses (landscape images) carry a larger head, full-body
|
||||
// poses (portrait images) a smaller one. These are nominal scales; UpdateFit() then scales
|
||||
// the whole composition to fill the preview, so absolute values here only set the ratio.
|
||||
bool fullBody = (double)ab.W / ab.H < 0.9;
|
||||
double r = fullBody ? 0.24 : 0.60; // full-body figures need a smaller (natural) head
|
||||
// Size the head against the body *below the neck* (torso→feet), not the full bbox: poses with
|
||||
// raised arms make the bbox taller, which would otherwise inflate the head ("big-head" look).
|
||||
double bodyBFrac = (double)(ab.MaxY - ab.NeckTop) / ab.H;
|
||||
double M = bodyBFrac * ah.H / (double)ah.BH;
|
||||
double bodyScale = 1.0;
|
||||
double hs = Math.Max(0.15, Math.Min(1.3, r * M));
|
||||
|
||||
// Attach the head at the body's neck: neck top (robust to raised arms) for Y, torso axis for X.
|
||||
double bodyNeckTopFrac = (double)ab.NeckTop / ab.H;
|
||||
double bodyOffY = -bodyScale * (bodyNeckTopFrac - 0.5) * StageH; // neck line at stage centre
|
||||
double lwB = StageH * ((double)ab.W / ab.H), lwH = StageH * ((double)ah.W / ah.H);
|
||||
double bodyNeckX = bodyScale * ((double)ab.AxisX / ab.W - 0.5) * lwB;
|
||||
double headCenterXf = (ah.MinX + ah.MaxX) / 2.0 / ah.W;
|
||||
double headOffX = bodyNeckX - hs * (headCenterXf - 0.5) * lwH;
|
||||
double headNeckFrac = (ah.MinY + chin * ah.BH) / (double)ah.H;
|
||||
double headOffY = -hs * (headNeckFrac - 0.5) * StageH;
|
||||
|
||||
_build.BodyScale = Math.Round(bodyScale, 3);
|
||||
_build.BodyOffsetX = 0;
|
||||
_build.BodyOffsetY = Math.Round(bodyOffY);
|
||||
_build.HeadScale = Math.Round(hs, 3);
|
||||
_build.HeadOffsetX = Math.Round(headOffX);
|
||||
_build.HeadOffsetY = Math.Round(headOffY);
|
||||
|
||||
UpdateFit();
|
||||
LoadLayerToSliders();
|
||||
RebuildStage();
|
||||
}
|
||||
|
||||
/// <summary>Scale/centre the whole composition (head + body) to fill the preview stage.</summary>
|
||||
private void UpdateFit()
|
||||
{
|
||||
var ab = ResolvePath(_build.BodyFile) is { } bp ? AlphaTools.Analyze(bp) : null;
|
||||
var ah = ResolvePath(_build.ExpressionFile) is { } ep ? AlphaTools.Analyze(ep)
|
||||
: ResolvePath(_build.HeadFile) is { } hp2 ? AlphaTools.Analyze(hp2) : null;
|
||||
|
||||
double x0 = double.MaxValue, x1 = double.MinValue, y0 = double.MaxValue, y1 = double.MinValue;
|
||||
void Acc(AlphaTools.Analysis? a, double s, double ox, double oy)
|
||||
{
|
||||
if (a == null) return;
|
||||
double lw = StageH * ((double)a.W / a.H);
|
||||
foreach (var fx in new[] { (double)a.MinX / a.W, (double)a.MaxX / a.W })
|
||||
{ double x = s * (fx - 0.5) * lw + ox; x0 = Math.Min(x0, x); x1 = Math.Max(x1, x); }
|
||||
foreach (var fy in new[] { (double)a.MinY / a.H, (double)a.MaxY / a.H })
|
||||
{ double y = s * (fy - 0.5) * StageH + oy; y0 = Math.Min(y0, y); y1 = Math.Max(y1, y); }
|
||||
}
|
||||
Acc(ab, _build.BodyScale, _build.BodyOffsetX, _build.BodyOffsetY);
|
||||
Acc(ah, _build.HeadScale, _build.HeadOffsetX, _build.HeadOffsetY);
|
||||
|
||||
if (x1 <= x0 || y1 <= y0) { _fit = 1; _fitCX = _fitCY = 0; return; }
|
||||
const double m = 0.03;
|
||||
_fit = Math.Min(StageW * (1 - 2 * m) / (x1 - x0), StageH * (1 - 2 * m) / (y1 - y0));
|
||||
_fitCX = (x0 + x1) / 2;
|
||||
_fitCY = (y0 + y1) / 2;
|
||||
}
|
||||
|
||||
// ---------- gesture preview ----------
|
||||
private void Play_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_char == null) return;
|
||||
if (_playing) StopPlay(); else StartPlay();
|
||||
}
|
||||
|
||||
private void StartPlay()
|
||||
{
|
||||
_playing = true; _phase = 0; _blinkRestoreAt = -1;
|
||||
BtnPlay.Content = "⏸ 정지";
|
||||
_timer.Start();
|
||||
}
|
||||
|
||||
private void StopPlay()
|
||||
{
|
||||
_playing = false;
|
||||
_timer.Stop();
|
||||
_floatT.Y = 0;
|
||||
BtnPlay.Content = "▶ 제스처 미리보기";
|
||||
if (_headImage != null && _headNormal != null) _headImage.Source = _headNormal;
|
||||
}
|
||||
|
||||
private void Timer_Tick(object? sender, EventArgs e)
|
||||
{
|
||||
_phase++;
|
||||
_floatT.Y = Math.Sin(_phase * 0.09) * 7;
|
||||
|
||||
// blink
|
||||
if (_headImage != null && _headBlink != null)
|
||||
{
|
||||
if (_phase % 55 == 0) { _headImage.Source = _headBlink; _blinkRestoreAt = _phase + 8; }
|
||||
else if (_phase == _blinkRestoreAt && _headNormal != null) { _headImage.Source = _headNormal; }
|
||||
}
|
||||
|
||||
// cycle gestures (bodies)
|
||||
if (CmbBody.Items.Count > 1 && _phase % 45 == 0)
|
||||
CmbBody.SelectedIndex = (CmbBody.SelectedIndex + 1) % CmbBody.Items.Count;
|
||||
}
|
||||
|
||||
// ---------- save / load / reset ----------
|
||||
private void Save_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_char == null) { TxtStatus.Text = "먼저 캐릭터를 선택하세요."; return; }
|
||||
var dlg = new Microsoft.Win32.SaveFileDialog
|
||||
{
|
||||
Title = "캐릭터 조합 저장",
|
||||
InitialDirectory = _char.FolderPath,
|
||||
Filter = "Markdown (*.md)|*.md",
|
||||
FileName = _char.Name + "_build.md",
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
_build.Name = System.IO.Path.GetFileNameWithoutExtension(dlg.FileName);
|
||||
_build.Character = _char.Name;
|
||||
BuildMd.Save(dlg.FileName, _build);
|
||||
TxtCharName.Text = _char.Name + " · " + _build.Name;
|
||||
TxtStatus.Text = "저장됨: " + dlg.FileName;
|
||||
}
|
||||
|
||||
private void Load_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Title = "캐릭터 조합 불러오기",
|
||||
InitialDirectory = _char?.FolderPath ?? _root ?? "",
|
||||
Filter = "Markdown (*.md)|*.md",
|
||||
};
|
||||
if (dlg.ShowDialog() != true) return;
|
||||
|
||||
BuildDefinition b;
|
||||
try { b = BuildMd.Load(dlg.FileName); }
|
||||
catch (Exception ex) { TxtStatus.Text = "불러오기 실패: " + ex.Message; return; }
|
||||
|
||||
var ci = _characters.FirstOrDefault(c => string.Equals(c.Name, b.Character, StringComparison.OrdinalIgnoreCase));
|
||||
if (ci == null) { TxtStatus.Text = "캐릭터 폴더를 찾을 수 없습니다: " + b.Character; return; }
|
||||
|
||||
_suppress = true;
|
||||
CharList.SelectedItem = ci;
|
||||
_suppress = false;
|
||||
SelectCharacter(ci, b);
|
||||
TxtStatus.Text = "불러옴: " + dlg.FileName;
|
||||
}
|
||||
|
||||
private void Reset_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_char != null) SelectCharacter(_char, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Character_Builder;
|
||||
|
||||
/// <summary>A character folder (has a Reference/ sheet).</summary>
|
||||
public class CharacterInfo
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string FolderPath { get; set; } = "";
|
||||
public string? SheetPath { get; set; }
|
||||
public ImageSource? Thumb { get; set; }
|
||||
public override string ToString() => Name;
|
||||
}
|
||||
|
||||
/// <summary>One scanned PNG asset (body/head/expression/hairmask/accessory).</summary>
|
||||
public class PartItem
|
||||
{
|
||||
public string Display { get; set; } = "";
|
||||
public string FileName { get; set; } = "";
|
||||
public string FilePath { get; set; } = "";
|
||||
public string Shape { get; set; } = ""; // for heads/expressions
|
||||
public string Expr { get; set; } = ""; // for expression frames
|
||||
public override string ToString() => Display;
|
||||
}
|
||||
|
||||
/// <summary>A composited accessory layer with its own transform.</summary>
|
||||
public class AccessoryLayer
|
||||
{
|
||||
public string FileName { get; set; } = "";
|
||||
public double OffsetX { get; set; }
|
||||
public double OffsetY { get; set; } = -190;
|
||||
public double Scale { get; set; } = 0.42;
|
||||
}
|
||||
|
||||
/// <summary>The saveable/loadable composition.</summary>
|
||||
public class BuildDefinition
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Character { get; set; } = "";
|
||||
public string? BodyFile { get; set; }
|
||||
public double BodyOffsetX { get; set; }
|
||||
public double BodyOffsetY { get; set; }
|
||||
public double BodyScale { get; set; } = 1.0;
|
||||
public string? HeadFile { get; set; }
|
||||
public string? ExpressionFile { get; set; }
|
||||
public string? HairmaskFile { get; set; }
|
||||
public string? HairColor { get; set; }
|
||||
public double HeadOffsetX { get; set; }
|
||||
public double HeadOffsetY { get; set; } = -190;
|
||||
public double HeadScale { get; set; } = 0.42;
|
||||
public List<AccessoryLayer> Accessories { get; set; } = new();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"runtimeTarget": {
|
||||
"name": ".NETCoreApp,Version=v8.0",
|
||||
"signature": ""
|
||||
},
|
||||
"compilationOptions": {},
|
||||
"targets": {
|
||||
".NETCoreApp,Version=v8.0": {
|
||||
"Character_Builder/1.0.0": {
|
||||
"runtime": {
|
||||
"Character_Builder.dll": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"Character_Builder/1.0.0": {
|
||||
"type": "project",
|
||||
"serviceable": false,
|
||||
"sha512": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net8.0",
|
||||
"frameworks": [
|
||||
{
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "8.0.0"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.WindowsDesktop.App",
|
||||
"version": "8.0.0"
|
||||
}
|
||||
],
|
||||
"configProperties": {
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"format": 1,
|
||||
"restore": {
|
||||
"D:\\Work_AI\\Dansori\\Characters_Build_Docs\\Character_Builder\\Character_Builder.csproj": {}
|
||||
},
|
||||
"projects": {
|
||||
"D:\\Work_AI\\Dansori\\Characters_Build_Docs\\Character_Builder\\Character_Builder.csproj": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "D:\\Work_AI\\Dansori\\Characters_Build_Docs\\Character_Builder\\Character_Builder.csproj",
|
||||
"projectName": "Character_Builder",
|
||||
"projectPath": "D:\\Work_AI\\Dansori\\Characters_Build_Docs\\Character_Builder\\Character_Builder.csproj",
|
||||
"packagesPath": "C:\\Users\\eKeerar\\.nuget\\packages\\",
|
||||
"outputPath": "D:\\Work_AI\\Dansori\\Characters_Build_Docs\\Character_Builder\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\eKeerar\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0-windows"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0-windows7.0": {
|
||||
"targetAlias": "net8.0-windows",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0-windows7.0": {
|
||||
"targetAlias": "net8.0-windows",
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
},
|
||||
"Microsoft.WindowsDesktop.App.WPF": {
|
||||
"privateAssets": "none"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.422/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
|
||||
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
|
||||
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
|
||||
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
|
||||
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\eKeerar\.nuget\packages\</NuGetPackageFolders>
|
||||
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
|
||||
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.11.2</NuGetToolVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
|
||||
<SourceRoot Include="C:\Users\eKeerar\.nuget\packages\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8" standalone="no"?>
|
||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
// <autogenerated />
|
||||
using System;
|
||||
using System.Reflection;
|
||||
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v8.0", FrameworkDisplayName = ".NET 8.0")]
|
||||
Binary file not shown.
@@ -0,0 +1,82 @@
|
||||
#pragma checksum "..\..\..\App.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "93E40EC7B36C3FCE2164127217EDE90B9E9D0788"
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Controls.Ribbon;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Effects;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media.Media3D;
|
||||
using System.Windows.Media.TextFormatting;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Shell;
|
||||
|
||||
|
||||
namespace Character_Builder {
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// App
|
||||
/// </summary>
|
||||
public partial class App : System.Windows.Application {
|
||||
|
||||
private bool _contentLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// InitializeComponent
|
||||
/// </summary>
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "8.0.28.0")]
|
||||
public void InitializeComponent() {
|
||||
if (_contentLoaded) {
|
||||
return;
|
||||
}
|
||||
_contentLoaded = true;
|
||||
|
||||
#line 4 "..\..\..\App.xaml"
|
||||
this.StartupUri = new System.Uri("MainWindow.xaml", System.UriKind.Relative);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
System.Uri resourceLocater = new System.Uri("/Character_Builder;component/app.xaml", System.UriKind.Relative);
|
||||
|
||||
#line 1 "..\..\..\App.xaml"
|
||||
System.Windows.Application.LoadComponent(this, resourceLocater);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Application Entry Point.
|
||||
/// </summary>
|
||||
[System.STAThreadAttribute()]
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "8.0.28.0")]
|
||||
public static void Main() {
|
||||
Character_Builder.App app = new Character_Builder.App();
|
||||
app.InitializeComponent();
|
||||
app.Run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: System.Reflection.AssemblyCompanyAttribute("Character_Builder")]
|
||||
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
|
||||
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
|
||||
[assembly: System.Reflection.AssemblyProductAttribute("Character_Builder")]
|
||||
[assembly: System.Reflection.AssemblyTitleAttribute("Character_Builder")]
|
||||
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
|
||||
[assembly: System.Runtime.Versioning.TargetPlatformAttribute("Windows7.0")]
|
||||
[assembly: System.Runtime.Versioning.SupportedOSPlatformAttribute("Windows7.0")]
|
||||
|
||||
// MSBuild WriteCodeFragment 클래스에서 생성되었습니다.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1c258de41a7d7aa346051148913e5c061ad06ddde6059e732bbb9876cdf09ec2
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
is_global = true
|
||||
build_property.TargetFramework = net8.0-windows
|
||||
build_property.TargetPlatformMinVersion = 7.0
|
||||
build_property.UsingMicrosoftNETSdkWeb =
|
||||
build_property.ProjectTypeGuids =
|
||||
build_property.InvariantGlobalization =
|
||||
build_property.PlatformNeutralAssembly =
|
||||
build_property.EnforceExtendedAnalyzerRules =
|
||||
build_property._SupportedPlatformList = Linux,macOS,Windows
|
||||
build_property.RootNamespace = Character_Builder
|
||||
build_property.ProjectDir = D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\
|
||||
build_property.EnableComHosting =
|
||||
build_property.EnableGeneratedComInterfaceComImportInterop =
|
||||
@@ -0,0 +1,6 @@
|
||||
// <auto-generated/>
|
||||
global using global::System;
|
||||
global using global::System.Collections.Generic;
|
||||
global using global::System.Linq;
|
||||
global using global::System.Threading;
|
||||
global using global::System.Threading.Tasks;
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
07a0985beef6e5eb47d3a46d05ca778b8054347852e2b1cae45c9a00e86007f5
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\MainWindow.baml
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\App.baml
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\MainWindow.g.cs
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\App.g.cs
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder_MarkupCompile.cache
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.g.resources
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.GeneratedMSBuildEditorConfig.editorconfig
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.AssemblyInfoInputs.cache
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.AssemblyInfo.cs
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.csproj.CoreCompileInputs.cache
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\bin\Debug\net8.0-windows\Character_Builder.exe
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\bin\Debug\net8.0-windows\Character_Builder.deps.json
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\bin\Debug\net8.0-windows\Character_Builder.runtimeconfig.json
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\bin\Debug\net8.0-windows\Character_Builder.dll
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\bin\Debug\net8.0-windows\Character_Builder.pdb
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.dll
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\refint\Character_Builder.dll
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.pdb
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.genruntimeconfig.cache
|
||||
D:\개인자료\Work_AI\DansoriEQ\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\ref\Character_Builder.dll
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\bin\Debug\net8.0-windows\Character_Builder.exe
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\bin\Debug\net8.0-windows\Character_Builder.deps.json
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\bin\Debug\net8.0-windows\Character_Builder.runtimeconfig.json
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\bin\Debug\net8.0-windows\Character_Builder.dll
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\bin\Debug\net8.0-windows\Character_Builder.pdb
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\MainWindow.baml
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\App.baml
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\MainWindow.g.cs
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\App.g.cs
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder_MarkupCompile.cache
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.g.resources
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.GeneratedMSBuildEditorConfig.editorconfig
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.AssemblyInfoInputs.cache
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.AssemblyInfo.cs
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.csproj.CoreCompileInputs.cache
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.dll
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\refint\Character_Builder.dll
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.pdb
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\Character_Builder.genruntimeconfig.cache
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\ref\Character_Builder.dll
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
e0da6d5848fb3c8f7177291c1a3794bcd05e16ca5e19ea615d9ef22a5fe104df
|
||||
Binary file not shown.
@@ -0,0 +1,20 @@
|
||||
Character_Builder
|
||||
|
||||
|
||||
winexe
|
||||
C#
|
||||
.cs
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\obj\Debug\net8.0-windows\
|
||||
Character_Builder
|
||||
none
|
||||
false
|
||||
TRACE;DEBUG;NET;NET8_0;NETCOREAPP
|
||||
D:\Work_AI\Dansori\Characters_Build_Docs\Character_Builder\App.xaml
|
||||
11407045341
|
||||
|
||||
827567737
|
||||
1981026658812
|
||||
MainWindow.xaml;
|
||||
|
||||
False
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,463 @@
|
||||
#pragma checksum "..\..\..\MainWindow.xaml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "8A60234C00FE3CBC6D4956AD0F7CF945C604F6A6"
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Automation;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Controls.Ribbon;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Effects;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media.Media3D;
|
||||
using System.Windows.Media.TextFormatting;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Shell;
|
||||
|
||||
|
||||
namespace Character_Builder {
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// MainWindow
|
||||
/// </summary>
|
||||
public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
|
||||
|
||||
|
||||
#line 16 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Border RootBorder;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 35 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button BtnRoot;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 36 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button BtnLoad;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 37 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button BtnReset;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 38 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button BtnSave;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 40 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button BtnMin;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 43 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button BtnMax;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 46 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button BtnClose;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 64 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.ListBox CharList;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 112 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock TxtCharName;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 114 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Button BtnPlay;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 119 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock PreviewHint;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 123 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Grid StageHost;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 128 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Grid Stage;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 143 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.ComboBox CmbBody;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 146 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.ComboBox CmbShape;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 149 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.ComboBox CmbExpr;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 152 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.WrapPanel SwatchPanel;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 158 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.StackPanel AccPanel;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 163 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.ComboBox CmbLayer;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 175 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Slider SldX;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 177 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Slider SldY;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 179 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.Slider SldScale;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
|
||||
#line 190 "..\..\..\MainWindow.xaml"
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
|
||||
internal System.Windows.Controls.TextBlock TxtStatus;
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
private bool _contentLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// InitializeComponent
|
||||
/// </summary>
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "8.0.28.0")]
|
||||
public void InitializeComponent() {
|
||||
if (_contentLoaded) {
|
||||
return;
|
||||
}
|
||||
_contentLoaded = true;
|
||||
System.Uri resourceLocater = new System.Uri("/Character_Builder;component/mainwindow.xaml", System.UriKind.Relative);
|
||||
|
||||
#line 1 "..\..\..\MainWindow.xaml"
|
||||
System.Windows.Application.LoadComponent(this, resourceLocater);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
}
|
||||
|
||||
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "8.0.28.0")]
|
||||
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
|
||||
void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
|
||||
switch (connectionId)
|
||||
{
|
||||
case 1:
|
||||
this.RootBorder = ((System.Windows.Controls.Border)(target));
|
||||
return;
|
||||
case 2:
|
||||
this.BtnRoot = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 35 "..\..\..\MainWindow.xaml"
|
||||
this.BtnRoot.Click += new System.Windows.RoutedEventHandler(this.Root_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 3:
|
||||
this.BtnLoad = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 36 "..\..\..\MainWindow.xaml"
|
||||
this.BtnLoad.Click += new System.Windows.RoutedEventHandler(this.Load_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 4:
|
||||
this.BtnReset = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 37 "..\..\..\MainWindow.xaml"
|
||||
this.BtnReset.Click += new System.Windows.RoutedEventHandler(this.Reset_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 5:
|
||||
this.BtnSave = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 38 "..\..\..\MainWindow.xaml"
|
||||
this.BtnSave.Click += new System.Windows.RoutedEventHandler(this.Save_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 6:
|
||||
this.BtnMin = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 40 "..\..\..\MainWindow.xaml"
|
||||
this.BtnMin.Click += new System.Windows.RoutedEventHandler(this.Min_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 7:
|
||||
this.BtnMax = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 43 "..\..\..\MainWindow.xaml"
|
||||
this.BtnMax.Click += new System.Windows.RoutedEventHandler(this.Max_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 8:
|
||||
this.BtnClose = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 46 "..\..\..\MainWindow.xaml"
|
||||
this.BtnClose.Click += new System.Windows.RoutedEventHandler(this.Close_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 9:
|
||||
this.CharList = ((System.Windows.Controls.ListBox)(target));
|
||||
|
||||
#line 66 "..\..\..\MainWindow.xaml"
|
||||
this.CharList.SelectionChanged += new System.Windows.Controls.SelectionChangedEventHandler(this.CharList_SelectionChanged);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 10:
|
||||
this.TxtCharName = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 11:
|
||||
this.BtnPlay = ((System.Windows.Controls.Button)(target));
|
||||
|
||||
#line 114 "..\..\..\MainWindow.xaml"
|
||||
this.BtnPlay.Click += new System.Windows.RoutedEventHandler(this.Play_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 12:
|
||||
this.PreviewHint = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
case 13:
|
||||
this.StageHost = ((System.Windows.Controls.Grid)(target));
|
||||
|
||||
#line 125 "..\..\..\MainWindow.xaml"
|
||||
this.StageHost.MouseLeftButtonDown += new System.Windows.Input.MouseButtonEventHandler(this.Stage_MouseDown);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
#line 126 "..\..\..\MainWindow.xaml"
|
||||
this.StageHost.MouseMove += new System.Windows.Input.MouseEventHandler(this.Stage_MouseMove);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
|
||||
#line 127 "..\..\..\MainWindow.xaml"
|
||||
this.StageHost.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(this.Stage_MouseUp);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 14:
|
||||
this.Stage = ((System.Windows.Controls.Grid)(target));
|
||||
return;
|
||||
case 15:
|
||||
this.CmbBody = ((System.Windows.Controls.ComboBox)(target));
|
||||
|
||||
#line 143 "..\..\..\MainWindow.xaml"
|
||||
this.CmbBody.SelectionChanged += new System.Windows.Controls.SelectionChangedEventHandler(this.Body_Changed);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 16:
|
||||
this.CmbShape = ((System.Windows.Controls.ComboBox)(target));
|
||||
|
||||
#line 146 "..\..\..\MainWindow.xaml"
|
||||
this.CmbShape.SelectionChanged += new System.Windows.Controls.SelectionChangedEventHandler(this.Shape_Changed);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 17:
|
||||
this.CmbExpr = ((System.Windows.Controls.ComboBox)(target));
|
||||
|
||||
#line 149 "..\..\..\MainWindow.xaml"
|
||||
this.CmbExpr.SelectionChanged += new System.Windows.Controls.SelectionChangedEventHandler(this.Expr_Changed);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 18:
|
||||
this.SwatchPanel = ((System.Windows.Controls.WrapPanel)(target));
|
||||
return;
|
||||
case 19:
|
||||
this.AccPanel = ((System.Windows.Controls.StackPanel)(target));
|
||||
return;
|
||||
case 20:
|
||||
this.CmbLayer = ((System.Windows.Controls.ComboBox)(target));
|
||||
|
||||
#line 163 "..\..\..\MainWindow.xaml"
|
||||
this.CmbLayer.SelectionChanged += new System.Windows.Controls.SelectionChangedEventHandler(this.Layer_Changed);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 21:
|
||||
this.SldX = ((System.Windows.Controls.Slider)(target));
|
||||
|
||||
#line 175 "..\..\..\MainWindow.xaml"
|
||||
this.SldX.ValueChanged += new System.Windows.RoutedPropertyChangedEventHandler<double>(this.LayerXform_Changed);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 22:
|
||||
this.SldY = ((System.Windows.Controls.Slider)(target));
|
||||
|
||||
#line 177 "..\..\..\MainWindow.xaml"
|
||||
this.SldY.ValueChanged += new System.Windows.RoutedPropertyChangedEventHandler<double>(this.LayerXform_Changed);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 23:
|
||||
this.SldScale = ((System.Windows.Controls.Slider)(target));
|
||||
|
||||
#line 179 "..\..\..\MainWindow.xaml"
|
||||
this.SldScale.ValueChanged += new System.Windows.RoutedPropertyChangedEventHandler<double>(this.LayerXform_Changed);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 24:
|
||||
|
||||
#line 181 "..\..\..\MainWindow.xaml"
|
||||
((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.AutoAlign_Click);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
return;
|
||||
case 25:
|
||||
this.TxtStatus = ((System.Windows.Controls.TextBlock)(target));
|
||||
return;
|
||||
}
|
||||
this._contentLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"version": 3,
|
||||
"targets": {
|
||||
"net8.0-windows7.0": {}
|
||||
},
|
||||
"libraries": {},
|
||||
"projectFileDependencyGroups": {
|
||||
"net8.0-windows7.0": []
|
||||
},
|
||||
"packageFolders": {
|
||||
"C:\\Users\\eKeerar\\.nuget\\packages\\": {}
|
||||
},
|
||||
"project": {
|
||||
"version": "1.0.0",
|
||||
"restore": {
|
||||
"projectUniqueName": "D:\\Work_AI\\Dansori\\Characters_Build_Docs\\Character_Builder\\Character_Builder.csproj",
|
||||
"projectName": "Character_Builder",
|
||||
"projectPath": "D:\\Work_AI\\Dansori\\Characters_Build_Docs\\Character_Builder\\Character_Builder.csproj",
|
||||
"packagesPath": "C:\\Users\\eKeerar\\.nuget\\packages\\",
|
||||
"outputPath": "D:\\Work_AI\\Dansori\\Characters_Build_Docs\\Character_Builder\\obj\\",
|
||||
"projectStyle": "PackageReference",
|
||||
"configFilePaths": [
|
||||
"C:\\Users\\eKeerar\\AppData\\Roaming\\NuGet\\NuGet.Config"
|
||||
],
|
||||
"originalTargetFrameworks": [
|
||||
"net8.0-windows"
|
||||
],
|
||||
"sources": {
|
||||
"https://api.nuget.org/v3/index.json": {}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0-windows7.0": {
|
||||
"targetAlias": "net8.0-windows",
|
||||
"projectReferences": {}
|
||||
}
|
||||
},
|
||||
"warningProperties": {
|
||||
"warnAsError": [
|
||||
"NU1605"
|
||||
]
|
||||
},
|
||||
"restoreAuditProperties": {
|
||||
"enableAudit": "true",
|
||||
"auditLevel": "low",
|
||||
"auditMode": "direct"
|
||||
}
|
||||
},
|
||||
"frameworks": {
|
||||
"net8.0-windows7.0": {
|
||||
"targetAlias": "net8.0-windows",
|
||||
"imports": [
|
||||
"net461",
|
||||
"net462",
|
||||
"net47",
|
||||
"net471",
|
||||
"net472",
|
||||
"net48",
|
||||
"net481"
|
||||
],
|
||||
"assetTargetFallback": true,
|
||||
"warn": true,
|
||||
"frameworkReferences": {
|
||||
"Microsoft.NETCore.App": {
|
||||
"privateAssets": "all"
|
||||
},
|
||||
"Microsoft.WindowsDesktop.App.WPF": {
|
||||
"privateAssets": "none"
|
||||
}
|
||||
},
|
||||
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\8.0.422/PortableRuntimeIdentifierGraph.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"version": 2,
|
||||
"dgSpecHash": "zLTNiLqmafM=",
|
||||
"success": true,
|
||||
"projectFilePath": "D:\\Work_AI\\Dansori\\Characters_Build_Docs\\Character_Builder\\Character_Builder.csproj",
|
||||
"expectedPackageFiles": [],
|
||||
"logs": []
|
||||
}
|
||||
Reference in New Issue
Block a user