/// <summary> /// Writes an FBX document to the stream /// </summary> /// <param name="document"></param> /// <exception cref="ArgumentNullException"/> /// <remarks> /// ASCII FBX files have no header or footer, so you can call this multiple times /// </remarks> public void Write(FbxDocument document) { if (document == null) { throw new ArgumentNullException(nameof(document)); } var sb = new StringBuilder(); // Write version header (a comment, but required for many importers) var vMajor = (int)document.Version / 1000; var vMinor = ((int)document.Version % 1000) / 100; var vRev = ((int)document.Version % 100) / 10; sb.Append($"; FBX {vMajor}.{vMinor}.{vRev} project file\n\n"); nodePath.Clear(); foreach (var n in document.Nodes) { if (n == null) { continue; } BuildString(n, sb, document.Version >= FbxVersion.v7_1); sb.Append('\n'); } var b = Encoding.ASCII.GetBytes(sb.ToString()); stream.Write(b, 0, b.Length); }
/// <summary> /// Writes an FBX document /// </summary> /// <param name="document">The top level document node</param> /// <param name="path"></param> public static void WriteAscii(FbxDocument document, string path) { if (path == null) throw new ArgumentNullException(nameof(path)); using (var stream = new FileStream(path, FileMode.Create)) { var writer = new FbxAsciiWriter(stream); writer.Write(document); } }
/// <summary> /// Reads a full document from the stream /// </summary> /// <returns>The complete document object</returns> public FbxDocument Read() { CultureInfo cultureInfoOriginal = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; var ret = new FbxDocument(); // Read version string const string versionString = @"; FBX (\d)\.(\d)\.(\d) project file"; char c; while (char.IsWhiteSpace(c = ReadChar()) && !endStream) { } // Skip whitespace bool hasVersionString = false; if (c == ';') { var sb = new StringBuilder(); do { sb.Append(c); } while (!IsLineEnd(c = ReadChar()) && !endStream); var match = Regex.Match(sb.ToString(), versionString); hasVersionString = match.Success; if (hasVersionString) { ret.Version = (FbxVersion)( int.Parse(match.Groups[1].Value) * 1000 + int.Parse(match.Groups[2].Value) * 100 + int.Parse(match.Groups[3].Value) * 10 ); } } if (!hasVersionString && errorLevel >= ErrorLevel.Strict) { throw new FbxException(line, column, "Invalid version string; first line must match \"" + versionString + "\""); } FbxNode node; while ((node = ReadNode()) != null) { ret.Nodes.Add(node); } return(ret); } finally { Thread.CurrentThread.CurrentCulture = cultureInfoOriginal; } }
/// <summary> /// Writes an FBX document /// </summary> /// <param name="document">The top level document node</param> /// <param name="path"></param> public static void WriteAscii(FbxDocument document, string path) { if (path == null) { throw new ArgumentNullException(nameof(path)); } using (var stream = new FileStream(path, FileMode.Create)) { var writer = new FbxAsciiWriter(stream); writer.Write(document); } }
public FbxDocument Read() { // Read header bool validHeader = ReadHeader(binStream.BaseStream); if (errorLevel >= ErrorLevel.Strict && !validHeader) { throw new FbxException(binStream.BaseStream.Position, "Invalid header string"); } versionNumber = binStream.ReadInt32(); var document = new FbxDocument { Version = (FbxVersion)versionNumber }; // Read nodes FbxNode nested; do { nested = ReadNode(); if (nested != null) { document.Nodes.Add(nested); } } while (nested != null); // Read footer code var footerCode = new byte[footerCodeSize]; binStream.BaseStream.Read(footerCode, 0, footerCode.Length); if (errorLevel >= ErrorLevel.Strict) { var validCode = GenerateFooterCode(document); if (!CheckEqual(footerCode, validCode)) { throw new FbxException(binStream.BaseStream.Position - footerCodeSize, "Incorrect footer code"); } } // Read footer extension var validFooterExtension = CheckFooter(binStream, document.Version); if (errorLevel >= ErrorLevel.Strict && !validFooterExtension) { throw new FbxException(binStream.BaseStream.Position, "Invalid footer"); } return(document); }
/// <summary> /// Writes an FBX file to the output /// </summary> /// <param name="document"></param> public void Write(FbxDocument document) { stream.BaseStream.Position = 0; WriteHeader(stream.BaseStream); stream.Write((int)document.Version); // TODO: Do we write a top level node or not? Maybe check the version? nodePath.Clear(); foreach (var node in document.Nodes) { WriteNode(node); } WriteNode(null); stream.Write(GenerateFooterCode(document)); WriteFooter(stream, (int)document.Version); output.Write(memory.GetBuffer(), 0, (int)memory.Position); }
/// <summary> /// Writes an FBX document /// </summary> /// <param name="document">The top level document node</param> /// <param name="path"></param> public static void WriteAscii(FbxDocument document, Stream stream) { CultureInfo cultureInfoOriginal = Thread.CurrentThread.CurrentCulture; try { Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; var writer = new FbxAsciiWriter(stream); writer.Write(document); } finally { Thread.CurrentThread.CurrentCulture = cultureInfoOriginal; } }
/// <summary> /// Reads an FBX document from the stream /// </summary> /// <returns>The top-level node</returns> /// <exception cref="FbxException">The FBX data was malformed /// for the reader's error level</exception> public FbxDocument Read() { // Read header bool validHeader = ReadHeader(stream.BaseStream); if (errorLevel >= ErrorLevel.Strict && !validHeader) throw new FbxException(stream.BaseStream.Position, "Invalid header string"); var document = new FbxDocument {Version = (FbxVersion) stream.ReadInt32()}; // Read nodes var dataPos = stream.BaseStream.Position; FbxNode nested; do { nested = ReadNode(); if(nested != null) document.Nodes.Add(nested); } while (nested != null); // Read footer code var footerCode = new byte[footerCodeSize]; stream.BaseStream.Read(footerCode, 0, footerCode.Length); if (errorLevel >= ErrorLevel.Strict) { var validCode = GenerateFooterCode(document); if(!CheckEqual(footerCode, validCode)) throw new FbxException(stream.BaseStream.Position - footerCodeSize, "Incorrect footer code"); } // Read footer extension dataPos = stream.BaseStream.Position; var validFooterExtension = CheckFooter(stream, document.Version); if(errorLevel >= ErrorLevel.Strict && !validFooterExtension) throw new FbxException(dataPos, "Invalid footer"); return document; }
// Writes a single document to the buffer void WriteNode(FbxDocument document, FbxNode node) { if (node == null) { var data = document.Version >= FbxVersion.v7_5 ? nullData7500 : nullData; stream.BaseStream.Write(data, 0, data.Length); } else { nodePath.Push(node.Name ?? ""); var name = string.IsNullOrEmpty(node.Name) ? null : Encoding.ASCII.GetBytes(node.Name); if (name != null && name.Length > byte.MaxValue) { throw new FbxException(stream.BaseStream.Position, "Node name is too long"); } // Header var endOffsetPos = stream.BaseStream.Position; long propertyLengthPos; if (document.Version >= FbxVersion.v7_5) { stream.Write((long)0); // End offset placeholder stream.Write((long)node.Properties.Count); propertyLengthPos = stream.BaseStream.Position; stream.Write((long)0); // Property length placeholder } else { stream.Write(0); // End offset placeholder stream.Write(node.Properties.Count); propertyLengthPos = stream.BaseStream.Position; stream.Write(0); // Property length placeholder } stream.Write((byte)(name?.Length ?? 0)); if (name != null) { stream.Write(name); } // Write properties and length var propertyBegin = stream.BaseStream.Position; for (int i = 0; i < node.Properties.Count; i++) { WriteProperty(node.Properties[i], i); } var propertyEnd = stream.BaseStream.Position; stream.BaseStream.Position = propertyLengthPos; if (document.Version >= FbxVersion.v7_5) { stream.Write((long)(propertyEnd - propertyBegin)); } else { stream.Write((int)(propertyEnd - propertyBegin)); } stream.BaseStream.Position = propertyEnd; // Write child nodes if (node.Nodes.Count > 0) { foreach (var n in node.Nodes) { if (n == null) { continue; } WriteNode(document, n); } WriteNode(document, null); } // Write end offset var dataEnd = stream.BaseStream.Position; stream.BaseStream.Position = endOffsetPos; if (document.Version >= FbxVersion.v7_5) { stream.Write((long)dataEnd); } else { stream.Write((int)dataEnd); } stream.BaseStream.Position = dataEnd; nodePath.Pop(); } }
/// <summary> /// Reads a full document from the stream /// </summary> /// <returns>The complete document object</returns> public FbxDocument Read() { var ret = new FbxDocument(); // Read version string const string versionString = @"; FBX (\d)\.(\d)\.(\d) project file"; char c; while(char.IsWhiteSpace(c = ReadChar()) && !endStream) { } // Skip whitespace bool hasVersionString = false; if (c == ';') { var sb = new StringBuilder(); do { sb.Append(c); } while (!IsLineEnd(c = ReadChar()) && !endStream); var match = Regex.Match(sb.ToString(), versionString); hasVersionString = match.Success; if(hasVersionString) ret.Version = (FbxVersion)( int.Parse(match.Groups[1].Value)*1000 + int.Parse(match.Groups[2].Value)*100 + int.Parse(match.Groups[3].Value)*10 ); } if(!hasVersionString && errorLevel >= ErrorLevel.Strict) throw new FbxException(line, column, "Invalid version string; first line must match \"" + versionString + "\""); FbxNode node; while((node = ReadNode()) != null) ret.Nodes.Add(node); return ret; }
/// <summary> /// Writes an FBX document /// </summary> /// <param name="document">The top level document node</param> /// <param name="stream">Output stream</param> public static void WriteBinary(FbxDocument document, Stream stream) { var writer = new FbxBinaryWriter(stream); writer.Write(document); }
/// <summary> /// Writes an FBX file to the output /// </summary> /// <param name="document"></param> public void Write(FbxDocument document) { stream.BaseStream.Position = 0; WriteHeader(stream.BaseStream); stream.Write((int)document.Version); // TODO: Do we write a top level node or not? Maybe check the version? nodePath.Clear(); foreach (var node in document.Nodes) WriteNode(node); WriteNode(null); stream.Write(GenerateFooterCode(document)); WriteFooter(stream, (int)document.Version); output.Write(memory.GetBuffer(), 0, (int)memory.Position); }
/// <summary> /// Reads a single node. /// </summary> /// <remarks> /// This won't read the file header or footer, and as such will fail if the stream is a full FBX file /// </remarks> /// <returns>The node</returns> /// <exception cref="FbxException">The FBX data was malformed /// for the reader's error level</exception> public FbxNode ReadNode(FbxDocument document) { var endOffset = document.Version >= FbxVersion.v7_5 ? stream.ReadInt64() : stream.ReadInt32(); var numProperties = document.Version >= FbxVersion.v7_5 ? stream.ReadInt64() : stream.ReadInt32(); var propertyListLen = document.Version >= FbxVersion.v7_5 ? stream.ReadInt64() : stream.ReadInt32(); var nameLen = stream.ReadByte(); var name = nameLen == 0 ? "" : Encoding.ASCII.GetString(stream.ReadBytes(nameLen)); if (endOffset == 0) { // The end offset should only be 0 in a null node if (errorLevel >= ErrorLevel.Checked && (numProperties != 0 || propertyListLen != 0 || !string.IsNullOrEmpty(name))) { throw new FbxException(stream.BaseStream.Position, "Invalid node; expected NULL record"); } return(null); } var node = new FbxNode { Name = name }; var propertyEnd = stream.BaseStream.Position + propertyListLen; // Read properties for (int i = 0; i < numProperties; i++) { node.Properties.Add(ReadProperty()); } if (errorLevel >= ErrorLevel.Checked && stream.BaseStream.Position != propertyEnd) { throw new FbxException(stream.BaseStream.Position, "Too many bytes in property list, end point is " + propertyEnd); } // Read nested nodes var listLen = endOffset - stream.BaseStream.Position; if (errorLevel >= ErrorLevel.Checked && listLen < 0) { throw new FbxException(stream.BaseStream.Position, "Node has invalid end point"); } if (listLen > 0) { FbxNode nested; do { nested = ReadNode(document); node.Nodes.Add(nested); } while (nested != null); if (errorLevel >= ErrorLevel.Checked && stream.BaseStream.Position != endOffset) { throw new FbxException(stream.BaseStream.Position, "Too many bytes in node, end point is " + endOffset); } } return(node); }
/// <summary> /// Writes an FBX document to the stream /// </summary> /// <param name="document"></param> /// <remarks> /// ASCII FBX files have no header or footer, so you can call this multiple times /// </remarks> public void Write(FbxDocument document) { if(document == null) throw new ArgumentNullException(nameof(document)); var sb = new StringBuilder(); // Write version header (a comment, but required for many importers) var vMajor = (int)document.Version/1000; var vMinor = ((int) document.Version%1000)/100; var vRev = ((int) document.Version%100)/10; sb.Append($"; FBX {vMajor}.{vMinor}.{vRev} project file\n\n"); nodePath.Clear(); foreach (var n in document.Nodes) { if (n == null) continue; BuildString(n, sb, document.Version >= FbxVersion.v7_1); sb.Append('\n'); } var b = Encoding.ASCII.GetBytes(sb.ToString()); stream.Write(b, 0, b.Length); }