/// <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 LineStringBuilder(); // 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"); foreach (var n in document.Nodes) { if (n == null) { continue; } BuildString(n, sb, document.Version); 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="stream"></param> public static void WriteAscii(FbxDocument document, Stream stream) { if (stream == null) { throw new ArgumentNullException(nameof(stream)); } var writer = new FbxAsciiWriter(stream); writer.Write(document); }
/// <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)) { WriteAscii(document, stream); } }
/// <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 FbxNode nested; do { nested = ReadNode(document); if (nested != null) { document.AddNode(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 var dataPos = stream.BaseStream.Position; var validFooterExtension = CheckFooter(stream, document.Version); if (errorLevel >= ErrorLevel.Strict && !validFooterExtension) { throw new FbxException(dataPos, "Invalid footer"); } return(document); }
/// <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; do { c = ReadChar(); }while (char.IsWhiteSpace(c)); bool hasVersionString = false; if (c == ';') { var sb = new StringBuilder(); do { sb.Append(c); c = ReadChar(); } while (!IsLineEnd(c) && !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.AddNode(node); } return(ret); }
/// <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? foreach (var node in document.Nodes) { WriteNode(document, node); } WriteNode(document, null); stream.Write(GenerateFooterCode(document)); WriteFooter(stream, (int)document.Version); output.Write(memory.ToArray(), 0, (int)memory.Position); }
/// <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"; _stream.TryParseWhiteSpaceToken(_fbxAsciiFileInfo, out var _); bool hasVersionString = false; if (_stream.TryParseCommentToken(_fbxAsciiFileInfo, out var comment)) { var match = Regex.Match(comment, 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(_fbxAsciiFileInfo, "Invalid version string; first line must match \"" + versionString + "\""); } FbxNode node; while ((node = ReadNode()) != null) { ret.AddNode(node); } return(ret); }
// 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.Length); propertyLengthPos = stream.BaseStream.Position; stream.Write((long)0); // Property length placeholder } else { stream.Write(0); // End offset placeholder stream.Write(node.Properties.Length); 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.Length; i++) { WriteProperty(node.Properties[i].AsObject, i); } var propertyEnd = stream.BaseStream.Position; stream.BaseStream.Position = propertyLengthPos; if (document.Version >= FbxVersion.v7_5) { stream.Write(propertyEnd - propertyBegin); } else { stream.Write((int)(propertyEnd - propertyBegin)); } stream.BaseStream.Position = propertyEnd; // Write child nodes if (node.Nodes.Length > 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(dataEnd); } else { stream.Write((int)dataEnd); } stream.BaseStream.Position = dataEnd; nodePath.Pop(); } }
/// <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.AddProperty(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.AddNode(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); }
// 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 { // 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.Length); propertyLengthPos = stream.BaseStream.Position; stream.Write((long)0); // Property length placeholder } else { stream.Write(0); // End offset placeholder stream.Write(node.Properties.Length); propertyLengthPos = stream.BaseStream.Position; stream.Write(0); // Property length placeholder } node.Identifier.WriteBinary(document.Version, stream); // Write properties and length var propertyBegin = stream.BaseStream.Position; for (int i = 0; i < node.Properties.Length; i++) { WriteProperty(document.Version, node.Properties[i]); } var propertyEnd = stream.BaseStream.Position; stream.BaseStream.Position = propertyLengthPos; if (document.Version >= FbxVersion.v7_5) { stream.Write(propertyEnd - propertyBegin); } else { stream.Write((int)(propertyEnd - propertyBegin)); } stream.BaseStream.Position = propertyEnd; // Write child nodes if (node.Nodes.Length > 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(dataEnd); } else { stream.Write((int)dataEnd); } stream.BaseStream.Position = dataEnd; } }