/// <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(FbxRootNode 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.v7100); sb.Append('\n'); } var b = Encoding.ASCII.GetBytes(sb.ToString()); _stream.Write(b, 0, b.Length); }
/// <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 FbxRootNode Parse() { _stream.BaseStream.Position = 0; // Read header bool validHeader = ReadHeader(_stream.BaseStream); if (_errorLevel >= ErrorLevel.Strict && !validHeader) { throw new FbxException(_stream.BaseStream.Position, "Invalid header string"); } var document = new FbxRootNode { Version = (FbxVersion)_stream.ReadInt32() }; // Read nodes var dataPos = _stream.BaseStream.Position; FbxNode nested; do { nested = ReadNode(document); 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); }
/// <summary> /// Reads a full document from the stream /// </summary> /// <returns>The complete document object</returns> public FbxRootNode Parse() { var ret = new FbxRootNode(); _stream.Position = 0; // 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 file to the output /// </summary> /// <param name="document"></param> public void Write(FbxRootNode 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(document, node); } WriteNode(document, null); stream.Write(GenerateFooterCode(document)); WriteFooter(stream, (int)document.Version); output.Write(memory.GetBuffer(), 0, (int)memory.Position); }
public static void WriteAscii(string path, FbxRootNode root) { using (FbxWriter writer = new FbxWriter(path, root.Version)) writer.WriteAscii(root); }
/// <inheritdoc/> public void WriteBinary(FbxRootNode root) { using (FbxBinaryWriter writer = new FbxBinaryWriter(this._stream)) writer.Write(root); }
/// <inheritdoc/> public void WriteAscii(FbxRootNode root) { using (FbxAsciiWriter writer = new FbxAsciiWriter(this._stream)) writer.Write(root); }
/// <inheritdoc/> public void WriteBinary(Scene scene) { using (FbxBinaryWriter writer = new FbxBinaryWriter(this._stream)) writer.Write(FbxRootNode.CreateFromScene(scene, this.Version)); }
// Writes a single document to the buffer void WriteNode(FbxRootNode document, FbxNode node) { if (node == null) { var data = document.Version >= FbxVersion.v7500 ? 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.v7500) { 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.v7500) { 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.v7500) { stream.Write((long)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(FbxRootNode document) { var endOffset = document.Version >= FbxVersion.v7500 ? _stream.ReadInt64() : _stream.ReadInt32(); var numProperties = document.Version >= FbxVersion.v7500 ? _stream.ReadInt64() : _stream.ReadInt32(); var propertyListLen = document.Version >= FbxVersion.v7500 ? _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); if (nested != null) { 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); }