JSON persistence for the Advanced Mapping Tools editor package. Stores per-tool presets and per-object builder records (type, plane height, origin, points, settings) under ProjectSettings/AdvancedMappingTools, and supports exporting and importing preset files.
using System.IO;
using System.Text.Json;
namespace AdvancedMappingTools;
/// <summary>
/// What a builder needs to pick a finished object up again.
/// </summary>
public sealed class BuilderRecord
{
public string Type { get; set; }
public bool Closed { get; set; }
public float PlaneHeight { get; set; }
public float[] Origin { get; set; }
public List<float[]> Points { get; set; } = [];
public Dictionary<string, string> Settings { get; set; } = [];
}
/// <summary>
/// Keeps presets and the records of built objects as JSON in the project settings.
/// </summary>
public static class BuilderStore
{
static string Root => Path.Combine( Project.Current.GetRootPath(), "ProjectSettings", "AdvancedMappingTools" );
static string PresetFile( string type ) => Path.Combine( Root, "presets", $"{type}.json" );
static string RecordFile( Guid id ) => Path.Combine( Root, "objects", $"{id}.json" );
public static Dictionary<string, Dictionary<string, string>> LoadPresets( string type )
{
var path = PresetFile( type );
if ( !File.Exists( path ) )
return[];
var loaded = StoreSafety.Read<Dictionary<string, Dictionary<string, string>>>( path ) ?? [];
if ( loaded.Values.Any( v => v is null ) )
{
StoreSafety.Warn( path, "A preset contains no settings" );
return loaded.Where( p => p.Value is not null ).ToDictionary( p => p.Key, p => p.Value );
}
return loaded;
}
public static void SavePresets( string type, Dictionary<string, Dictionary<string, string>> presets )
{
var path = PresetFile( type );
Directory.CreateDirectory( Path.GetDirectoryName( path ) );
StoreSafety.Write( path, JsonSerializer.Serialize( presets, new JsonSerializerOptions { WriteIndented = true } ) );
}
/// <summary>
/// A file with the presets of one builder, to hand to someone else or keep elsewhere.
/// </summary>
public sealed class PresetFileContent
{
public string Tool { get; set; }
public Dictionary<string, Dictionary<string, string>> Presets { get; set; } = [];
}
/// <summary>
/// Write the presets of a builder to a file. Without any saved preset, the current settings
/// go in as one called "Current", so an export is never empty.
/// </summary>
public static int ExportPresets( string type, string path, Dictionary<string, string> current )
{
var presets = LoadPresets( type );
if ( presets.Count == 0 ) presets["Current"] = current;
File.WriteAllText( path, JsonSerializer.Serialize( new PresetFileContent { Tool = type, Presets = presets }, new JsonSerializerOptions { WriteIndented = true } ) );
return presets.Count;
}
/// <summary>
/// Add the presets from a file to the ones of a builder. A name that exists already gets a
/// number. Returns how many were added, or the reason nothing was.
/// </summary>
public static (int Added, string Problem) ImportPresets( string type, string path )
{
PresetFileContent content;
try
{
content = JsonSerializer.Deserialize<PresetFileContent>( File.ReadAllText( path ) );
}
catch ( Exception e )
{
return (0, $"The file could not be read: {e.Message}");
}
if ( content?.Presets is null || content.Presets.Count == 0 )
return (0, "The file holds no presets.");
if ( content.Tool != type )
return (0, $"The presets are for {content.Tool}, not for this tool.");
var presets = LoadPresets( type );
foreach ( var(name, settings)in content.Presets )
{
var unique = name;
for ( var i = 2; presets.ContainsKey( unique ); i++ )
unique = $"{name} ({i})";
if ( settings is null )
return (0, "A preset contains no settings.");
presets[unique] = settings;
}
SavePresets( type, presets );
return (content.Presets.Count, null);
}
public static void SaveRecord( Guid id, BuilderRecord record )
{
var path = RecordFile( id );
Directory.CreateDirectory( Path.GetDirectoryName( path ) );
StoreSafety.Write( path, JsonSerializer.Serialize( record ) );
LineSnap.Forget( id );
}
public static BuilderRecord LoadRecord( Guid id )
{
var path = RecordFile( id );
var record = StoreSafety.Read<BuilderRecord>( path );
if ( record is null )
return null;
if ( string.IsNullOrWhiteSpace( record.Type ) || record.Origin is null || record.Origin.Length < 3 || record.Origin.Take( 3 ).Any( v => !float.IsFinite( v ) ) || record.Points is null || record.Points.Count == 0 || record.Points.Any( p => p is null || p.Length < 3 || p.Take( 3 ).Any( v => !float.IsFinite( v ) ) ) )
{
StoreSafety.Warn( path, "Incomplete object coordinates" );
return null;
}
record.Settings ??= [];
return record;
}
public static void DeleteRecord( Guid id )
{
var path = RecordFile( id );
if ( File.Exists( path ) ) File.Delete( path );
}
}