Serialization.WriteSettings.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. using System;
  2. using System.IO;
  3. using BYTES = System.ArraySegment<byte>;
  4. using MODEL = SharpGLTF.Schema2.ModelRoot;
  5. using VALIDATIONMODE = SharpGLTF.Validation.ValidationMode;
  6. namespace SharpGLTF.Schema2
  7. {
  8. /// <summary>
  9. /// Determines how resources are written.
  10. /// </summary>
  11. public enum ResourceWriteMode
  12. {
  13. /// <summary>
  14. /// Use the most appropiate mode.
  15. /// </summary>
  16. Default,
  17. /// <summary>
  18. /// Resources will be stored as external satellite files.
  19. /// </summary>
  20. SatelliteFile,
  21. /// <summary>
  22. /// Resources will be embedded into the JSON encoded in MIME64.
  23. /// </summary>
  24. Embedded,
  25. /// <summary>
  26. /// Resources will be stored as internal binary buffers. Valid only for <see cref="Image"/>
  27. /// </summary>
  28. BufferView
  29. }
  30. /// <summary>
  31. /// Write settings and base class of <see cref="WriteContext"/>
  32. /// </summary>
  33. public class WriteSettings
  34. {
  35. #region lifecycle
  36. public static implicit operator WriteSettings(VALIDATIONMODE vmode)
  37. {
  38. return new WriteSettings
  39. {
  40. Validation = vmode
  41. };
  42. }
  43. public WriteSettings() { }
  44. public WriteSettings(WriteSettings other)
  45. {
  46. Guard.NotNull(other, nameof(other));
  47. other.CopyTo(this);
  48. }
  49. #endregion
  50. #region data
  51. private System.Text.Json.JsonWriterOptions _JsonOptions = default;
  52. #endregion
  53. #region properties
  54. /// <summary>
  55. /// Gets or sets a value indicating how to write the images of the model.
  56. /// </summary>
  57. public ResourceWriteMode ImageWriting { get; set; } = ResourceWriteMode.Default;
  58. /// <summary>
  59. /// Gets or sets a callback hook that controls the image writing behavior.
  60. /// </summary>
  61. public ImageWriterCallback ImageWriteCallback { get; set; }
  62. /// <summary>
  63. /// Gets or sets a value indicating whether to merge all the buffers in <see cref="MODEL.LogicalBuffers"/> into a single buffer.
  64. /// </summary>
  65. public Boolean MergeBuffers { get; set; } = true;
  66. /// <summary>
  67. /// Gets or sets the size used to split all the resources into individual buffers.
  68. /// </summary>
  69. /// <remarks>
  70. /// It only has an effect when these conditions are met:
  71. /// <list type="table">
  72. /// <item><see cref="MergeBuffers"/> must be true.</item>
  73. /// <item>Output format must be glTF, not GLB</item>
  74. /// </list>
  75. /// </remarks>
  76. public int BuffersMaxSize { get; set; } = int.MaxValue;
  77. /// <summary>
  78. /// Gets or sets a value indicating whether the JSON formatting will include indentation.
  79. /// </summary>
  80. public Boolean JsonIndented
  81. {
  82. get => _JsonOptions.Indented;
  83. set => _JsonOptions.Indented = value;
  84. }
  85. /// <summary>
  86. /// Gets or sets a value indicating the Json options to be used for writing.
  87. /// </summary>
  88. public System.Text.Json.JsonWriterOptions JsonOptions
  89. {
  90. get => _JsonOptions;
  91. set => _JsonOptions = value;
  92. }
  93. /// <summary>
  94. /// Gets or sets a value indicating the level of validation applied when loading a file.
  95. /// </summary>
  96. public VALIDATIONMODE Validation { get; set; } = VALIDATIONMODE.Strict;
  97. /// <summary>
  98. /// Gets or sets the callback used to postprocess the json text before parsing it.
  99. /// </summary>
  100. public JsonFilterCallback JsonPostprocessor { get; set; }
  101. #endregion
  102. #region API
  103. public void CopyTo(WriteSettings other)
  104. {
  105. Guard.NotNull(other, nameof(other));
  106. other.ImageWriting = this.ImageWriting;
  107. other.ImageWriteCallback = this.ImageWriteCallback;
  108. other.MergeBuffers = this.MergeBuffers;
  109. other.BuffersMaxSize = this.BuffersMaxSize;
  110. other._JsonOptions = this._JsonOptions;
  111. other.Validation = this.Validation;
  112. other.JsonPostprocessor = this.JsonPostprocessor;
  113. }
  114. #endregion
  115. }
  116. partial class ModelRoot
  117. {
  118. #region save / write methods
  119. /// <summary>
  120. /// Writes this <see cref="MODEL"/> to a file in GLTF or GLB based on the extension of <paramref name="filePath"/>.
  121. /// </summary>
  122. /// <param name="filePath">A valid file path to write to.</param>
  123. /// <param name="settings">Optional settings.</param>
  124. public void Save(string filePath, WriteSettings settings = null)
  125. {
  126. bool isGltfExtension = filePath
  127. .ToLower(System.Globalization.CultureInfo.InvariantCulture)
  128. .EndsWith(".gltf", StringComparison.OrdinalIgnoreCase);
  129. if (isGltfExtension) SaveGLTF(filePath, settings);
  130. else SaveGLB(filePath, settings);
  131. }
  132. /// <summary>
  133. /// Writes this <see cref="MODEL"/> to a file in GLB format.
  134. /// </summary>
  135. /// <param name="filePath">A valid file path to write to.</param>
  136. /// <param name="settings">Optional settings.</param>
  137. public void SaveGLB(string filePath, WriteSettings settings = null)
  138. {
  139. if (!(settings is WriteContext context))
  140. {
  141. context = WriteContext
  142. .CreateFromFile(filePath)
  143. .WithSettingsFrom(settings);
  144. }
  145. context.WithBinarySettings();
  146. var name = Path.GetFileNameWithoutExtension(filePath);
  147. context.WriteBinarySchema2(name, this);
  148. }
  149. /// <summary>
  150. /// Writes this <see cref="MODEL"/> to a file in GLTF format.
  151. /// </summary>
  152. /// <param name="filePath">A valid file path to write to.</param>
  153. /// <param name="settings">Optional settings.</param>
  154. /// <remarks>
  155. /// Satellite files like buffers and images are also saved with the file name formatted as "FILE_{Index}.EXT".
  156. /// </remarks>
  157. public void SaveGLTF(string filePath, WriteSettings settings = null)
  158. {
  159. if (!(settings is WriteContext context))
  160. {
  161. context = WriteContext
  162. .CreateFromFile(filePath)
  163. .WithSettingsFrom(settings);
  164. }
  165. var name = Path.GetFileNameWithoutExtension(filePath);
  166. context.WriteTextSchema2(name, this);
  167. }
  168. [Obsolete("Use GetJsonPreview", true)]
  169. public string GetJSON(bool indented) { return GetJsonPreview(); }
  170. /// <summary>
  171. /// Gets the JSON document of this <see cref="MODEL"/>.
  172. /// </summary>
  173. /// <returns>A JSON content.</returns>
  174. /// <remarks>
  175. /// ⚠ Beware: this method serializes the current model into a json, without taking care of the binary buffers,
  176. /// so the produced json might not be usable!
  177. /// </remarks>
  178. public string GetJsonPreview()
  179. {
  180. return _GetJSON(true);
  181. }
  182. /// <summary>
  183. /// Gets the JSON document of this <see cref="MODEL"/>.
  184. /// </summary>
  185. /// <param name="indented">The formatting of the JSON document.</param>
  186. /// <returns>A JSON content.</returns>
  187. internal string _GetJSON(bool indented)
  188. {
  189. var options = new System.Text.Json.JsonWriterOptions
  190. {
  191. Indented = indented
  192. };
  193. using (var mm = new System.IO.MemoryStream())
  194. {
  195. _WriteJSON(mm, options, null);
  196. mm.Position = 0;
  197. using (var ss = new System.IO.StreamReader(mm))
  198. {
  199. return ss.ReadToEnd();
  200. }
  201. }
  202. }
  203. /// <summary>
  204. /// Writes this <see cref="MODEL"/> to a <see cref="byte"/> array in GLB format.
  205. /// </summary>
  206. /// <param name="settings">Optional settings.</param>
  207. /// <returns>A <see cref="byte"/> array containing a GLB file.</returns>
  208. public BYTES WriteGLB(WriteSettings settings = null)
  209. {
  210. using (var m = new MemoryStream())
  211. {
  212. WriteGLB(m, settings);
  213. return m.ToArraySegment();
  214. }
  215. }
  216. /// <summary>
  217. /// Writes this <see cref="MODEL"/> to a <see cref="Stream"/> in GLB format.
  218. /// </summary>
  219. /// <param name="stream">A <see cref="Stream"/> open for writing.</param>
  220. /// <param name="settings">Optional settings.</param>
  221. public void WriteGLB(Stream stream, WriteSettings settings = null)
  222. {
  223. Guard.NotNull(stream, nameof(stream));
  224. Guard.IsTrue(stream.CanWrite, nameof(stream));
  225. var context = WriteContext
  226. .CreateFromStream(stream)
  227. .WithSettingsFrom(settings);
  228. if (settings != null)
  229. {
  230. // override settings with required values for GLB writing.
  231. context.MergeBuffers = true;
  232. context.ImageWriting = ResourceWriteMode.Default;
  233. }
  234. context.WriteBinarySchema2("model", this);
  235. }
  236. #endregion
  237. #region core
  238. internal void _WriteJSON(System.IO.Stream sw, System.Text.Json.JsonWriterOptions options, JsonFilterCallback filter)
  239. {
  240. if (filter == null)
  241. {
  242. using (var writer = new System.Text.Json.Utf8JsonWriter(sw, options))
  243. {
  244. this.Serialize(writer);
  245. }
  246. return;
  247. }
  248. string text = null;
  249. using (var mm = new System.IO.MemoryStream())
  250. {
  251. _WriteJSON(mm, options, null);
  252. mm.Position = 0;
  253. using (var ss = new System.IO.StreamReader(mm))
  254. {
  255. text = ss.ReadToEnd();
  256. }
  257. }
  258. text = filter.Invoke(text);
  259. var bytes = System.Text.Encoding.UTF8.GetBytes(text);
  260. using (var mm = new System.IO.MemoryStream(bytes, false))
  261. {
  262. mm.CopyTo(sw);
  263. }
  264. }
  265. internal void _PrepareBuffersForSatelliteWriting(WriteContext context, string baseName)
  266. {
  267. // setup all buffers to be written as satellite files
  268. for (int i = 0; i < this._buffers.Count; ++i)
  269. {
  270. var buffer = this._buffers[i];
  271. var bname = this._buffers.Count != 1 ? $"{baseName}_{i}.bin" : $"{baseName}.bin";
  272. buffer._WriteToSatellite(context, bname);
  273. }
  274. }
  275. internal void _PrepareBuffersForInternalWriting()
  276. {
  277. // setup all buffers to be written internally
  278. for (int i = 0; i < this._buffers.Count; ++i)
  279. {
  280. var buffer = this._buffers[i];
  281. buffer._WriteToInternal();
  282. }
  283. }
  284. internal void _PrepareImagesForWriting(WriteContext context, string baseName, ResourceWriteMode rmode)
  285. {
  286. if (context.ImageWriting != ResourceWriteMode.Default) rmode = context.ImageWriting;
  287. // setup all images to be written to the appropiate location.
  288. for (int i = 0; i < this._images.Count; ++i)
  289. {
  290. var image = this._images[i];
  291. if (rmode != ResourceWriteMode.SatelliteFile)
  292. {
  293. image._WriteToInternal();
  294. }
  295. else
  296. {
  297. var iname = this._images.Count != 1 ? $"{baseName}_{i}" : $"{baseName}";
  298. image._WriteToSatellite(context, iname);
  299. }
  300. }
  301. }
  302. internal void _AfterWriting()
  303. {
  304. foreach (var b in this._buffers) b._ClearAfterWrite();
  305. foreach (var i in this._images) i._ClearAfterWrite();
  306. }
  307. #endregion
  308. }
  309. }