"Appearance Presets" file (*.vap) is JSON formatted.
target vap file example.
But it is not a strict format and cannot be parsed.
i.e. this not works.
Therefore, let's get a part of JSON that can be interpreted by low-level processing, using this code.
Usage
target vap file example.
JSON:
{
"setUnlistedParamsToDefault" : "true",
"storables" : [
{
"id" : "geometry",
"useAdvancedColliders" : "true",
"useAuxBreastColliders" : "true",
"disableAnatomy" : "false",
"useMaleMorphsOnFemale" : "false",
"useFemaleMorphsOnMale" : "false",
"character" : "Female Custom",
"clothing" : [
{
"id" : "...",
"internalId" : "...",
"enabled" : "true"
}, ...
],
"hair" : [
{
"id" : "...",
"internalId" : "...",
"enabled" : "true"
}, ...
],
"morphs" : [
{
"uid" : "...",
"name" : "...",
"value" : "0.5"
}, ...
],...
{
"id" : "rescaleObject",
"scale" : "0.95"
},
But it is not a strict format and cannot be parsed.
i.e. this not works.
C#:
string jsonStr = SuperController.singleton.ReadFileIntoString(@"Custom\Atom\Person\Appearance\Preset_presetname.vap"); // OK
JsonNode jn = JSON.Parse(jsonStr); // partially fails silently...
// jn["storables"]["morphs"] is null
Therefore, let's get a part of JSON that can be interpreted by low-level processing, using this code.
C#:
/// <summary>
/// Read values from ".vap" file.
/// Although vap is JSON, it is not a strict format and cannot be parsed, so it is converted to JSON that can be interpreted by low-level processing and then parsed.
/// </summary>
/// <param name="filepath"></param>
/// <param name="tag">lowest array key. ex. hair, morphs, ...</param>
/// <returns></returns>
public static JSONNode parseVap(string filepath, string tag)
{
string json = "";
try { json = SuperController.singleton.ReadFileIntoString(filepath); }
catch(Exception e) { dUtil.error($"Error while reading`{filepath}`.\n{e.Message}"); } // not work b/c error squeeze&clashed.
if(json == "") return new JSONNode();
int start = json.IndexOf(tag) - 1;
int end = json.IndexOf("]\n", start);
json = json.Substring(start, end - start + 1);
var ret = JSON.Parse(json);
if (ret == null)
{
string mes = $"'${tag}' not found in '${filepath}'.";
dUtil.error(mes);
throw new Exception(mes);
}
return ret;
}
/// <summary>
/// Read values from ".vap" file.
/// Although vap is JSON, it is not a strict format and cannot be parsed, so it is converted to JSON that can be interpreted by low-level processing and then parsed.
/// </summary>
/// <param name="filepath"></param>
/// <param name="tag">lowest dictionary id. ex. rescaleObject, ...</param>
/// <returns></returns>
public static JSONNode parseVapDictionary(string filepath, string tag)
{
var json = SuperController.singleton.ReadFileIntoString(filepath);
int start = json.IndexOf($"\"id\" : \"{tag}") - 1;
int end = json.IndexOf("}", start);
json = "{" + json.Substring(start, end - start + 1);
var ret = JSON.Parse(json);
if (ret == null)
{
string mes = $"'${tag}' not found in '${filepath}'.";
dUtil.error(mes);
throw new Exception(mes);
}
return ret;
}
Usage
C#:
appearancePath = @"Custom\Atom\Person\Appearance\Preset_presetname.vap";
// morph
var morphs = new Dictionary<string, float>(); // k,v : name,value
foreach (var j in CharacterFusioner.parseVap(appearancePath, "morphs").Childs)
morphs[j["name"]] = j["value"].AsFloat;
// rescaleObject
float scale = CharacterFusioner.parseVapDictionary(appearancePath, "rescaleObject")["scale"].AsFloat;