// Gets a single timestamp component static int GetTimestampVar(FbxNode timestamp, string element) { var elementNode = timestamp[element].FirstOrDefault(); if (elementNode != null && elementNode.Properties.Length > 0) { var prop = elementNode.Properties[0]; if (prop.TryGetAsLong(out var longValue)) { return((int)longValue); } } throw new FbxException(-1, "Timestamp has no " + element); }
// Gets a single timestamp component static int GetTimestampVar(FbxNode timestamp, string element) { var elementNode = timestamp[element]; if (elementNode != null && elementNode.Properties.Length > 0) { var prop = elementNode.Properties[0].AsObject; if (prop is int || prop is long) { return((int)prop); } } throw new FbxException(timePath, -1, "Timestamp has no " + element); }
// Adds the given node text to the string void BuildString(FbxNode node, LineStringBuilder sb, FbxVersion version, int indentLevel = 0) { // Write identifier sb.Indent(indentLevel); node.Identifier.WriteAscii(version, sb, indentLevel); // Write properties var first = true; for (int j = 0; j < node.Properties.Length; j++) { var p = node.Properties[j]; if (p == null) { continue; } if (!first) { sb.Append(","); } sb.Append(" "); p.WriteAscii(version, sb, indentLevel); first = false; } // Write child nodes if (node.Nodes.Length > 0) { sb.Append(" {\n"); foreach (var n in node.Nodes) { if (n == null) { continue; } BuildString(n, sb, version, indentLevel + 1); } sb.Indent(indentLevel); sb.Append("}"); } sb.Append("\n"); }
// 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 the next node from the stream /// </summary> /// <returns>The read node, or <c>null</c></returns> public FbxNode ReadNode() { var first = ReadToken(); if (!(first is Identifier id)) { if (first is EndOfStream) { return(null); } throw new FbxException(_fbxAsciiFileInfo, "Unexpected '" + first + "', expected an identifier"); } var node = new FbxNode { Name = id.Value }; // Read properties object token = ReadToken(); bool expectComma = false; while (!'{'.Equals(token) && !(token is Identifier) && !'}'.Equals(token)) { if (expectComma) { if (!','.Equals(token)) { throw new FbxException(_fbxAsciiFileInfo, "Unexpected '" + token + "', expected a ','"); } expectComma = false; token = ReadToken(); continue; } if (token is char c) { switch (c) { case '*': token = ReadArray(); break; case '}': case ':': case ',': throw new FbxException(_fbxAsciiFileInfo, "Unexpected '" + c + "' in property list"); } } node.AddProperty(new FbxValue(token)); expectComma = true; // The final comma before the open brace isn't required token = ReadToken(); } // TODO: Merge property list into an array as necessary // Now we're either at an open brace, close brace or a new node if (token is Identifier || '}'.Equals(token)) { prevToken = token; return(node); } // The while loop can't end unless we're at an open brace, so we can continue right on object endBrace = ReadToken(); while (!'}'.Equals(endBrace)) { prevToken = endBrace; // If it's not an end brace, the next node will need it node.AddNode(ReadNode()); endBrace = ReadToken(); } if (node.Nodes.Length < 1) // If there's an open brace, we want that to be preserved { node.AddNode(null); } return(node); }
// Adds the given node text to the string void BuildString(FbxNode node, StringBuilder sb, bool writeArrayLength, int indentLevel = 0) { nodePath.Push(node.Name ?? ""); int lineStart = sb.Length; // Write identifier for (int i = 0; i < indentLevel; i++) { sb.Append('\t'); } sb.Append(node.Name).Append(':'); // Write properties var first = true; for (int j = 0; j < node.Properties.Length; j++) { var p = node.Properties[j].AsObject; if (p == null) { continue; } if (!first) { sb.Append(','); } sb.Append(' '); if (p is string) { sb.Append('"').Append(p).Append('"'); } else if (p is Array array) { var elementType = p.GetType().GetElementType(); // ReSharper disable once PossibleNullReferenceException // We know it's an array, so we don't need to check for null if (array.Rank != 1 || !elementType.GetTypeInfo().IsPrimitive) { throw new FbxException(nodePath, j, "Invalid array type " + p.GetType()); } if (writeArrayLength) { sb.Append('*').Append(array.Length).Append(" {\n"); lineStart = sb.Length; for (int i = -1; i < indentLevel; i++) { sb.Append('\t'); } sb.Append("a: "); } bool pFirst = true; foreach (var v in (Array)p) { if (!pFirst) { sb.Append(','); } var vstr = v.ToString(); if ((sb.Length - lineStart) + vstr.Length >= MaxLineLength) { sb.Append('\n'); lineStart = sb.Length; } sb.Append(vstr); pFirst = false; } if (writeArrayLength) { sb.Append('\n'); for (int i = 0; i < indentLevel; i++) { sb.Append('\t'); } sb.Append('}'); } } else if (p is char) { sb.Append((char)p); } else if (p.GetType().GetTypeInfo().IsPrimitive&& p is IFormattable) { sb.Append(p); } else { throw new FbxException(nodePath, j, "Invalid property type " + p.GetType()); } first = false; } // Write child nodes if (node.Nodes.Length > 0) { sb.Append(" {\n"); foreach (var n in node.Nodes) { if (n == null) { continue; } BuildString(n, sb, writeArrayLength, indentLevel + 1); } for (int i = 0; i < indentLevel; i++) { sb.Append('\t'); } sb.Append('}'); } sb.Append('\n'); 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; } }
/// <summary> /// Add node to node array /// </summary> /// <param name="node"></param> public void AddNode(FbxNode node) { _nodes.Add(node); }
/// <summary> /// Reads the next node from the stream /// </summary> /// <returns>The read node, or <c>null</c></returns> public FbxNode ReadNode() { var first = ReadToken(); if (!(first is IdentifierToken id)) { if (first is Token tok && tok.TokenType == TokenType.EndOfStream) { return(null); } throw new FbxException(_fbxAsciiFileInfo, "Unexpected '" + first + "', expected an identifier"); } var node = new FbxNode(id); // Read properties Token token = ReadToken(); bool expectComma = false; while (token.TokenType != TokenType.OpenBrace && token.TokenType != TokenType.Identifier && token.TokenType != TokenType.CloseBrace) { if (expectComma) { if (token.TokenType != TokenType.Comma) { throw new FbxException(_fbxAsciiFileInfo, "Unexpected '" + token + "', expected a ','"); } expectComma = false; token = ReadToken(); continue; } if (token.TokenType == TokenType.Asterix) { token = ReadArray(); } else if (token.TokenType == TokenType.CloseBrace || token.TokenType == TokenType.Comma) { throw new FbxException(_fbxAsciiFileInfo, "Unexpected '" + token.TokenType + "' in property list"); } node.AddProperty(token); expectComma = true; // The final comma before the open brace isn't required token = ReadToken(); } // TODO: Merge property list into an array as necessary // Now we're either at an open brace, close brace or a new node if (token.TokenType == TokenType.Identifier || token.TokenType == TokenType.CloseBrace) { _tokenStack.Push(token); return(node); } // The while loop can't end unless we're at an open brace, so we can continue right on Token endBrace = ReadToken(); while (endBrace.TokenType != TokenType.CloseBrace) { _tokenStack.Push(endBrace); node.AddNode(ReadNode()); endBrace = ReadToken(); } if (node.Nodes.Length < 1) // If there's an open brace, we want that to be preserved { node.AddNode(null); } return(node); }