using System; using System.IO; using BYTES = System.ArraySegment; using MODEL = SharpGLTF.Schema2.ModelRoot; using VALIDATIONMODE = SharpGLTF.Validation.ValidationMode; namespace SharpGLTF.Schema2 { /// /// Determines how resources are written. /// public enum ResourceWriteMode { /// /// Use the most appropiate mode. /// Default, /// /// Resources will be stored as external satellite files. /// SatelliteFile, /// /// Resources will be embedded into the JSON encoded in MIME64. /// Embedded, /// /// Resources will be stored as internal binary buffers. Valid only for /// BufferView } /// /// Write settings and base class of /// public class WriteSettings { #region lifecycle public static implicit operator WriteSettings(VALIDATIONMODE vmode) { return new WriteSettings { Validation = vmode }; } public WriteSettings() { } public WriteSettings(WriteSettings other) { Guard.NotNull(other, nameof(other)); other.CopyTo(this); } #endregion #region data private System.Text.Json.JsonWriterOptions _JsonOptions = default; #endregion #region properties /// /// Gets or sets a value indicating how to write the images of the model. /// public ResourceWriteMode ImageWriting { get; set; } = ResourceWriteMode.Default; /// /// Gets or sets a callback hook that controls the image writing behavior. /// public ImageWriterCallback ImageWriteCallback { get; set; } /// /// Gets or sets a value indicating whether to merge all the buffers in into a single buffer. /// public Boolean MergeBuffers { get; set; } = true; /// /// Gets or sets the size used to split all the resources into individual buffers. /// /// /// It only has an effect when these conditions are met: /// /// must be true. /// Output format must be glTF, not GLB /// /// public int BuffersMaxSize { get; set; } = int.MaxValue; /// /// Gets or sets a value indicating whether the JSON formatting will include indentation. /// public Boolean JsonIndented { get => _JsonOptions.Indented; set => _JsonOptions.Indented = value; } /// /// Gets or sets a value indicating the Json options to be used for writing. /// public System.Text.Json.JsonWriterOptions JsonOptions { get => _JsonOptions; set => _JsonOptions = value; } /// /// Gets or sets a value indicating the level of validation applied when loading a file. /// public VALIDATIONMODE Validation { get; set; } = VALIDATIONMODE.Strict; /// /// Gets or sets the callback used to postprocess the json text before parsing it. /// public JsonFilterCallback JsonPostprocessor { get; set; } #endregion #region API public void CopyTo(WriteSettings other) { Guard.NotNull(other, nameof(other)); other.ImageWriting = this.ImageWriting; other.ImageWriteCallback = this.ImageWriteCallback; other.MergeBuffers = this.MergeBuffers; other.BuffersMaxSize = this.BuffersMaxSize; other._JsonOptions = this._JsonOptions; other.Validation = this.Validation; other.JsonPostprocessor = this.JsonPostprocessor; } #endregion } partial class ModelRoot { #region save / write methods /// /// Writes this to a file in GLTF or GLB based on the extension of . /// /// A valid file path to write to. /// Optional settings. public void Save(string filePath, WriteSettings settings = null) { bool isGltfExtension = filePath .ToLower(System.Globalization.CultureInfo.InvariantCulture) .EndsWith(".gltf", StringComparison.OrdinalIgnoreCase); if (isGltfExtension) SaveGLTF(filePath, settings); else SaveGLB(filePath, settings); } /// /// Writes this to a file in GLB format. /// /// A valid file path to write to. /// Optional settings. public void SaveGLB(string filePath, WriteSettings settings = null) { if (!(settings is WriteContext context)) { context = WriteContext .CreateFromFile(filePath) .WithSettingsFrom(settings); } context.WithBinarySettings(); var name = Path.GetFileNameWithoutExtension(filePath); context.WriteBinarySchema2(name, this); } /// /// Writes this to a file in GLTF format. /// /// A valid file path to write to. /// Optional settings. /// /// Satellite files like buffers and images are also saved with the file name formatted as "FILE_{Index}.EXT". /// public void SaveGLTF(string filePath, WriteSettings settings = null) { if (!(settings is WriteContext context)) { context = WriteContext .CreateFromFile(filePath) .WithSettingsFrom(settings); } var name = Path.GetFileNameWithoutExtension(filePath); context.WriteTextSchema2(name, this); } [Obsolete("Use GetJsonPreview", true)] public string GetJSON(bool indented) { return GetJsonPreview(); } /// /// Gets the JSON document of this . /// /// A JSON content. /// /// ⚠ Beware: this method serializes the current model into a json, without taking care of the binary buffers, /// so the produced json might not be usable! /// public string GetJsonPreview() { return _GetJSON(true); } /// /// Gets the JSON document of this . /// /// The formatting of the JSON document. /// A JSON content. internal string _GetJSON(bool indented) { var options = new System.Text.Json.JsonWriterOptions { Indented = indented }; using (var mm = new System.IO.MemoryStream()) { _WriteJSON(mm, options, null); mm.Position = 0; using (var ss = new System.IO.StreamReader(mm)) { return ss.ReadToEnd(); } } } /// /// Writes this to a array in GLB format. /// /// Optional settings. /// A array containing a GLB file. public BYTES WriteGLB(WriteSettings settings = null) { using (var m = new MemoryStream()) { WriteGLB(m, settings); return m.ToArraySegment(); } } /// /// Writes this to a in GLB format. /// /// A open for writing. /// Optional settings. public void WriteGLB(Stream stream, WriteSettings settings = null) { Guard.NotNull(stream, nameof(stream)); Guard.IsTrue(stream.CanWrite, nameof(stream)); var context = WriteContext .CreateFromStream(stream) .WithSettingsFrom(settings); if (settings != null) { // override settings with required values for GLB writing. context.MergeBuffers = true; context.ImageWriting = ResourceWriteMode.Default; } context.WriteBinarySchema2("model", this); } #endregion #region core internal void _WriteJSON(System.IO.Stream sw, System.Text.Json.JsonWriterOptions options, JsonFilterCallback filter) { if (filter == null) { using (var writer = new System.Text.Json.Utf8JsonWriter(sw, options)) { this.Serialize(writer); } return; } string text = null; using (var mm = new System.IO.MemoryStream()) { _WriteJSON(mm, options, null); mm.Position = 0; using (var ss = new System.IO.StreamReader(mm)) { text = ss.ReadToEnd(); } } text = filter.Invoke(text); var bytes = System.Text.Encoding.UTF8.GetBytes(text); using (var mm = new System.IO.MemoryStream(bytes, false)) { mm.CopyTo(sw); } } internal void _PrepareBuffersForSatelliteWriting(WriteContext context, string baseName) { // setup all buffers to be written as satellite files for (int i = 0; i < this._buffers.Count; ++i) { var buffer = this._buffers[i]; var bname = this._buffers.Count != 1 ? $"{baseName}_{i}.bin" : $"{baseName}.bin"; buffer._WriteToSatellite(context, bname); } } internal void _PrepareBuffersForInternalWriting() { // setup all buffers to be written internally for (int i = 0; i < this._buffers.Count; ++i) { var buffer = this._buffers[i]; buffer._WriteToInternal(); } } internal void _PrepareImagesForWriting(WriteContext context, string baseName, ResourceWriteMode rmode) { if (context.ImageWriting != ResourceWriteMode.Default) rmode = context.ImageWriting; // setup all images to be written to the appropiate location. for (int i = 0; i < this._images.Count; ++i) { var image = this._images[i]; if (rmode != ResourceWriteMode.SatelliteFile) { image._WriteToInternal(); } else { var iname = this._images.Count != 1 ? $"{baseName}_{i}" : $"{baseName}"; image._WriteToSatellite(context, iname); } } } internal void _AfterWriting() { foreach (var b in this._buffers) b._ClearAfterWrite(); foreach (var i in this._images) i._ClearAfterWrite(); } #endregion } }