684 lines
24 KiB
C#
684 lines
24 KiB
C#
using _2DGAMELIB;
|
|
using SlaveMatrix.Properties;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
namespace SlaveMatrix.Extract;
|
|
|
|
class Program
|
|
{
|
|
static readonly string ProjectRoot = Environment.CurrentDirectory;
|
|
|
|
static string AssetsDir = Path.Combine(ProjectRoot, "SlaveMatrix", "Assets");
|
|
static string OutputDir;
|
|
|
|
// Complete translation map for extraction output.
|
|
// Separate from the runtime KeyMap in ObjExtensions — the runtime map only
|
|
// contains entries that match the old game code's string lookups.
|
|
static readonly Dictionary<string, string> EnglishNameMap = new()
|
|
{
|
|
["咳"] = "Cough",
|
|
["腰"] = "Waist",
|
|
["胴"] = "Torso",
|
|
["首"] = "Neck",
|
|
["頭"] = "Head",
|
|
["後髪0"] = "BackHair0",
|
|
["後髪1"] = "BackHair1",
|
|
["横髪"] = "SideHair",
|
|
["脚"] = "Leg",
|
|
["腕"] = "Arm",
|
|
["肩"] = "Shoulder",
|
|
["胸"] = "Chest",
|
|
["下腕"] = "LowerArm",
|
|
["上腕"] = "UpperArm",
|
|
["鳥翼上腕"] = "鳥翼UpperArm",
|
|
["獣翼上腕"] = "獣翼UpperArm",
|
|
["四足上腕"] = "四足UpperArm",
|
|
["鳥翼下腕"] = "鳥翼LowerArm",
|
|
["獣翼下腕"] = "獣翼LowerArm",
|
|
["四足下腕"] = "四足LowerArm",
|
|
["基髪"] = "BaseHair",
|
|
["胸毛"] = "ChestHair",
|
|
["前髪"] = "FrontHair",
|
|
["眉左"] = "EyebrowLeft",
|
|
["額目"] = "ForeheadEye",
|
|
["額瞼"] = "ForeheadEyelid",
|
|
["顔ハイライト左"] = "FaceHighlightLeft",
|
|
["顔面"] = "Face",
|
|
["魔性中瞼左"] = "DemonicMidEyelidLeft",
|
|
["魔性強瞼左"] = "DemonicStrongEyelidLeft",
|
|
["魔性弱瞼左"] = "DemonicWeakEyelidLeft",
|
|
["単眼目"] = "MonoEye",
|
|
["単眼眉"] = "MonoEyebrow",
|
|
["単眼瞼"] = "MonoEyelid",
|
|
["単足"] = "MonoLeg",
|
|
["獣性瞼左"] = "BestialEyelidLeft",
|
|
["目傷左"] = "EyeScarLeft",
|
|
["目尻影左"] = "EyeCornerShadowLeft",
|
|
["目左"] = "EyeLeft",
|
|
["目隠帯"] = "Blindfold",
|
|
["頬目左"] = "CheekEyeLeft",
|
|
["頬瞼左"] = "CheekEyelidLeft",
|
|
["頬肌左"] = "CheekSkinLeft",
|
|
["口"] = "Mouth",
|
|
["舌"] = "Tongue",
|
|
["涎左"] = "DroolLeft",
|
|
["涎口裂け左"] = "DroolMouthGashLeft",
|
|
["呼気"] = "Breath",
|
|
["吹出し"] = "SpeechBubble",
|
|
["鼻"] = "Nose",
|
|
["鼻水左"] = "NoseDripLeft",
|
|
["鼻肌"] = "NoseSkin",
|
|
["耳"] = "Ear",
|
|
["獣耳"] = "BeastEar",
|
|
["角"] = "Horn",
|
|
["胸郭"] = "Chest",
|
|
["胸郭肌"] = "ChestSkin",
|
|
["胸郭腹板"] = "ChestPlate",
|
|
["胸左"] = "LeftBreast",
|
|
["胴肌"] = "TorsoSkin",
|
|
["胴腹板"] = "TorsoPlate",
|
|
["腰肌"] = "WaistSkin",
|
|
["ボテ腹"] = "PregnantBelly",
|
|
["ボテ腹板"] = "PregnantBellyPlate",
|
|
["固定帯"] = "FixingBelt",
|
|
["紅潮"] = "Blush",
|
|
["肛門"] = "Anus",
|
|
["肛門精液垂れ"] = "AnusSemenDrip",
|
|
["膣基"] = "VaginaBase",
|
|
["膣内精液"] = "InternalSemen",
|
|
["性器"] = "Genitals",
|
|
["性器精液垂れ"] = "GenitalsSemenDrip",
|
|
["射精"] = "Ejaculation",
|
|
["放尿"] = "Urination",
|
|
["噴乳左"] = "LactationLeft",
|
|
["潮吹"] = "Squirting",
|
|
["尾"] = "Tail",
|
|
["背中"] = "Back",
|
|
["手"] = "Hand",
|
|
["足"] = "Foot",
|
|
["腿"] = "Thigh",
|
|
["前翅"] = "Forewing",
|
|
["後翅"] = "Hindwing",
|
|
["触手"] = "Tentacle",
|
|
["触覚"] = "Antenna",
|
|
["節足"] = "SegmentLeg",
|
|
["虫鎌"] = "InsectScythe",
|
|
["虫顎"] = "InsectJaw",
|
|
["植"] = "Plant",
|
|
["葉"] = "Leaf",
|
|
["羽根箒"] = "FeatherDuster",
|
|
["飛沫"] = "Splash",
|
|
["飛膜先"] = "MembraneTip",
|
|
["飛膜根"] = "MembraneBase",
|
|
["上着トップ"] = "JacketTop",
|
|
["上着ミドル"] = "JacketMid",
|
|
["上着ボトム前"] = "JacketBottomFront",
|
|
["上着ボトム後"] = "JacketBottomBack",
|
|
["下着トップ"] = "UnderwearTop",
|
|
["下着ボトム"] = "UnderwearBottom",
|
|
["下着乳首左"] = "UnderwearNippleLeft",
|
|
["下着陰核"] = "UnderwearClitoris",
|
|
["帽子"] = "Hat",
|
|
["パンスト"] = "Pantyhose",
|
|
["キャップ左"] = "CapLeft",
|
|
["キャップ中"] = "CapMid",
|
|
["玉口枷"] = "BallGag",
|
|
["留具前"] = "FastenerFront",
|
|
["留具後"] = "FastenerBack",
|
|
["拘束具上"] = "RestraintUpper",
|
|
["拘束具下"] = "RestraintLower",
|
|
["拘束鎖"] = "RestraintChain",
|
|
["押し付け"] = "Pressing",
|
|
["コモン"] = "CommonCursor",
|
|
["ハンド"] = "HandCursor",
|
|
["ペニス"] = "PenisCursor",
|
|
["アナル"] = "AnalCursor",
|
|
["マウス"] = "MouthCursor",
|
|
["ディル"] = "DildoCursor",
|
|
["デンマ"] = "VibratorCursor",
|
|
["ドリル"] = "DrillCursor",
|
|
["パール"] = "PearlCursor",
|
|
["ロータ"] = "RotoCursor",
|
|
["T字剃刀"] = "TRazorCursor",
|
|
["ぶっかけ"] = "Bukkake",
|
|
["キスマーク"] = "KissMark",
|
|
["汗"] = "Sweat",
|
|
["汚れ"] = "Stain",
|
|
["流血中"] = "BleedingMid",
|
|
["流血大"] = "BleedingLarge",
|
|
["流血小"] = "BleedingSmall",
|
|
["鞭痕"] = "WhipMark",
|
|
["横髪左"] = "SideHairLeft",
|
|
["涙左"] = "TearLeft",
|
|
["青筋左"] = "VeinLeft",
|
|
["頭部前"] = "Forehead",
|
|
["頭部後"] = "BackOfHead",
|
|
["鰭"] = "Fin",
|
|
["獣翼手"] = "BeastWingHand",
|
|
["鳥翼手"] = "BirdWingHand",
|
|
["エイリアン目左"] = "AlienEyeLeft",
|
|
["ピアス"] = "Piercing",
|
|
["反応"] = "Reaction",
|
|
["多足"] = "MultiLeg",
|
|
["大顎上"] = "MandibleUpper",
|
|
["大顎基"] = "MandibleBase",
|
|
["意思表示"] = "Expression",
|
|
["断面"] = "CrossSection",
|
|
["染み"] = "Spot",
|
|
["長物"] = "Phallus",
|
|
["調教鞭"] = "Whip",
|
|
["衝撃"] = "Impact",
|
|
["鎖"] = "Chain",
|
|
["四足ボテ腹"] = "QuadrupedPregnantBelly",
|
|
["四足固定帯"] = "QuadrupedFixingBelt",
|
|
["四足性器"] = "QuadrupedGenitals",
|
|
["四足性器精液垂れ"] = "QuadrupedGenitalsSemenDrip",
|
|
["四足手"] = "QuadrupedHand",
|
|
["四足放尿"] = "QuadrupedUrination",
|
|
["四足断面"] = "QuadrupedCrossSection",
|
|
["四足染み"] = "QuadrupedSpot",
|
|
["四足潮吹"] = "QuadrupedSquirting",
|
|
["四足留具前"] = "QuadrupedFastenerFront",
|
|
["四足留具後"] = "QuadrupedFastenerBack",
|
|
["四足肛門"] = "QuadrupedAnus",
|
|
["四足肛門精液垂れ"] = "QuadrupedAnusSemenDrip",
|
|
["四足胴"] = "QuadrupedTorso",
|
|
["四足胸郭"] = "QuadrupedChest",
|
|
["四足脇"] = "QuadrupedSide",
|
|
["四足脚"] = "QuadrupedLeg",
|
|
["四足腰"] = "QuadrupedWaist",
|
|
["四足腿"] = "QuadrupedThigh",
|
|
["四足膣内精液"] = "QuadrupedInternalSemen",
|
|
["四足膣基"] = "QuadrupedVaginaBase",
|
|
["四足足"] = "QuadrupedFoot",
|
|
["四足飛沫"] = "QuadrupedSplash",
|
|
};
|
|
|
|
static void Main(string[] args)
|
|
{
|
|
Console.WriteLine("SlaveMatrix Asset Extractor");
|
|
Console.WriteLine("=================================\n");
|
|
|
|
OutputDir = args.Length > 0 && args[0] == "--output" && args.Length > 1
|
|
? Path.GetFullPath(args[1])
|
|
: Path.GetFullPath(AssetsDir);
|
|
|
|
var partsDir = Path.Combine(OutputDir, "Parts");
|
|
var jsonDir = Path.Combine(OutputDir, "..", "..", "extracted");
|
|
Directory.CreateDirectory(partsDir);
|
|
Directory.CreateDirectory(jsonDir);
|
|
|
|
var resources = new (string Name, byte[] Data)[]
|
|
{
|
|
("胴体", Resources.胴体),
|
|
("肩左", Resources.肩左),
|
|
("腕左", Resources.腕左),
|
|
("脚左", Resources.脚左),
|
|
("尻尾", Resources.尻尾),
|
|
("半身", Resources.半身),
|
|
("肢左", Resources.肢左),
|
|
("肢中", Resources.肢中),
|
|
("性器", Resources.性器),
|
|
("性器付", Resources.性器付),
|
|
("スタンプ", Resources.スタンプ),
|
|
("カーソル", Resources.カーソル),
|
|
("その他", Resources.その他),
|
|
};
|
|
|
|
var catalog = new JObject();
|
|
catalog["parts"] = new JArray();
|
|
var catalogParts = (JArray)catalog["parts"];
|
|
|
|
foreach (var (name, data) in resources)
|
|
{
|
|
Console.Write($"Loading {name}... ");
|
|
Obj? obj;
|
|
try
|
|
{
|
|
obj = data.ObjLoadRaw();
|
|
if (obj == null) { Console.WriteLine("FAILED: null"); continue; }
|
|
Console.WriteLine($"OK ({obj.Difss.Count} keys)");
|
|
}
|
|
catch (Exception ex) { Console.WriteLine($"FAILED: {ex.Message}"); continue; }
|
|
|
|
Console.Write(" Applying MigrateKeys... ");
|
|
// Capture original keys before migration for original_key tracking
|
|
var origKeys = obj.Difss.Keys.Cast<string>().ToList();
|
|
obj.MigrateKeys();
|
|
Console.WriteLine("OK");
|
|
|
|
var jsonObj = ExportObj(obj);
|
|
var jsonPath = Path.Combine(jsonDir, $"{name}.json");
|
|
File.WriteAllText(jsonPath, JsonConvert.SerializeObject(jsonObj, Formatting.Indented));
|
|
|
|
var keys = obj.Difss.Keys.Cast<string>().ToList();
|
|
|
|
foreach (var key in keys)
|
|
{
|
|
// Use extraction EnglishNameMap for directory naming
|
|
var engKey = EnglishNameMap.TryGetValue(key, out var mapped) ? mapped : key;
|
|
var difs = obj.Difss[key];
|
|
var partDir = Path.Combine(partsDir, SanitizeName(engKey));
|
|
Directory.CreateDirectory(partDir);
|
|
|
|
var foundJoints = new List<string>();
|
|
// original_key: the pre-migration Japanese key
|
|
var originalKey = origKeys.Contains(key) ? key : origKeys.FirstOrDefault(ok => EnglishNameMap.GetValueOrDefault(ok, ok) == engKey) ?? key;
|
|
|
|
var partEntry = new JObject
|
|
{
|
|
["id"] = engKey,
|
|
["original_key"] = originalKey,
|
|
["resource"] = name,
|
|
["morph_x"] = difs.CountX,
|
|
["morph_y"] = difs.CountY,
|
|
["variants"] = new JArray(),
|
|
["fields"] = new JArray()
|
|
};
|
|
|
|
var variants = (JArray)partEntry["variants"];
|
|
|
|
for (int x = 0; x < difs.CountX; x++)
|
|
{
|
|
var dif = difs[x];
|
|
for (int y = 0; y < dif.Count; y++)
|
|
{
|
|
var pars = dif[y];
|
|
var svgName = $"x{x}y{y}.svg";
|
|
var svgContent = ExportParsToSvg(pars, foundJoints);
|
|
|
|
variants.Add(new JObject
|
|
{
|
|
["x"] = x,
|
|
["y"] = y,
|
|
["file"] = svgName
|
|
});
|
|
|
|
File.WriteAllText(Path.Combine(partDir, svgName), svgContent);
|
|
}
|
|
}
|
|
|
|
foreach (string childKey in difs[0][0].Keys)
|
|
{
|
|
((JArray)partEntry["fields"]).Add(new JObject { ["name"] = childKey });
|
|
}
|
|
|
|
if (foundJoints.Count > 0)
|
|
{
|
|
var joints = new JArray();
|
|
foreach (var j in foundJoints.Distinct())
|
|
{
|
|
var parts = j.Split(',');
|
|
joints.Add(new JObject
|
|
{
|
|
["position"] = new JArray
|
|
{
|
|
double.Parse(parts[0], CultureInfo.InvariantCulture),
|
|
double.Parse(parts[1], CultureInfo.InvariantCulture)
|
|
}
|
|
});
|
|
}
|
|
partEntry["joints"] = joints;
|
|
}
|
|
|
|
var yaml = ToYaml((JObject)partEntry);
|
|
File.WriteAllText(Path.Combine(partDir, "part.yaml"), yaml);
|
|
|
|
catalogParts.Add(new JObject
|
|
{
|
|
["id"] = engKey,
|
|
["path"] = $"Parts/{SanitizeName(engKey)}/"
|
|
});
|
|
}
|
|
|
|
Console.WriteLine($" -> {keys.Count} parts exported");
|
|
}
|
|
|
|
var catalogYaml = ToYaml(catalog);
|
|
var catalogPath = Path.Combine(OutputDir, "Catalog.yaml");
|
|
File.WriteAllText(catalogPath, catalogYaml);
|
|
Console.WriteLine($"\nCatalog: {catalogPath}");
|
|
Console.WriteLine($"Total: {((JArray)catalog["parts"]).Count} parts\n");
|
|
}
|
|
|
|
static string ToYaml(JObject obj, int indent = 0)
|
|
{
|
|
var prefix = new string(' ', indent);
|
|
var sb = new StringBuilder();
|
|
foreach (var prop in obj.Properties())
|
|
{
|
|
if (prop.Value is JObject nested)
|
|
{
|
|
sb.AppendLine($"{prefix}{prop.Name}:");
|
|
sb.Append(ToYaml(nested, indent + 2));
|
|
}
|
|
else if (prop.Value is JArray arr)
|
|
{
|
|
if (arr.Count == 0)
|
|
{
|
|
sb.AppendLine($"{prefix}{prop.Name}: []");
|
|
continue;
|
|
}
|
|
|
|
if (arr[0] is JObject)
|
|
{
|
|
sb.AppendLine($"{prefix}{prop.Name}:");
|
|
foreach (var item in arr)
|
|
{
|
|
var objItem = (JObject)item;
|
|
sb.Append($"{prefix}- ");
|
|
var first = true;
|
|
foreach (var p in objItem.Properties())
|
|
{
|
|
if (first)
|
|
{
|
|
sb.AppendLine($"{p.Name}: {FormatYamlValue(p.Value)}");
|
|
first = false;
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"{prefix} {p.Name}: {FormatYamlValue(p.Value)}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"{prefix}{prop.Name}:");
|
|
foreach (var item in arr)
|
|
sb.AppendLine($"{prefix}- {FormatYamlValue(item)}");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb.AppendLine($"{prefix}{prop.Name}: {FormatYamlValue(prop.Value)}");
|
|
}
|
|
}
|
|
return sb.ToString();
|
|
}
|
|
|
|
static string FormatYamlValue(JToken token)
|
|
{
|
|
if (token.Type == JTokenType.String)
|
|
{
|
|
var s = token.Value<string>();
|
|
if (s.Contains(':') || s.Contains('#') || s.Contains('\n'))
|
|
return $"\"{s}\"";
|
|
return s;
|
|
}
|
|
if (token.Type == JTokenType.Float)
|
|
return ((double)token).ToString("G", CultureInfo.InvariantCulture);
|
|
if (token.Type == JTokenType.Integer)
|
|
return ((long)token).ToString(CultureInfo.InvariantCulture);
|
|
return token.ToString();
|
|
}
|
|
|
|
static string ExportParsToSvg(PartGroup PartGroup, List<string> foundJoints)
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
|
|
sb.AppendLine("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1 1\">");
|
|
ExportParsToSvgInner(PartGroup, sb, foundJoints);
|
|
sb.AppendLine("</svg>");
|
|
return sb.ToString();
|
|
}
|
|
|
|
static void ExportParsToSvgInner(PartGroup PartGroup, StringBuilder sb, List<string> foundJoints)
|
|
{
|
|
foreach (string key in PartGroup.Keys)
|
|
{
|
|
var val = PartGroup[key];
|
|
if (val is PartGroup childPars)
|
|
{
|
|
sb.AppendLine($"<g id=\"{EscapeXml(key)}\">");
|
|
ExportParsToSvgInner(childPars, sb, foundJoints);
|
|
sb.AppendLine("</g>");
|
|
}
|
|
else if (val is ShapePart par)
|
|
{
|
|
ExportParToSvg(par, key, sb, foundJoints);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ExportParToSvg(ShapePart ShapePart, string name, StringBuilder sb, List<string> foundJoints)
|
|
{
|
|
if (!ShapePart.Dra) return;
|
|
|
|
var bx = ShapePart.BasePoint.X;
|
|
var by = ShapePart.BasePoint.Y;
|
|
var px = ShapePart.Position.X;
|
|
var py = ShapePart.Position.Y;
|
|
var angle = ShapePart.Angle;
|
|
var sx = ShapePart.Size * ShapePart.SizeX;
|
|
var sy = ShapePart.Size * ShapePart.SizeY;
|
|
|
|
var hasTransform = System.Math.Abs(bx) > 0.001 || System.Math.Abs(by) > 0.001
|
|
|| System.Math.Abs(px) > 0.001 || System.Math.Abs(py) > 0.001
|
|
|| System.Math.Abs(angle) > 0.001 || System.Math.Abs(sx - 1) > 0.001 || System.Math.Abs(sy - 1) > 0.001;
|
|
|
|
if (hasTransform)
|
|
sb.Append($"<g transform=\"translate({F(px)},{F(py)}) rotate({F(angle)}) scale({F(sx)},{F(sy)}) translate({F(-bx)},{F(-by)})\">");
|
|
|
|
foreach (var outObj in ShapePart.OP)
|
|
{
|
|
var points = outObj.ps;
|
|
if (points.Count < 2) continue;
|
|
|
|
var d = BuildSvgPath(points, outObj.Tension, ShapePart.Closed);
|
|
var fill = ShapePart.Closed ? "#cccccc" : "none";
|
|
var stroke = outObj.Outline ? "#000000" : "none";
|
|
var sw_val = outObj.Outline ? System.Math.Max(ShapePart.PenWidth, 0.001) : 0.0;
|
|
sb.AppendLine($"<path d=\"{d}\" fill=\"{fill}\" stroke=\"{stroke}\" stroke-width=\"{F(sw_val)}\"/>");
|
|
}
|
|
|
|
if (hasTransform)
|
|
sb.Append("</g>");
|
|
|
|
foreach (var joi in ShapePart.JP)
|
|
{
|
|
foundJoints.Add($"{F(joi.Joint.X)},{F(joi.Joint.Y)}");
|
|
}
|
|
}
|
|
|
|
static string BuildSvgPath(List<Vector2D> points, float tension, bool closed)
|
|
{
|
|
int n = points.Count;
|
|
if (n < 2) return "";
|
|
|
|
var sb = new StringBuilder();
|
|
sb.Append($"M {F(points[0].X)} {F(points[0].Y)}");
|
|
|
|
if (n == 2)
|
|
{
|
|
sb.Append($" L {F(points[1].X)} {F(points[1].Y)}");
|
|
if (closed) sb.Append(" Z");
|
|
return sb.ToString();
|
|
}
|
|
|
|
double tensionFactor = 1.0 - tension;
|
|
if (tensionFactor < 0.0) tensionFactor = 0.0;
|
|
int segmentCount = closed ? n : n - 1;
|
|
|
|
for (int i = 0; i < segmentCount; i++)
|
|
{
|
|
int p0, p1, p2, p3;
|
|
|
|
if (closed)
|
|
{
|
|
p0 = (i - 1 + n) % n;
|
|
p1 = i;
|
|
p2 = (i + 1) % n;
|
|
p3 = (i + 2) % n;
|
|
}
|
|
else
|
|
{
|
|
p0 = System.Math.Max(i - 1, 0);
|
|
p1 = i;
|
|
p2 = i + 1;
|
|
p3 = System.Math.Min(i + 2, n - 1);
|
|
}
|
|
|
|
var ci1x = tensionFactor * 0.5 * (points[p2].X - points[p0].X);
|
|
var ci1y = tensionFactor * 0.5 * (points[p2].Y - points[p0].Y);
|
|
var ci2x = tensionFactor * 0.5 * (points[p3].X - points[p1].X);
|
|
var ci2y = tensionFactor * 0.5 * (points[p3].Y - points[p1].Y);
|
|
|
|
var c1x = F(points[p1].X + ci1x / 3);
|
|
var c1y = F(points[p1].Y + ci1y / 3);
|
|
var c2x = F(points[p2].X + ci2x / 3);
|
|
var c2y = F(points[p2].Y + ci2y / 3);
|
|
var ex = F(points[p2].X);
|
|
var ey = F(points[p2].Y);
|
|
|
|
sb.Append($" C {c1x} {c1y} {c2x} {c2y} {ex} {ey}");
|
|
}
|
|
|
|
if (closed) sb.Append(" Z");
|
|
return sb.ToString();
|
|
}
|
|
|
|
static string F(double v) => v.ToString("G", CultureInfo.InvariantCulture);
|
|
|
|
static string EscapeXml(string s) => s.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace("\"", """);
|
|
|
|
static string SanitizeName(string name)
|
|
{
|
|
var invalid = Path.GetInvalidFileNameChars();
|
|
var result = name;
|
|
foreach (var c in invalid)
|
|
result = result.Replace(c, '_');
|
|
return result;
|
|
}
|
|
|
|
static JObject ExportObj(Obj obj)
|
|
{
|
|
var keys = obj.Difss.Keys.Cast<string>().ToList();
|
|
var result = new JObject
|
|
{
|
|
["Tag"] = obj.Tag ?? "",
|
|
["KeyCount"] = keys.Count,
|
|
["Keys"] = JArray.FromObject(keys),
|
|
["Difss"] = new JObject()
|
|
};
|
|
|
|
foreach (var key in keys)
|
|
{
|
|
result["Difss"][key] = ExportDifs(obj.Difss[key]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static JObject ExportDifs(Difs difs)
|
|
{
|
|
int xCount = difs.CountX;
|
|
int yCount = difs.CountY;
|
|
|
|
var result = new JObject
|
|
{
|
|
["Tag"] = difs.Tag ?? "",
|
|
["ValueX"] = difs.ValueX,
|
|
["ValueY"] = difs.ValueY,
|
|
["CountX"] = xCount,
|
|
["CountY"] = yCount,
|
|
["Difs"] = new JArray()
|
|
};
|
|
|
|
for (int x = 0; x < xCount; x++)
|
|
{
|
|
var dif = difs[x];
|
|
var difArr = new JArray();
|
|
|
|
for (int y = 0; y < dif.Count; y++)
|
|
{
|
|
difArr.Add(ExportPars(dif[y]));
|
|
}
|
|
|
|
((JArray)result["Difs"]).Add(difArr);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static JObject ExportPars(PartGroup PartGroup)
|
|
{
|
|
var result = new JObject
|
|
{
|
|
["Tag"] = PartGroup.Tag ?? "",
|
|
["Children"] = new JObject()
|
|
};
|
|
|
|
foreach (string key in PartGroup.Keys)
|
|
{
|
|
var val = PartGroup[key];
|
|
if (val is PartGroup childPars)
|
|
result["Children"][key] = ExportPars(childPars);
|
|
else if (val is ShapePartT parT)
|
|
result["Children"][key] = ExportParT(parT);
|
|
else if (val is ShapePart par)
|
|
result["Children"][key] = ExportPar(par);
|
|
else
|
|
result["Children"][key] = val?.ToString() ?? "null";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static JObject ExportPar(ShapePart ShapePart)
|
|
{
|
|
var result = new JObject
|
|
{
|
|
["Tag"] = ShapePart.Tag ?? "",
|
|
["Dra"] = ShapePart.Dra,
|
|
["PenWidth"] = ShapePart.PenWidth,
|
|
["Closed"] = ShapePart.Closed,
|
|
["BasePoint"] = ExportVec(ShapePart.BasePoint),
|
|
["Position"] = ExportVec(ShapePart.Position),
|
|
["Angle"] = ShapePart.Angle,
|
|
["Size"] = ShapePart.Size,
|
|
["SizeX"] = ShapePart.SizeX,
|
|
["SizeY"] = ShapePart.SizeY,
|
|
["Out"] = new JArray()
|
|
};
|
|
|
|
foreach (var outObj in ShapePart.OP)
|
|
{
|
|
var outJ = new JObject
|
|
{
|
|
["Tension"] = outObj.Tension,
|
|
["Outline"] = outObj.Outline,
|
|
["Points"] = new JArray()
|
|
};
|
|
foreach (var pt in outObj.ps)
|
|
((JArray)outJ["Points"]).Add(ExportVec(pt));
|
|
((JArray)result["Out"]).Add(outJ);
|
|
}
|
|
|
|
if (ShapePart.JP.Count > 0)
|
|
{
|
|
result["Joints"] = new JArray();
|
|
foreach (var joi in ShapePart.JP)
|
|
((JArray)result["Joints"]).Add(ExportVec(joi.Joint));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static JObject ExportParT(ShapePartT ShapePartT)
|
|
{
|
|
var result = ExportPar(ShapePartT);
|
|
result["Text"] = ShapePartT.Text ?? "";
|
|
result["FontSize"] = ShapePartT.FontSize;
|
|
return result;
|
|
}
|
|
|
|
static JObject ExportVec(Vector2D v) => new JObject { ["X"] = v.X, ["Y"] = v.Y };
|
|
} |