diff --git a/src/Opc.Ua.OpenUsdScene.Server/UsdSceneExporter.cs b/src/Opc.Ua.OpenUsdScene.Server/UsdSceneExporter.cs
index b4114bdb75..a518ef54b6 100644
--- a/src/Opc.Ua.OpenUsdScene.Server/UsdSceneExporter.cs
+++ b/src/Opc.Ua.OpenUsdScene.Server/UsdSceneExporter.cs
@@ -209,11 +209,10 @@ private static UsdAttribute ExportAttribute(
// Infer liveness from the write access, so a Mode A attribute that is not
// historized still exports as live.
Live = (attributeNode.AccessLevel & Opc.Ua.AccessLevels.CurrentWrite) != 0,
- // BoxingBehavior.Legacy unwraps an ArrayOf/MatrixOf to the T[]/T[,]
- // shapes Decoerce consumes, so array, tuple and matrix values survive the
- // round trip instead of being stringified as an opaque struct (§7.2).
- Value = UsdValueCoercion.Decoerce(
- attributeNode.Value.AsBoxedObject(Variant.BoxingBehavior.Legacy))
+ // Decoerce reads the Variant through its typed accessors, so an ArrayOf or
+ // MatrixOf keeps its shape and array, tuple and matrix values survive the
+ // round trip without a boxing accessor (§7.2).
+ Value = UsdValueCoercion.Decoerce(attributeNode.Value)
};
// §7.2: time samples are held by the materialization result, not the node, so recover
@@ -411,14 +410,14 @@ private static void ExportMetadata(
///
/// Recovers a Metadata/ folder into a metadata dictionary (§6.3), the inverse of the
- /// materializer's typed authoring. A leaf Property is read back in its own type — Legacy
- /// boxing unwraps an ArrayOf<T>/MatrixOf<T> and Decoerce
- /// reverses the §6.2 coercion, so a value round-trips as itself rather than an opaque box
- /// (the same fix as the attribute-value path). A nested Metadata folder is recovered as a
- /// nested dictionary, so structured customData keeps its authored nesting to any depth.
+ /// materializer's typed authoring. A leaf Property is read back in its own type through
+ /// Decoerce, which reverses the §6.2 coercion off the Variant's typed accessors, so
+ /// a value round-trips as itself rather than an opaque box. A nested Metadata folder is
+ /// recovered as a nested dictionary, so structured customData keeps its authored
+ /// nesting to any depth.
///
private static void ReadMetadataFolder(
- ISystemContext context, NodeState folder, IDictionary into)
+ ISystemContext context, NodeState folder, IDictionary into)
{
var children = new List();
folder.GetChildren(context, children);
@@ -432,13 +431,12 @@ private static void ReadMetadataFolder(
switch (child)
{
case BaseVariableState property:
- into[key] = UsdValueCoercion.Decoerce(
- property.Value.AsBoxedObject(Variant.BoxingBehavior.Legacy));
+ into[key] = UsdValueCoercion.Decoerce(property.Value);
break;
case FolderState subFolder:
- var nested = new Dictionary(StringComparer.Ordinal);
+ var nested = new Dictionary(StringComparer.Ordinal);
ReadMetadataFolder(context, subFolder, nested);
- into[key] = nested;
+ into[key] = UsdValue.FromDictionary(nested);
break;
}
}
diff --git a/src/Opc.Ua.OpenUsdScene.Server/UsdSceneMaterializer.Properties.cs b/src/Opc.Ua.OpenUsdScene.Server/UsdSceneMaterializer.Properties.cs
index ad32765b8e..1afad0265a 100644
--- a/src/Opc.Ua.OpenUsdScene.Server/UsdSceneMaterializer.Properties.cs
+++ b/src/Opc.Ua.OpenUsdScene.Server/UsdSceneMaterializer.Properties.cs
@@ -345,18 +345,18 @@ private static void MaterializeMetadata(
private static void MaterializeMetadataEntries(
ISystemContext context,
NodeState folder,
- IEnumerable> entries,
+ IEnumerable> entries,
ushort ns)
{
- foreach (KeyValuePair entry in entries)
+ foreach (KeyValuePair entry in entries)
{
if (string.IsNullOrEmpty(entry.Key))
{
continue;
}
- if (TryAsNestedDictionary(
- entry.Value, out IEnumerable> nested))
+ if (entry.Value.TryGetDictionary(
+ out IReadOnlyDictionary nested))
{
var subFolder = new FolderState(folder)
{
@@ -380,13 +380,13 @@ private static void MaterializeMetadataEntries(
DataType = Opc.Ua.DataTypeIds.BaseDataType,
ValueRank = ValueRanks.Scalar
};
- if (entry.Value != null)
+ if (!entry.Value.IsNull)
{
if (TryCoerceMetadataValue(
entry.Value, out Variant variant, out NodeId dataType, out int valueRank))
{
// A recognised value keeps its type through the round trip, so the
- // exporter recovers the authored object rather than an opaque string (§6.3).
+ // exporter recovers the authored value rather than an opaque string (§6.3).
property.Value = variant;
property.DataType = dataType;
property.ValueRank = valueRank;
@@ -395,10 +395,7 @@ private static void MaterializeMetadataEntries(
{
// A value of no representable type is carried as its invariant textual
// form rather than dropped — the §6.3 last resort, not a typed guess.
- property.Value = Variant.From(
- entry.Value as string ??
- Convert.ToString(entry.Value, CultureInfo.InvariantCulture) ??
- string.Empty);
+ property.Value = Variant.From(entry.Value.ToString());
property.DataType = Opc.Ua.DataTypeIds.String;
}
}
@@ -408,52 +405,45 @@ entry.Value as string ??
}
///
- /// Whether a metadata value is a nested dictionary (structured customData), and if so
- /// its entries. Only string-keyed dictionaries qualify; anything else is a leaf value.
- ///
- private static bool TryAsNestedDictionary(
- object? value, out IEnumerable> entries)
- {
- switch (value)
- {
- case IReadOnlyDictionary readOnly:
- entries = readOnly;
- return true;
- case IDictionary readWrite:
- entries = readWrite;
- return true;
- default:
- entries = Array.Empty>();
- return false;
- }
- }
-
- ///
- /// Chooses a Variant, DataType and ValueRank for a leaf metadata value from its CLR type,
+ /// Chooses a Variant, DataType and ValueRank for a leaf metadata value from its kind,
/// so a scalar keeps its exact type and an array keeps its (inferred) element type through
- /// the materialize→export round trip (§6.3). Returns false for a scalar whose type is
+ /// the materialize→export round trip (§6.3). Returns false for a value whose kind is
/// not representable, so the caller carries its textual form instead of guessing.
///
private static bool TryCoerceMetadataValue(
- object value, out Variant variant, out NodeId dataType, out int valueRank)
+ UsdValue value, out Variant variant, out NodeId dataType, out int valueRank)
{
valueRank = ValueRanks.Scalar;
- switch (value)
+ switch (value.Kind)
{
- case bool v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.Boolean; return true;
- case sbyte v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.SByte; return true;
- case byte v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.Byte; return true;
- case short v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.Int16; return true;
- case ushort v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.UInt16; return true;
- case int v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.Int32; return true;
- case uint v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.UInt32; return true;
- case long v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.Int64; return true;
- case ulong v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.UInt64; return true;
- case float v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.Float; return true;
- case double v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.Double; return true;
- case string v: variant = Variant.From(v); dataType = Opc.Ua.DataTypeIds.String; return true;
- case System.Collections.IEnumerable sequence:
- return TryCoerceMetadataArray(sequence, out variant, out dataType, out valueRank);
+ case UsdValueKind.Boolean:
+ value.TryGetBoolean(out bool b);
+ variant = Variant.From(b);
+ dataType = Opc.Ua.DataTypeIds.Boolean;
+ return true;
+ case UsdValueKind.Integer:
+ value.TryGetInteger(out long l);
+ variant = Variant.From(l);
+ dataType = Opc.Ua.DataTypeIds.Int64;
+ return true;
+ case UsdValueKind.Double:
+ value.TryGetDouble(out double d);
+ variant = Variant.From(d);
+ dataType = Opc.Ua.DataTypeIds.Double;
+ return true;
+ case UsdValueKind.String:
+ case UsdValueKind.Token:
+ case UsdValueKind.AssetPath:
+ case UsdValueKind.PathReference:
+ value.TryGetText(out string s);
+ variant = Variant.From(s);
+ dataType = Opc.Ua.DataTypeIds.String;
+ return true;
+ case UsdValueKind.Tuple:
+ case UsdValueKind.Array:
+ case UsdValueKind.Matrix:
+ value.TryGetItems(out ArrayOf items);
+ return TryCoerceMetadataArray(items, out variant, out dataType, out valueRank);
default:
variant = default;
dataType = Opc.Ua.DataTypeIds.String;
@@ -462,57 +452,52 @@ private static bool TryCoerceMetadataValue(
}
///
- /// Coerces a metadata array or list into a typed 1-D Variant, inferring the element type
- /// from the first non-null element. A homogeneous numeric or boolean array keeps its
+ /// Coerces a metadata array or tuple into a typed 1-D Variant, inferring the element type
+ /// from the first non-absent element. A homogeneous numeric or boolean sequence keeps its
/// element type; an empty or mixed sequence is carried as a string array so it round-trips
/// as a sequence rather than being dropped (§6.3, fail closed — no numeric guess).
///
private static bool TryCoerceMetadataArray(
- System.Collections.IEnumerable sequence,
+ ArrayOf sequence,
out Variant variant,
out NodeId dataType,
out int valueRank)
{
valueRank = ValueRanks.OneDimension;
- var items = new List
, ]");
+ UsdValue value = UsdaReader.ParseValue("[, ]");
- Assert.That(value, Is.EqualTo(new List
- {
- "/P/A.outputs:surface",
- "/P/B.outputs:surface",
- }));
+ UsdTestHelpers.AssertTextItems(value, "/P/A.outputs:surface", "/P/B.outputs:surface");
}
[Test]
@@ -163,14 +159,68 @@ public void EmptyConnectionList_ParsesToNoTargets()
Assert.That(attr.Connections, Is.Empty);
}
+ [Test]
+ public void QuotedRelationshipTarget_StillParsesAsATarget()
+ {
+ // A relationship target authored as a quoted string rather than a reference
+ // is read from the value's text rather than dropped.
+ UsdStage stage = UsdaReader.Parse(
+ Wrap(" rel material:binding = \"/P/Mat\""),
+ "Rel");
+
+ UsdRelationship rel = UsdTestHelpers.RequireRelationship(
+ UsdTestHelpers.RequirePrim(stage, "/P"), "material:binding");
+ Assert.That(rel.Targets, Is.EqualTo(new[] { "/P/Mat" }));
+ }
+
+ [Test]
+ public void QuotedRelationshipTargetList_ParsesEachTarget()
+ {
+ UsdStage stage = UsdaReader.Parse(
+ Wrap(" rel material:binding = [\"/P/MatA\", \"/P/MatB\"]"),
+ "Rel");
+
+ UsdRelationship rel = UsdTestHelpers.RequireRelationship(
+ UsdTestHelpers.RequirePrim(stage, "/P"), "material:binding");
+ Assert.That(rel.Targets, Is.EqualTo(new[] { "/P/MatA", "/P/MatB" }));
+ }
+
+ [Test]
+ public void QuotedConnectionTarget_StillParsesAsATarget()
+ {
+ // A target authored as a quoted string rather than a reference is still a
+ // target: the reader falls back to reading the value's text rather than dropping it.
+ UsdStage stage = UsdaReader.Parse(
+ Wrap(" token inputs:surface.connect = \"/P/A.outputs:surface\""),
+ "Conn");
+
+ UsdAttribute attr = AttributeNamed(stage, "/P", "inputs:surface");
+ Assert.That(attr.Connections, Is.EqualTo(new[] { "/P/A.outputs:surface" }));
+ }
+
+ [Test]
+ public void QuotedConnectionTargetList_ParsesEachTarget()
+ {
+ UsdStage stage = UsdaReader.Parse(
+ Wrap(" token inputs:surface.connect = [\"/P/A.outputs:surface\", \"/P/B.outputs:surface\"]"),
+ "Conn");
+
+ UsdAttribute attr = AttributeNamed(stage, "/P", "inputs:surface");
+ Assert.That(attr.Connections, Is.EqualTo(new[]
+ {
+ "/P/A.outputs:surface",
+ "/P/B.outputs:surface",
+ }));
+ }
+
// ---- Task 1.2: asset arrays ---------------------------------------------------
[Test]
public void ParseValue_AssetArray_ParsesEachElementUnwrapped()
{
- object? value = UsdaReader.ParseValue("[@./a.usda@, @./b.usda@]");
+ UsdValue value = UsdaReader.ParseValue("[@./a.usda@, @./b.usda@]");
- Assert.That(value, Is.EqualTo(new List { "./a.usda", "./b.usda" }));
+ UsdTestHelpers.AssertTextItems(value, "./a.usda", "./b.usda");
}
[Test]
@@ -181,7 +231,7 @@ public void AssetArrayAttribute_ParsesToUnwrappedPaths()
"Assets");
UsdAttribute attr = AttributeNamed(stage, "/P", "inputs:files");
- Assert.That(attr.Value, Is.EqualTo(new List { "./a.usda", "./b.usda" }));
+ UsdTestHelpers.AssertTextItems(attr.Value, "./a.usda", "./b.usda");
}
[Test]
@@ -201,14 +251,16 @@ public void WriterAssetArrayOutput_ReparsesToTheSamePaths()
var prim = new UsdPrim("P", "Xform");
prim.Attributes.Add(new UsdAttribute("inputs:files", "asset[]")
{
- Value = new List { "./a.usda", "./b.usda" },
+ Value = UsdTestHelpers.AssetArray("./a.usda", "./b.usda"),
});
stage.AddRootPrim(prim);
UsdStage reparsed = UsdaReader.Parse(UsdaWriter.Write(stage), stage.StageName);
UsdAttribute reparsedAttr = AttributeNamed(reparsed, "/P", "inputs:files");
- Assert.That(reparsedAttr.Value, Is.EqualTo(new List { "./a.usda", "./b.usda" }));
+ Assert.That(reparsedAttr.Value.TryGetArray(out ArrayOf paths), Is.True);
+ Assert.That(paths.ToArray()!.Select(p => p.TryGetAssetPath(out string text) ? text : string.Empty).ToArray(),
+ Is.EqualTo(new[] { "./a.usda", "./b.usda" }));
}
[Test]
@@ -219,7 +271,7 @@ public void SingleAssetScalar_StillParsesUnwrapped()
"Assets");
UsdAttribute attr = AttributeNamed(stage, "/P", "inputs:file");
- Assert.That(attr.Value, Is.EqualTo("./pump.usda"));
+ UsdTestHelpers.AssertAssetPath(attr.Value, "./pump.usda");
}
}
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/ConversionEmitPathTests.cs b/tests/Opc.Ua.OpenUsd.Tests/ConversionEmitPathTests.cs
index ced721ff6e..22f35d5d62 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/ConversionEmitPathTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/ConversionEmitPathTests.cs
@@ -76,7 +76,8 @@ private static void AssertRoundTripSignature(UsdStage expected)
public void TokenArray_FromDecoercedShape_EmitsBracketedDoubleQuotedElements()
{
// UsdValueCoercion.Decoerce hands the writer an object?[] for a token[] array.
- object? decoerced = UsdValueCoercion.Decoerce(new string[] { "xformOp:translate" });
+ UsdValue decoerced = UsdValueCoercion.Decoerce(
+ Variant.From((ArrayOf)new[] { "xformOp:translate" }));
string usda = EmitRootAttribute(
"Xform",
@@ -100,7 +101,7 @@ public void TokenArray_FromObjectArrayShape_EmitsBracketedArray()
new UsdAttribute("xformOpOrder", "token[]")
{
Variability = UsdVariabilityEnum.Uniform,
- Value = new object?[] { "xformOp:translate", "xformOp:scale" },
+ Value = UsdTestHelpers.TokenArray("xformOp:translate", "xformOp:scale"),
});
Assert.That(
@@ -113,7 +114,8 @@ public void Color3fArray_FromDecoercedMatrix_EmitsBracketedTupleRows()
{
// A color3f[] materializes as a rectangular matrix; Decoerce regroups it into per-tuple
// rows carried in an object?[]. The writer must emit "[(...)]", not "((...))".
- object? decoerced = UsdValueCoercion.Decoerce(new float[,] { { 0f, 0f, 1f } });
+ UsdValue decoerced = UsdValueCoercion.Decoerce(
+ Variant.From(new float[,] { { 0f, 0f, 1f } }.ToMatrixOf()));
string usda = EmitRootAttribute(
"Mesh",
@@ -130,11 +132,9 @@ public void Color3fArray_FromObjectArrayShape_MultipleRows_EmitsEachTuple()
"Mesh",
new UsdAttribute("primvars:displayColor", "color3f[]")
{
- Value = new object?[]
- {
- new object?[] { 0.0, 0.0, 1.0 },
- new object?[] { 1.0, 1.0, 0.0 },
- },
+ Value = UsdTestHelpers.Array(
+ UsdTestHelpers.NumberTuple(0.0, 0.0, 1.0),
+ UsdTestHelpers.NumberTuple(1.0, 1.0, 0.0)),
});
Assert.That(
@@ -145,7 +145,8 @@ public void Color3fArray_FromObjectArrayShape_MultipleRows_EmitsEachTuple()
[Test]
public void AssetArray_FromDecoercedShape_EmitsAtDelimitedElements()
{
- object? decoerced = UsdValueCoercion.Decoerce(new string[] { "./a.usda" });
+ UsdValue decoerced = UsdValueCoercion.Decoerce(
+ Variant.From((ArrayOf)new[] { "./a.usda" }));
string usda = EmitRootAttribute(
"Xform",
@@ -162,7 +163,8 @@ public void Double3Scalar_IsStillEmittedAsParenthesisedTuple_NotArray()
{
// The type-name keying must not turn a fixed-size math scalar into an array: a double3
// (ValueRank one-dimension, three components) is a single parenthesised tuple.
- object? decoerced = UsdValueCoercion.Decoerce(new double[] { 1.0, 2.0, 3.0 });
+ UsdValue decoerced = UsdValueCoercion.Decoerce(
+ Variant.From((ArrayOf)new[] { 1.0, 2.0, 3.0 }));
string usda = EmitRootAttribute(
"Xform",
@@ -185,18 +187,18 @@ public void FullEmitPath_ArraysAndConnection_RoundTripsUnderSignature()
mesh.Attributes.Add(new UsdAttribute("xformOpOrder", "token[]")
{
Variability = UsdVariabilityEnum.Uniform,
- Value = new object?[] { "xformOp:translate", "xformOp:scale" },
+ Value = UsdTestHelpers.TokenArray("xformOp:translate", "xformOp:scale"),
});
mesh.Attributes.Add(new UsdAttribute("primvars:displayColor", "color3f[]")
{
- Value = new object?[] { new object?[] { 0.0, 0.0, 1.0 } },
+ Value = UsdTestHelpers.Array(UsdTestHelpers.NumberTuple(0.0, 0.0, 1.0)),
});
mesh.Attributes.Add(new UsdAttribute("inputs:files", "asset[]")
{
- Value = new object?[] { "./a.usda", "./b.usda" },
+ Value = UsdTestHelpers.AssetArray("./a.usda", "./b.usda"),
});
- var surface = new UsdAttribute("outputs:surface", "token") { Value = "fallback" };
+ var surface = new UsdAttribute("outputs:surface", "token") { Value = UsdValue.FromString("fallback") };
surface.Connections.Add("/World/Shader.outputs:surface");
mesh.Attributes.Add(surface);
@@ -213,7 +215,7 @@ public void ValueAndConnect_OnOneAttribute_ReparseMergesIntoSingleAttribute()
{
var stage = new UsdStage("Conn") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- var attr = new UsdAttribute("inputs:surface", "token") { Value = "fallback" };
+ var attr = new UsdAttribute("inputs:surface", "token") { Value = UsdValue.FromString("fallback") };
attr.Connections.Add("/P/Shader.outputs:surface");
prim.Attributes.Add(attr);
stage.AddRootPrim(prim);
@@ -226,7 +228,7 @@ public void ValueAndConnect_OnOneAttribute_ReparseMergesIntoSingleAttribute()
Assert.That(matching, Has.Count.EqualTo(1),
"a value co-authored with a .connect must re-parse as one attribute, not two");
- Assert.That(matching[0].Value, Is.EqualTo("fallback"));
+ UsdTestHelpers.AssertText(matching[0].Value, "fallback");
Assert.That(matching[0].Connections, Is.EqualTo(new[] { "/P/Shader.outputs:surface" }));
}
@@ -237,10 +239,10 @@ public void ValueSamplesAndConnect_AllCoalesceOntoOneAttribute()
var prim = new UsdPrim("P", "Xform");
var attr = new UsdAttribute("xformOp:translate", "double3")
{
- Value = new object?[] { 1.0, 2.0, 3.0 },
+ Value = UsdTestHelpers.NumberTuple(1.0, 2.0, 3.0),
};
- attr.TimeSamples[0.0] = new object?[] { 1.0, 2.0, 3.0 };
- attr.TimeSamples[24.0] = new object?[] { 4.0, 5.0, 6.0 };
+ attr.TimeSamples[0.0] = UsdTestHelpers.NumberTuple(1.0, 2.0, 3.0);
+ attr.TimeSamples[24.0] = UsdTestHelpers.NumberTuple(4.0, 5.0, 6.0);
attr.Connections.Add("/P/Rig.outputs:translate");
prim.Attributes.Add(attr);
stage.AddRootPrim(prim);
@@ -255,7 +257,7 @@ public void ValueSamplesAndConnect_AllCoalesceOntoOneAttribute()
"value, time samples and .connect on one attribute must not split into duplicates");
Assert.That(matching[0].Connections, Is.EqualTo(new[] { "/P/Rig.outputs:translate" }));
Assert.That(matching[0].TimeSamples, Has.Count.EqualTo(2));
- Assert.That(matching[0].Value, Is.Not.Null);
+ Assert.That(matching[0].Value.IsNull, Is.False);
}
// ---- M-1: a known string/token attribute with a structured value fails closed ----
@@ -267,7 +269,9 @@ public void KnownTokenArray_WithTupleElements_FailsClosed_NeverPublishesClrTypeN
uint components = UsdValueTypeMap.ComponentCount("token[]");
bool ok = UsdValueCoercion.TryCoerce(
- new object?[] { new object?[] { 1L, 2L }, new object?[] { 3L, 4L } },
+ UsdTestHelpers.Array(
+ UsdTestHelpers.IntegerTuple(1L, 2L),
+ UsdTestHelpers.IntegerTuple(3L, 4L)),
mapping,
components,
out Variant result);
@@ -283,7 +287,7 @@ public void KnownStringScalar_WithTupleValue_FailsClosed()
uint components = UsdValueTypeMap.ComponentCount("string");
bool ok = UsdValueCoercion.TryCoerce(
- new object?[] { 1L, 2L }, mapping, components, out Variant _);
+ UsdTestHelpers.IntegerTuple(1L, 2L), mapping, components, out Variant _);
Assert.That(ok, Is.False);
}
@@ -294,7 +298,7 @@ public void KnownStringScalar_WithNumber_StillStringifiesFaithfully()
UsdValueTypeMapping mapping = UsdValueTypeMap.Map("string", null);
uint components = UsdValueTypeMap.ComponentCount("string");
- bool ok = UsdValueCoercion.TryCoerce(42L, mapping, components, out Variant result);
+ bool ok = UsdValueCoercion.TryCoerce(UsdValue.From(42L), mapping, components, out Variant result);
Assert.That(ok, Is.True);
Assert.That(result.TryGetValue(out string rendered), Is.True);
@@ -309,7 +313,7 @@ public void KnownTokenArray_WithStringElements_StillCoercesNormally()
uint components = UsdValueTypeMap.ComponentCount("token[]");
bool ok = UsdValueCoercion.TryCoerce(
- new object?[] { "a", "b" }, mapping, components, out Variant result);
+ UsdTestHelpers.TokenArray("a", "b"), mapping, components, out Variant result);
Assert.That(ok, Is.True);
Assert.That(result.TryGetValue(out ArrayOf tokens), Is.True);
@@ -341,7 +345,7 @@ public void CloseParenInsideQuotedMetadataString_DoesNotTruncateBlock()
// The metadata after the ')'-bearing string survived (the block was not truncated).
Assert.That(prim.Kind, Is.EqualTo(UsdPrimKindEnum.Component));
Assert.That(prim.Metadata.ContainsKey("comment"), Is.True);
- Assert.That(prim.Metadata["comment"], Is.EqualTo("note with ) paren and ( too"));
+ UsdTestHelpers.AssertString(prim.Metadata["comment"], "note with ) paren and ( too");
}
// ---- Defect 9: §6.3 custom prim metadata round-trips through Metadata ----
@@ -351,19 +355,19 @@ public void CustomScalarMetadata_RoundTripsThroughMetadataDictionary()
{
var stage = new UsdStage("Meta") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- prim.Metadata["displayName"] = "Pump Assembly";
- prim.Metadata["revision"] = 3L;
- prim.Metadata["approved"] = true;
- prim.Metadata["tolerance"] = 0.25;
+ prim.Metadata["displayName"] = UsdValue.FromString("Pump Assembly");
+ prim.Metadata["revision"] = UsdValue.From(3L);
+ prim.Metadata["approved"] = UsdValue.From(true);
+ prim.Metadata["tolerance"] = UsdValue.From(0.25);
stage.AddRootPrim(prim);
UsdStage reparsed = UsdaReader.Parse(UsdaWriter.Write(stage), stage.StageName);
UsdPrim prim2 = reparsed.Find("/P")!;
- Assert.That(prim2.Metadata["displayName"], Is.EqualTo("Pump Assembly"));
- Assert.That(prim2.Metadata["revision"], Is.EqualTo(3L));
- Assert.That(prim2.Metadata["approved"], Is.True);
- Assert.That(prim2.Metadata["tolerance"], Is.EqualTo(0.25));
+ UsdTestHelpers.AssertString(prim2.Metadata["displayName"], "Pump Assembly");
+ UsdTestHelpers.AssertInteger(prim2.Metadata["revision"], 3L);
+ UsdTestHelpers.AssertBoolean(prim2.Metadata["approved"], true);
+ UsdTestHelpers.AssertDouble(prim2.Metadata["tolerance"], 0.25);
}
[Test]
@@ -371,25 +375,24 @@ public void CustomNestedDictionaryMetadata_RoundTripsAsNestedDictionary()
{
var stage = new UsdStage("Meta") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- var custom = new Dictionary(StringComparer.Ordinal)
+ var custom = new Dictionary(StringComparer.Ordinal)
{
- ["author"] = "acme",
- ["weight"] = 12.5,
- ["count"] = 7L,
+ ["author"] = UsdValue.FromString("acme"),
+ ["weight"] = UsdValue.From(12.5),
+ ["count"] = UsdValue.From(7L),
};
- prim.Metadata["customData"] = custom;
+ prim.Metadata["customData"] = UsdValue.FromDictionary(custom);
stage.AddRootPrim(prim);
UsdStage reparsed = UsdaReader.Parse(UsdaWriter.Write(stage), stage.StageName);
UsdPrim prim2 = reparsed.Find("/P")!;
Assert.That(prim2.Metadata.ContainsKey("customData"), Is.True);
- Assert.That(prim2.Metadata["customData"], Is.InstanceOf>());
-
- var nested = (IDictionary)prim2.Metadata["customData"]!;
- Assert.That(nested["author"], Is.EqualTo("acme"));
- Assert.That(nested["weight"], Is.EqualTo(12.5));
- Assert.That(nested["count"], Is.EqualTo(7L));
+ Assert.That(prim2.Metadata["customData"].TryGetDictionary(out IReadOnlyDictionary nested),
+ Is.True);
+ UsdTestHelpers.AssertString(nested["author"], "acme");
+ UsdTestHelpers.AssertDouble(nested["weight"], 12.5);
+ UsdTestHelpers.AssertInteger(nested["count"], 7L);
}
[Test]
@@ -402,7 +405,7 @@ public void CustomMetadata_CoexistsWithWellKnownMetadata_WithoutPollution()
Documentation = "A documented prim",
};
prim.ApiSchemas.Add(new UsdApiSchema("PhysicsRigidBodyAPI"));
- prim.Metadata["displayName"] = "Widget";
+ prim.Metadata["displayName"] = UsdValue.FromString("Widget");
stage.AddRootPrim(prim);
UsdStage reparsed = UsdaReader.Parse(UsdaWriter.Write(stage), stage.StageName);
@@ -413,7 +416,7 @@ public void CustomMetadata_CoexistsWithWellKnownMetadata_WithoutPollution()
Assert.That(prim2.Documentation, Is.EqualTo("A documented prim"));
Assert.That(prim2.ApiSchemas.Select(a => a.SchemaName), Does.Contain("PhysicsRigidBodyAPI"));
Assert.That(prim2.Metadata.ContainsKey("displayName"), Is.True);
- Assert.That(prim2.Metadata["displayName"], Is.EqualTo("Widget"));
+ UsdTestHelpers.AssertString(prim2.Metadata["displayName"], "Widget");
Assert.That(prim2.Metadata.ContainsKey("kind"), Is.False);
Assert.That(prim2.Metadata.ContainsKey("doc"), Is.False);
Assert.That(prim2.Metadata.ContainsKey("apiSchemas"), Is.False);
@@ -424,14 +427,92 @@ public void CustomMetadata_WithCloseParenInStringValue_RoundTrips()
{
var stage = new UsdStage("Meta") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform") { Kind = UsdPrimKindEnum.Component };
- prim.Metadata["comment"] = "torque curve peaks at (n) then drops)";
+ prim.Metadata["comment"] = UsdValue.FromString("torque curve peaks at (n) then drops)");
stage.AddRootPrim(prim);
UsdStage reparsed = UsdaReader.Parse(UsdaWriter.Write(stage), stage.StageName);
UsdPrim prim2 = reparsed.Find("/P")!;
Assert.That(prim2.Kind, Is.EqualTo(UsdPrimKindEnum.Component));
- Assert.That(prim2.Metadata["comment"], Is.EqualTo("torque curve peaks at (n) then drops)"));
+ UsdTestHelpers.AssertString(prim2.Metadata["comment"], "torque curve peaks at (n) then drops)");
+ }
+
+ [Test]
+ public void Color3fArray_FlatComponentRun_IsRegroupedIntoTupleRows()
+ {
+ // A tuple-group base type handed back as a flat run of components must be regrouped
+ // per tuple, so a color3f[] still authors "[(r, g, b), …]" and not a flat list.
+ string usda = EmitRootAttribute(
+ "Mesh",
+ new UsdAttribute("primvars:displayColor", "color3f[]")
+ {
+ Value = UsdTestHelpers.NumberArray(1.0, 0.0, 0.0, 0.0, 1.0, 0.0),
+ });
+
+ Assert.That(
+ usda,
+ Does.Contain("color3f[] primvars:displayColor = [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0)]"));
+ }
+
+ [Test]
+ public void Color3fArray_GroupedRows_AreEmittedOnePerElement()
+ {
+ // Three already-grouped rows are themselves divisible by the group width, so the
+ // writer must notice the elements are sequences and not regroup them a second time.
+ string usda = EmitRootAttribute(
+ "Mesh",
+ new UsdAttribute("primvars:displayColor", "color3f[]")
+ {
+ Value = UsdTestHelpers.Array(
+ UsdTestHelpers.NumberTuple(1.0, 0.0, 0.0),
+ UsdTestHelpers.NumberTuple(0.0, 1.0, 0.0),
+ UsdTestHelpers.NumberTuple(0.0, 0.0, 1.0)),
+ });
+
+ Assert.That(
+ usda,
+ Does.Contain(
+ "color3f[] primvars:displayColor = "
+ + "[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)]"));
+ }
+
+ [Test]
+ public void OpaqueAbsentValue_RendersAsEmptyText()
+ {
+ bool rendered = UsdaWriter.TryRenderOpaqueValue(UsdValue.Null, out string text);
+
+ Assert.That(rendered, Is.True);
+ Assert.That(text, Is.Empty);
+ }
+
+ [Test]
+ public void CompositeMetadata_IsEmittedInUsdSyntax()
+ {
+ // A tuple/array metadata value has no scalar spelling, so it renders through the
+ // structured renderer rather than being published as a CLR type name.
+ var stage = new UsdStage("Meta") { DefaultPrim = "P" };
+ var prim = new UsdPrim("P", "Xform");
+ prim.Metadata["extent"] = UsdTestHelpers.NumberTuple(1.0, 2.0);
+ prim.Metadata["order"] = UsdTestHelpers.IntegerArray(1L, 2L);
+ prim.Metadata["source"] = UsdTestHelpers.Array(UsdValue.FromPathReference("/P/A"));
+ stage.AddRootPrim(prim);
+
+ string usda = UsdaWriter.Write(stage);
+
+ Assert.That(usda, Does.Contain("extent = (1.0, 2.0)"));
+ Assert.That(usda, Does.Contain("order = [1, 2]"));
+ Assert.That(usda, Does.Contain("source = ['/P/A']"));
+ Assert.That(usda, Does.Not.Contain("System."));
+ }
+
+ [Test]
+ public void BoolAttribute_IsEmittedAsALowerCaseLiteral()
+ {
+ string usda = EmitRootAttribute(
+ "Xform",
+ new UsdAttribute("visible", "bool") { Value = UsdValue.From(false) });
+
+ Assert.That(usda, Does.Contain("bool visible = false"));
}
}
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/ConversionFixTests.cs b/tests/Opc.Ua.OpenUsd.Tests/ConversionFixTests.cs
index a581bde783..65b271080b 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/ConversionFixTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/ConversionFixTests.cs
@@ -47,12 +47,14 @@ namespace Opc.Ua.OpenUsdScene.Tests
/// string.
/// H4 — the writer emits a co-authored default value together with every connection
/// target, not just the first connection (§5.4).
+ /// A uint64 above survives the export round trip as
+ /// its invariant decimal text instead of wrapping into a negative integer.
///
///
[TestFixture]
public class ConversionFixTests
{
- private static bool Coerce(string typeName, object? value, out Variant result)
+ private static bool Coerce(string typeName, UsdValue value, out Variant result)
{
UsdValueTypeMapping mapping = UsdValueTypeMap.Map(typeName, null);
uint components = UsdValueTypeMap.ComponentCount(typeName);
@@ -66,7 +68,7 @@ public void OpaqueTuple_RendersUsdSyntax_NotClrTypeName()
{
// color4f is not in the value-type table, so it is carried opaquely. A tuple must be
// rendered as "(...)" rather than the literal "System.Object[]".
- bool ok = Coerce("color4f", new object?[] { 0.1, 0.2, 0.3, 1.0 }, out Variant v);
+ bool ok = Coerce("color4f", UsdTestHelpers.NumberTuple(0.1, 0.2, 0.3, 1.0), out Variant v);
Assert.That(ok, Is.True);
Assert.That(v.TryGetValue(out string rendered), Is.True);
@@ -79,7 +81,7 @@ public void OpaqueArray_RendersUsdSyntax_NotClrTypeName()
{
// A [...] array is modelled as List; it must render as "[...]" rather than
// the literal "System.Collections.Generic.List`1[System.Object]".
- bool ok = Coerce("mvtype", new List { 1L, 2L, 3L }, out Variant v);
+ bool ok = Coerce("mvtype", UsdTestHelpers.IntegerArray(1L, 2L, 3L), out Variant v);
Assert.That(ok, Is.True);
Assert.That(v.TryGetValue(out string rendered), Is.True);
@@ -87,13 +89,26 @@ public void OpaqueArray_RendersUsdSyntax_NotClrTypeName()
Assert.That(rendered, Does.Not.Contain("System."));
}
+ [Test]
+ public void OpaqueBoolean_RendersItsUsdSpelling()
+ {
+ // A bool carried opaquely must author USD's "true"/"false", never a CLR spelling.
+ bool ok = Coerce("mvtype", UsdValue.From(true), out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TryGetValue(out string rendered), Is.True);
+ Assert.That(rendered, Is.EqualTo("true"));
+ }
+
[Test]
public void OpaqueNestedTuple_RendersRecursively()
{
// matrix2d authored as two nested 2-tuples must render every level.
bool ok = Coerce(
"matrix2d",
- new object?[] { new object?[] { 1L, 0L }, new object?[] { 0L, 1L } },
+ UsdTestHelpers.Tuple(
+ UsdTestHelpers.IntegerTuple(1L, 0L),
+ UsdTestHelpers.IntegerTuple(0L, 1L)),
out Variant v);
Assert.That(ok, Is.True);
@@ -104,7 +119,7 @@ public void OpaqueNestedTuple_RendersRecursively()
[Test]
public void OpaqueStringLeaf_IsQuotedInsideStructure()
{
- bool ok = Coerce("mvtype", new List { "a", "b" }, out Variant v);
+ bool ok = Coerce("mvtype", UsdTestHelpers.StringArray("a", "b"), out Variant v);
Assert.That(ok, Is.True);
Assert.That(v.TryGetValue(out string rendered), Is.True);
@@ -117,7 +132,11 @@ public void OpaqueValue_FailsClosed_WhenLeafCannotBeRendered()
// A leaf the writer cannot render faithfully must leave the value unresolved rather
// than publish a plausible-but-wrong string (fail closed).
bool ok = Coerce(
- "mvtype", new object?[] { 0.1, DateTime.UtcNow }, out Variant v);
+ "mvtype",
+ UsdTestHelpers.Array(
+ UsdValue.From(0.1),
+ UsdValue.FromDictionary(new Dictionary(StringComparer.Ordinal))),
+ out Variant v);
Assert.That(ok, Is.False);
Assert.That(v.TryGetValue(out string _), Is.False);
@@ -128,13 +147,11 @@ public void OpaqueValue_FailsClosed_WhenLeafCannotBeRendered()
[Test]
public void Matrix4d_NestedTuples_AreFlattenedAndHonoured()
{
- object?[] nested =
- {
- new object?[] { 1.0, 0.0, 0.0, 0.0 },
- new object?[] { 0.0, 1.0, 0.0, 0.0 },
- new object?[] { 0.0, 0.0, 1.0, 0.0 },
- new object?[] { 0.0, 0.0, 0.0, 1.0 },
- };
+ UsdValue nested = UsdTestHelpers.Tuple(
+ UsdTestHelpers.NumberTuple(1.0, 0.0, 0.0, 0.0),
+ UsdTestHelpers.NumberTuple(0.0, 1.0, 0.0, 0.0),
+ UsdTestHelpers.NumberTuple(0.0, 0.0, 1.0, 0.0),
+ UsdTestHelpers.NumberTuple(0.0, 0.0, 0.0, 1.0));
bool ok = Coerce("matrix4d", nested, out Variant v);
@@ -152,13 +169,11 @@ public void Matrix4d_NestedTuples_AreFlattenedAndHonoured()
[Test]
public void Matrix4d_AlreadyFlat_StillHonoured()
{
- object?[] flatAuthored =
- {
+ UsdValue flatAuthored = UsdTestHelpers.NumberTuple(
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
- 0.0, 0.0, 0.0, 1.0,
- };
+ 0.0, 0.0, 0.0, 1.0);
bool ok = Coerce("matrix4d", flatAuthored, out Variant v);
@@ -170,14 +185,12 @@ public void Matrix4d_AlreadyFlat_StillHonoured()
[Test]
public void Matrix4dArray_NestedTuples_AreFlattenedPerRow()
{
- object?[] identity =
- {
- new object?[] { 1.0, 0.0, 0.0, 0.0 },
- new object?[] { 0.0, 1.0, 0.0, 0.0 },
- new object?[] { 0.0, 0.0, 1.0, 0.0 },
- new object?[] { 0.0, 0.0, 0.0, 1.0 },
- };
- var value = new List { identity, identity };
+ UsdValue identity = UsdTestHelpers.Tuple(
+ UsdTestHelpers.NumberTuple(1.0, 0.0, 0.0, 0.0),
+ UsdTestHelpers.NumberTuple(0.0, 1.0, 0.0, 0.0),
+ UsdTestHelpers.NumberTuple(0.0, 0.0, 1.0, 0.0),
+ UsdTestHelpers.NumberTuple(0.0, 0.0, 0.0, 1.0));
+ UsdValue value = UsdTestHelpers.Array(identity, identity);
bool ok = Coerce("matrix4d[]", value, out Variant v);
@@ -192,11 +205,9 @@ public void Color3fArray_StaysGrouped_NotOverFlattened()
// The over-flatten guard: color3f[] is a sequence of 3-tuples. Flattening applies to
// the element shape only, so the outer array must keep two rows of three, not collapse
// to one flat run.
- var value = new List
- {
- new object?[] { 1f, 2f, 3f },
- new object?[] { 4f, 5f, 6f },
- };
+ UsdValue value = UsdTestHelpers.Array(
+ UsdTestHelpers.NumberTuple(1.0, 2.0, 3.0),
+ UsdTestHelpers.NumberTuple(4.0, 5.0, 6.0));
bool ok = Coerce("color3f[]", value, out Variant v);
@@ -210,7 +221,7 @@ public void WrongArityFixedType_StillFailsClosed_AfterFlatten()
{
// Flattening must not paper over a genuinely wrong arity: float3 with two components
// still cannot be honoured.
- bool ok = Coerce("float3", new object?[] { 1f, 2f }, out Variant v);
+ bool ok = Coerce("float3", UsdTestHelpers.NumberTuple(1.0, 2.0), out Variant v);
Assert.That(ok, Is.False);
Assert.That(v.TryGetValue(out ArrayOf _), Is.False);
@@ -223,7 +234,7 @@ public void AssetValue_IsEmittedWithAtDelimiters_NotQuoted()
{
var stage = new UsdStage("Assets") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- prim.Attributes.Add(new UsdAttribute("inputs:file", "asset") { Value = "./pump.usda" });
+ prim.Attributes.Add(new UsdAttribute("inputs:file", "asset") { Value = UsdValue.FromString("./pump.usda") });
stage.AddRootPrim(prim);
string usda = UsdaWriter.Write(stage);
@@ -240,7 +251,7 @@ public void AssetArray_EmitsEachElementWithAtDelimiters()
prim.Attributes.Add(
new UsdAttribute("inputs:files", "asset[]")
{
- Value = new List { "./a.usda", "./b.usda" },
+ Value = UsdTestHelpers.AssetArray("./a.usda", "./b.usda"),
});
stage.AddRootPrim(prim);
@@ -256,7 +267,7 @@ public void OpaqueCarriedValue_IsEmittedVerbatim_NotReQuoted()
// writer must emit it verbatim so the structured text survives the round trip.
var stage = new UsdStage("Opaque") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- prim.Attributes.Add(new UsdAttribute("extent", "color4f") { Value = "(0.1, 0.2, 0.3, 1.0)" });
+ prim.Attributes.Add(new UsdAttribute("extent", "color4f") { Value = UsdValue.FromString("(0.1, 0.2, 0.3, 1.0)") });
stage.AddRootPrim(prim);
string usda = UsdaWriter.Write(stage);
@@ -272,7 +283,7 @@ public void MultipleConnections_AreAllEmitted_WithCoAuthoredValue()
{
var stage = new UsdStage("Conn") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- var attr = new UsdAttribute("inputs:surface", "token") { Value = "fallback" };
+ var attr = new UsdAttribute("inputs:surface", "token") { Value = UsdValue.FromString("fallback") };
attr.Connections.Add("/P/A.outputs:surface");
attr.Connections.Add("/P/B.outputs:surface");
prim.Attributes.Add(attr);
@@ -303,5 +314,60 @@ public void SingleConnection_IsEmittedAsBarePathReference()
Assert.That(usda, Does.Contain(".connect = "));
Assert.That(usda, Does.Not.Contain("[]"));
}
+
+ // ---- A uint64 above long.MaxValue is preserved, never wrapped to a negative integer ----
+
+ [Test]
+ public void UInt64_WithinInt64Max_StaysIntegral()
+ {
+ UsdTestHelpers.AssertInteger(UsdValueCoercion.Decoerce(Variant.From(42UL)), 42L);
+ }
+
+ [Test]
+ public void UInt64_AboveInt64Max_IsPreservedAsInvariantText()
+ {
+ UsdValue decoerced = UsdValueCoercion.Decoerce(Variant.From(ulong.MaxValue));
+
+ // The unconditional cast this replaces authored "-1" for ulong.MaxValue.
+ UsdTestHelpers.AssertToken(decoerced, "18446744073709551615");
+ }
+
+ [Test]
+ public void UInt64_AboveInt64Max_RoundTripsBackToTheSameValue()
+ {
+ UsdValue decoerced = UsdValueCoercion.Decoerce(Variant.From(ulong.MaxValue));
+
+ bool ok = Coerce("uint64", decoerced, out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TryGetValue(out ulong recovered), Is.True);
+ Assert.That(recovered, Is.EqualTo(ulong.MaxValue));
+ }
+
+ [Test]
+ public void UInt64Array_AboveInt64Max_IsPreservedElementwise()
+ {
+ UsdValue decoerced = UsdValueCoercion.Decoerce(
+ Variant.From((ArrayOf)new[] { 1UL, ulong.MaxValue }));
+
+ Assert.That(decoerced.TryGetArray(out ArrayOf items), Is.True);
+ Assert.That(items.Count, Is.EqualTo(2));
+ UsdTestHelpers.AssertInteger(items[0], 1L);
+ UsdTestHelpers.AssertToken(items[1], "18446744073709551615");
+ }
+
+ [Test]
+ public void UInt64_AboveInt64Max_ReparsesFromItsAuthoredLiteral()
+ {
+ // The authored literal must neither overflow the reader's integral parse nor lose its
+ // digits to a double, so the coercion layer recovers the exact value.
+ UsdValue parsed = UsdaReader.ParseValue("18446744073709551615");
+
+ bool ok = Coerce("uint64", parsed, out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TryGetValue(out ulong recovered), Is.True);
+ Assert.That(recovered, Is.EqualTo(ulong.MaxValue));
+ }
}
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceAnchorCoercionTests.cs b/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceAnchorCoercionTests.cs
index 7463f46dc1..80091d70f1 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceAnchorCoercionTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceAnchorCoercionTests.cs
@@ -51,7 +51,9 @@ public sealed class GeoreferenceAnchorCoercionTests
public void AnAnchorAuthoredInMixedNumericTypesIsCoercedToDouble()
{
UsdPrim world = TypedGeoreference(
- latitude: 47.0f, longitude: -122L, height: 56);
+ latitude: UsdValue.From(47.0),
+ longitude: UsdValue.From(-122L),
+ height: UsdValue.From(56L));
List portable = PortableGeoreference(world);
@@ -65,7 +67,9 @@ public void AnAnchorAuthoredInMixedNumericTypesIsCoercedToDouble()
public void AnAnchorAuthoredAsInvariantTextIsParsed()
{
UsdPrim world = TypedGeoreference(
- latitude: "47.6062", longitude: "-122.3321", height: "56.0");
+ latitude: UsdValue.FromString("47.6062"),
+ longitude: UsdValue.FromString("-122.3321"),
+ height: UsdValue.FromString("56.0"));
List portable = PortableGeoreference(world);
@@ -78,7 +82,9 @@ public void AnAnchorAuthoredAsInvariantTextIsParsed()
public void AnAnchorWithAnUnparsableComponentPublishesNoPortableAnchor()
{
UsdPrim world = TypedGeoreference(
- latitude: "north", longitude: Longitude, height: 56.0);
+ latitude: UsdValue.FromString("north"),
+ longitude: UsdValue.From(Longitude),
+ height: UsdValue.From(56.0));
Assert.That(PortableGeoreference(world), Is.Empty);
}
@@ -88,9 +94,9 @@ public void AGlobeAnchorMissingItsHeightPublishesNoPortableAnchor()
{
var anchor = new UsdPrim("Anchor", "CesiumGlobeAnchorAPI");
anchor.Attributes.Add(
- new UsdAttribute("cesium:anchor:latitude", "double") { Value = Latitude });
+ new UsdAttribute("cesium:anchor:latitude", "double") { Value = UsdValue.From(Latitude) });
anchor.Attributes.Add(
- new UsdAttribute("cesium:anchor:longitude", "double") { Value = Longitude });
+ new UsdAttribute("cesium:anchor:longitude", "double") { Value = UsdValue.From(Longitude) });
var stage = new UsdStage("Test");
stage.AddRootPrim(anchor);
@@ -104,11 +110,11 @@ public void AGlobeAnchorWithACompleteAnchorPublishesThePortableAnchor()
{
var anchor = new UsdPrim("Anchor", "CesiumGlobeAnchorAPI");
anchor.Attributes.Add(
- new UsdAttribute("cesium:anchor:latitude", "double") { Value = Latitude });
+ new UsdAttribute("cesium:anchor:latitude", "double") { Value = UsdValue.From(Latitude) });
anchor.Attributes.Add(
- new UsdAttribute("cesium:anchor:longitude", "double") { Value = Longitude });
+ new UsdAttribute("cesium:anchor:longitude", "double") { Value = UsdValue.From(Longitude) });
anchor.Attributes.Add(
- new UsdAttribute("cesium:anchor:height", "double") { Value = 56.0 });
+ new UsdAttribute("cesium:anchor:height", "double") { Value = UsdValue.From(56.0) });
var stage = new UsdStage("Test");
stage.AddRootPrim(anchor);
@@ -120,7 +126,7 @@ public void AGlobeAnchorWithACompleteAnchorPublishesThePortableAnchor()
Assert.That(portable[0].Height!.Value, Is.EqualTo(56.0).Within(1e-12));
}
- private static UsdPrim TypedGeoreference(object latitude, object longitude, object height)
+ private static UsdPrim TypedGeoreference(UsdValue latitude, UsdValue longitude, UsdValue height)
{
var world = new UsdPrim("World", "CesiumGeoreferencePrim");
world.Attributes.Add(
diff --git a/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceTests.cs b/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceTests.cs
index 8da7fd832b..6d42e0157f 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceTests.cs
@@ -105,8 +105,8 @@ public void PartialAnchor_MissingLongitude_PublishesNoPortableAnchor()
var site = new UsdPrim("Site", "Xform");
site.ApiSchemas.Add(new UsdApiSchema("CesiumGeoreferencePrim"));
// Latitude only — a partial anchor would place the prim at a wrong position.
- site.Attributes.Add(new UsdAttribute("cesium:anchor:latitude", "double") { Value = Latitude });
- site.Attributes.Add(new UsdAttribute("cesium:anchor:height", "double") { Value = Height });
+ site.Attributes.Add(new UsdAttribute("cesium:anchor:latitude", "double") { Value = UsdValue.From(Latitude) });
+ site.Attributes.Add(new UsdAttribute("cesium:anchor:height", "double") { Value = UsdValue.From(Height) });
stage.AddRootPrim(site);
MaterializedScene ms = MaterializationHarness.Materialize(stage);
@@ -141,19 +141,19 @@ private static UsdStage AnnexBScene()
var site = new UsdPrim("Site", "Xform");
site.ApiSchemas.Add(new UsdApiSchema("CesiumGeoreferencePrim"));
- site.Attributes.Add(new UsdAttribute("cesium:anchor:latitude", "double") { Value = Latitude });
+ site.Attributes.Add(new UsdAttribute("cesium:anchor:latitude", "double") { Value = UsdValue.From(Latitude) });
site.Attributes.Add(
- new UsdAttribute("cesium:anchor:longitude", "double") { Value = Longitude });
- site.Attributes.Add(new UsdAttribute("cesium:anchor:height", "double") { Value = Height });
+ new UsdAttribute("cesium:anchor:longitude", "double") { Value = UsdValue.From(Longitude) });
+ site.Attributes.Add(new UsdAttribute("cesium:anchor:height", "double") { Value = UsdValue.From(Height) });
var anchor = new UsdPrim("Anchor", "Xform");
anchor.ApiSchemas.Add(new UsdApiSchema("CesiumGlobeAnchorAPI"));
anchor.Attributes.Add(
- new UsdAttribute("cesium:anchor:latitude", "double") { Value = AnchorLatitude });
+ new UsdAttribute("cesium:anchor:latitude", "double") { Value = UsdValue.From(AnchorLatitude) });
anchor.Attributes.Add(
- new UsdAttribute("cesium:anchor:longitude", "double") { Value = AnchorLongitude });
+ new UsdAttribute("cesium:anchor:longitude", "double") { Value = UsdValue.From(AnchorLongitude) });
anchor.Attributes.Add(
- new UsdAttribute("cesium:anchor:height", "double") { Value = AnchorHeight });
+ new UsdAttribute("cesium:anchor:height", "double") { Value = UsdValue.From(AnchorHeight) });
site.AddChild(anchor);
stage.AddRootPrim(site);
diff --git a/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceTypedPrimTests.cs b/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceTypedPrimTests.cs
index 4e53d548e4..a3bf85e5cd 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceTypedPrimTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/GeoreferenceTypedPrimTests.cs
@@ -101,9 +101,9 @@ public void TypedGlobeAnchorPrim_DualAuthorsPortableAnchor()
{
var stage = new UsdStage("Geo") { DefaultPrim = "Anchor" };
var anchor = new UsdPrim("Anchor", "CesiumGlobeAnchorAPI");
- anchor.Attributes.Add(new UsdAttribute("cesium:anchor:latitude", "double") { Value = Latitude });
- anchor.Attributes.Add(new UsdAttribute("cesium:anchor:longitude", "double") { Value = Longitude });
- anchor.Attributes.Add(new UsdAttribute("cesium:anchor:height", "double") { Value = Height });
+ anchor.Attributes.Add(new UsdAttribute("cesium:anchor:latitude", "double") { Value = UsdValue.From(Latitude) });
+ anchor.Attributes.Add(new UsdAttribute("cesium:anchor:longitude", "double") { Value = UsdValue.From(Longitude) });
+ anchor.Attributes.Add(new UsdAttribute("cesium:anchor:height", "double") { Value = UsdValue.From(Height) });
stage.AddRootPrim(anchor);
MaterializedScene ms = MaterializationHarness.Materialize(stage);
@@ -125,8 +125,8 @@ public void TypedGeoreferencePrim_MissingLongitude_PublishesNoPortableAnchor()
var world = new UsdPrim("World", "CesiumGeoreferencePrim");
// Longitude omitted — a partial anchor would place the prim at a wrong position, so
// the portable anchor is withheld entirely (fail closed).
- world.Attributes.Add(new UsdAttribute("cesium:anchor:latitude", "double") { Value = Latitude });
- world.Attributes.Add(new UsdAttribute("cesium:anchor:height", "double") { Value = Height });
+ world.Attributes.Add(new UsdAttribute("cesium:anchor:latitude", "double") { Value = UsdValue.From(Latitude) });
+ world.Attributes.Add(new UsdAttribute("cesium:anchor:height", "double") { Value = UsdValue.From(Height) });
stage.AddRootPrim(world);
MaterializedScene ms = MaterializationHarness.Materialize(stage);
@@ -157,9 +157,9 @@ private static UsdStage TypedGeoreferenceScene()
{
var stage = new UsdStage("Geo") { DefaultPrim = "World", UpAxis = "Z", MetersPerUnit = 1.0 };
var world = new UsdPrim("World", "CesiumGeoreferencePrim");
- world.Attributes.Add(new UsdAttribute("cesium:anchor:latitude", "double") { Value = Latitude });
- world.Attributes.Add(new UsdAttribute("cesium:anchor:longitude", "double") { Value = Longitude });
- world.Attributes.Add(new UsdAttribute("cesium:anchor:height", "double") { Value = Height });
+ world.Attributes.Add(new UsdAttribute("cesium:anchor:latitude", "double") { Value = UsdValue.From(Latitude) });
+ world.Attributes.Add(new UsdAttribute("cesium:anchor:longitude", "double") { Value = UsdValue.From(Longitude) });
+ world.Attributes.Add(new UsdAttribute("cesium:anchor:height", "double") { Value = UsdValue.From(Height) });
stage.AddRootPrim(world);
return stage;
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/MaterializerFallbackTests.cs b/tests/Opc.Ua.OpenUsd.Tests/MaterializerFallbackTests.cs
index e06ad30ae0..2cde51d592 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/MaterializerFallbackTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/MaterializerFallbackTests.cs
@@ -133,7 +133,8 @@ public void WrongArityFixedMathType_LeavesValueUnset_ButKeepsType()
var stage = new UsdStage("Arity") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
// float3 declares three components; only two are authored — it cannot be honoured.
- prim.Attributes.Add(new UsdAttribute("badVec", "float3") { Value = new object[] { 1f, 2f } });
+ prim.Attributes.Add(
+ new UsdAttribute("badVec", "float3") { Value = UsdTestHelpers.NumberTuple(1.0, 2.0) });
stage.AddRootPrim(prim);
MaterializedScene ms = MaterializationHarness.Materialize(stage);
@@ -153,7 +154,7 @@ public void CorrectArityFixedMathType_SetsValue()
var stage = new UsdStage("Arity") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
prim.Attributes.Add(
- new UsdAttribute("goodVec", "float3") { Value = new object[] { 1f, 2f, 3f } });
+ new UsdAttribute("goodVec", "float3") { Value = UsdTestHelpers.NumberTuple(1.0, 2.0, 3.0) });
stage.AddRootPrim(prim);
MaterializedScene ms = MaterializationHarness.Materialize(stage);
@@ -211,8 +212,8 @@ public void DuplicateAttributeNames_CollapseToSingleVariable_LastWins()
{
var stage = new UsdStage("Dup") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- prim.Attributes.Add(new UsdAttribute("dup", "double") { Value = 1.0 });
- prim.Attributes.Add(new UsdAttribute("dup", "token") { Value = "x" });
+ prim.Attributes.Add(new UsdAttribute("dup", "double") { Value = UsdValue.From(1.0) });
+ prim.Attributes.Add(new UsdAttribute("dup", "token") { Value = UsdValue.FromString("x") });
stage.AddRootPrim(prim);
MaterializedScene ms = MaterializationHarness.Materialize(stage);
@@ -231,7 +232,7 @@ public void OddlyNamespacedAttribute_SplitsOnLastColon()
{
var stage = new UsdStage("Odd") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- prim.Attributes.Add(new UsdAttribute("a:b:c", "double") { Value = 1.0 });
+ prim.Attributes.Add(new UsdAttribute("a:b:c", "double") { Value = UsdValue.From(1.0) });
stage.AddRootPrim(prim);
MaterializedScene ms = MaterializationHarness.Materialize(stage);
diff --git a/tests/Opc.Ua.OpenUsd.Tests/MaterializerTests.cs b/tests/Opc.Ua.OpenUsd.Tests/MaterializerTests.cs
index 80d48246e8..de23ecd7c2 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/MaterializerTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/MaterializerTests.cs
@@ -397,7 +397,7 @@ private static UsdStage LiveStage()
{
var stage = new UsdStage("Live") { DefaultPrim = "Rig" };
var rig = new UsdPrim("Rig", "Xform");
- rig.Attributes.Add(new UsdAttribute("speed", "double") { Value = 0.0, Live = true });
+ rig.Attributes.Add(new UsdAttribute("speed", "double") { Value = UsdValue.From(0.0), Live = true });
stage.AddRootPrim(rig);
return stage;
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/MetadataCoercionTests.cs b/tests/Opc.Ua.OpenUsd.Tests/MetadataCoercionTests.cs
index b19c59b21d..28e13492a1 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/MetadataCoercionTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/MetadataCoercionTests.cs
@@ -36,10 +36,9 @@
namespace Opc.Ua.OpenUsdScene.Tests
{
///
- /// Unit tests for the §6.3 metadata coercion rules of the materializer: every CLR scalar
+ /// Unit tests for the §6.3 metadata coercion rules of the materializer: every USD scalar
/// kind keeps its own OPC UA DataType, a homogeneous sequence keeps its element type, and
- /// anything the mapping cannot represent falls back to its invariant textual form rather
- /// than being dropped or guessed at.
+ /// mixed sequences fall back to their textual form rather than being dropped or guessed at.
///
[TestFixture]
[Category("OpenUsd")]
@@ -49,8 +48,8 @@ public sealed class MetadataCoercionTests
public void AnEntryWithAnEmptyKeyIsSkipped()
{
var prim = new UsdPrim("Cube", "Cube");
- prim.Metadata[string.Empty] = "dropped";
- prim.Metadata["kept"] = "value";
+ prim.Metadata[string.Empty] = UsdValue.FromString("dropped");
+ prim.Metadata["kept"] = UsdValue.FromString("value");
List properties = MetadataProperties(prim);
@@ -62,7 +61,7 @@ public void AnEntryWithAnEmptyKeyIsSkipped()
public void AnEntryWithANullValueBecomesAValuelessProperty()
{
var prim = new UsdPrim("Cube", "Cube");
- prim.Metadata["missing"] = null;
+ prim.Metadata["missing"] = UsdValue.Null;
PropertyState property = SingleMetadataProperty(prim);
@@ -70,14 +69,8 @@ public void AnEntryWithANullValueBecomesAValuelessProperty()
Assert.That(property.Value.IsNull, Is.True);
}
- [TestCase((sbyte)-8, Opc.Ua.DataTypes.SByte)]
- [TestCase((byte)8, Opc.Ua.DataTypes.Byte)]
- [TestCase((short)-16, Opc.Ua.DataTypes.Int16)]
- [TestCase((ushort)16, Opc.Ua.DataTypes.UInt16)]
- [TestCase(32u, Opc.Ua.DataTypes.UInt32)]
- [TestCase(64UL, Opc.Ua.DataTypes.UInt64)]
- [TestCase(1.5f, Opc.Ua.DataTypes.Float)]
- public void AScalarKeepsItsOwnDataType(object value, uint expectedDataType)
+ [TestCaseSource(nameof(ScalarCases))]
+ public void AScalarKeepsItsOwnDataType(UsdValue value, uint expectedDataType)
{
var prim = new UsdPrim("Cube", "Cube");
prim.Metadata["scalar"] = value;
@@ -92,11 +85,11 @@ public void AScalarKeepsItsOwnDataType(object value, uint expectedDataType)
}
[Test]
- public void AnUnrepresentableScalarIsCarriedAsInvariantText()
+ public void ATextScalarIsCarriedAsText()
{
var prim = new UsdPrim("Cube", "Cube");
var stamp = new DateTime(2026, 3, 4, 5, 6, 7, DateTimeKind.Utc);
- prim.Metadata["stamp"] = new UnprintableMetadata(stamp);
+ prim.Metadata["stamp"] = UsdValue.FromString(stamp.ToString("O"));
PropertyState property = SingleMetadataProperty(prim);
@@ -106,11 +99,14 @@ public void AnUnrepresentableScalarIsCarriedAsInvariantText()
}
[Test]
- public void ANestedWriteOnlyDictionaryBecomesASubFolder()
+ public void ANestedDictionaryBecomesASubFolder()
{
var prim = new UsdPrim("Cube", "Cube");
- var nested = new WriteOnlyMetadataDictionary { ["vendor"] = "Contoso" };
- prim.Metadata["customData"] = nested;
+ var nested = new Dictionary(StringComparer.Ordinal)
+ {
+ ["vendor"] = UsdValue.FromString("Contoso")
+ };
+ prim.Metadata["customData"] = UsdValue.FromDictionary(nested);
MaterializedScene scene = Materialize(prim);
FolderState metadata = MetadataFolder(scene);
@@ -129,7 +125,12 @@ public void ANestedWriteOnlyDictionaryBecomesASubFolder()
public void ABooleanSequenceKeepsItsElementType()
{
PropertyState property = SingleMetadataProperty(
- WithMetadata("flags", new[] { true, false, true }));
+ WithMetadata(
+ "flags",
+ UsdTestHelpers.Array(
+ UsdValue.From(true),
+ UsdValue.From(false),
+ UsdValue.From(true))));
Assert.That(property.ValueRank, Is.EqualTo(ValueRanks.OneDimension));
Assert.That(property.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Boolean));
@@ -142,7 +143,7 @@ public void ABooleanSequenceKeepsItsElementType()
public void ALongSequenceKeepsItsElementType()
{
PropertyState property = SingleMetadataProperty(
- WithMetadata("ticks", new[] { 9_000_000_000L, 2L }));
+ WithMetadata("ticks", UsdTestHelpers.IntegerArray(9_000_000_000L, 2L)));
Assert.That(property.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Int64));
Assert.That(property.Value.TryGetValue(out ArrayOf values), Is.True);
@@ -153,7 +154,7 @@ public void ALongSequenceKeepsItsElementType()
public void AnUnsignedSequenceWidensToInt64()
{
PropertyState property = SingleMetadataProperty(
- WithMetadata("ids", new[] { 1u, 2u }));
+ WithMetadata("ids", UsdTestHelpers.IntegerArray(1L, 2L)));
Assert.That(property.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Int64));
Assert.That(property.Value.TryGetValue(out ArrayOf values), Is.True);
@@ -164,7 +165,7 @@ public void AnUnsignedSequenceWidensToInt64()
public void AFloatingPointSequenceKeepsItsElementType()
{
PropertyState property = SingleMetadataProperty(
- WithMetadata("scales", new[] { 1.5f, 2.5f }));
+ WithMetadata("scales", UsdTestHelpers.NumberArray(1.5, 2.5)));
Assert.That(property.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Double));
Assert.That(property.Value.TryGetValue(out ArrayOf values), Is.True);
@@ -175,7 +176,7 @@ public void AFloatingPointSequenceKeepsItsElementType()
public void AnEmptySequenceFallsBackToAStringSequence()
{
PropertyState property = SingleMetadataProperty(
- WithMetadata("tags", Array.Empty()));
+ WithMetadata("tags", UsdValue.FromArray(ArrayOf.Empty)));
Assert.That(property.ValueRank, Is.EqualTo(ValueRanks.OneDimension));
Assert.That(property.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.String));
@@ -187,7 +188,7 @@ public void AnEmptySequenceFallsBackToAStringSequence()
public void ASequenceHoldingANullElementFallsBackToText()
{
PropertyState property = SingleMetadataProperty(
- WithMetadata("order", new object?[] { 3, null }));
+ WithMetadata("order", UsdTestHelpers.Array(UsdValue.From(3L), UsdValue.Null)));
Assert.That(property.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.String));
Assert.That(property.Value.TryGetValue(out ArrayOf values), Is.True);
@@ -200,14 +201,24 @@ public void ASequenceHoldingANullElementFallsBackToText()
public void ASequenceWithAnUnconvertibleElementFallsBackToText()
{
PropertyState property = SingleMetadataProperty(
- WithMetadata("flags", new object[] { true, "not-a-boolean" }));
+ WithMetadata(
+ "flags",
+ UsdTestHelpers.Array(UsdValue.From(true), UsdValue.FromString("not-a-boolean"))));
Assert.That(property.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.String));
Assert.That(property.Value.TryGetValue(out ArrayOf values), Is.True);
Assert.That(values[1], Is.EqualTo("not-a-boolean"));
}
- private static UsdPrim WithMetadata(string key, object value)
+ private static IEnumerable ScalarCases()
+ {
+ yield return new TestCaseData(UsdValue.From(false), Opc.Ua.DataTypes.Boolean);
+ yield return new TestCaseData(UsdValue.From(32L), Opc.Ua.DataTypes.Int64);
+ yield return new TestCaseData(UsdValue.From(1.5), Opc.Ua.DataTypes.Double);
+ yield return new TestCaseData(UsdValue.FromString("text"), Opc.Ua.DataTypes.String);
+ }
+
+ private static UsdPrim WithMetadata(string key, UsdValue value)
{
var prim = new UsdPrim("Cube", "Cube");
prim.Metadata[key] = value;
@@ -245,102 +256,5 @@ private static PropertyState SingleMetadataProperty(UsdPrim prim)
return properties[0];
}
- ///
- /// A metadata value of a kind the §6.3 mapping does not represent, so the materializer
- /// must fall back to its invariant textual form.
- ///
- private sealed class UnprintableMetadata
- {
- private readonly DateTime m_stamp;
-
- public UnprintableMetadata(DateTime stamp)
- {
- m_stamp = stamp;
- }
-
- public override string ToString()
- {
- return m_stamp.ToString("O");
- }
- }
-
- ///
- /// A dictionary that implements only the mutable dictionary contract, so the nested
- /// customData detection has to fall through to its second, read-write case.
- ///
- private sealed class WriteOnlyMetadataDictionary : IDictionary
- {
- private readonly Dictionary m_inner =
- new Dictionary(StringComparer.Ordinal);
-
- public object? this[string key]
- {
- get => m_inner[key];
- set => m_inner[key] = value;
- }
-
- public ICollection Keys => m_inner.Keys;
-
- public ICollection Values => m_inner.Values;
-
- public int Count => m_inner.Count;
-
- public bool IsReadOnly => false;
-
- public void Add(string key, object? value)
- {
- m_inner.Add(key, value);
- }
-
- public void Add(KeyValuePair item)
- {
- m_inner.Add(item.Key, item.Value);
- }
-
- public void Clear()
- {
- m_inner.Clear();
- }
-
- public bool Contains(KeyValuePair item)
- {
- return m_inner.TryGetValue(item.Key, out object? found) && Equals(found, item.Value);
- }
-
- public bool ContainsKey(string key)
- {
- return m_inner.ContainsKey(key);
- }
-
- public void CopyTo(KeyValuePair[] array, int arrayIndex)
- {
- ((ICollection>)m_inner).CopyTo(array, arrayIndex);
- }
-
- public IEnumerator> GetEnumerator()
- {
- return m_inner.GetEnumerator();
- }
-
- public bool Remove(string key)
- {
- return m_inner.Remove(key);
- }
-
- public bool Remove(KeyValuePair item)
- {
- return m_inner.Remove(item.Key);
- }
-
- public bool TryGetValue(string key, out object? value)
- {
- return m_inner.TryGetValue(key, out value);
- }
-
- IEnumerator IEnumerable.GetEnumerator()
- {
- return m_inner.GetEnumerator();
- }
- }
}
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/OptionalMemberMaterializationTests.cs b/tests/Opc.Ua.OpenUsd.Tests/OptionalMemberMaterializationTests.cs
index bf65f476f0..702d0a94be 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/OptionalMemberMaterializationTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/OptionalMemberMaterializationTests.cs
@@ -167,7 +167,7 @@ public void ACustomRelationshipAuthorsTheCustomFlag()
public void ARelationshipTargetingAnAttributeResolvesToThatAttributeNode()
{
var target = new UsdPrim("Mesh", "Mesh");
- target.Attributes.Add(new UsdAttribute("size", "double") { Value = 2.0 });
+ target.Attributes.Add(new UsdAttribute("size", "double") { Value = UsdValue.From(2.0) });
var source = new UsdPrim("Binding", "Scope");
var relationship = new UsdRelationship("drivenBy");
relationship.Targets.Add("/Mesh.size");
diff --git a/tests/Opc.Ua.OpenUsd.Tests/PrimMetadataMaterializationTests.cs b/tests/Opc.Ua.OpenUsd.Tests/PrimMetadataMaterializationTests.cs
index 6afd18753a..504c3b220f 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/PrimMetadataMaterializationTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/PrimMetadataMaterializationTests.cs
@@ -29,7 +29,9 @@
using System;
using System.Collections.Generic;
+using System.Linq;
using NUnit.Framework;
+using Opc.Ua;
using Opc.Ua.OpenUsdScene.Scene;
using Opc.Ua.OpenUsdScene.Server;
@@ -51,24 +53,24 @@ public void Metadata_ScalarTypes_RoundTrip()
{
var stage = new UsdStage("S") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- prim.Metadata["author"] = "Ada";
- prim.Metadata["visible"] = true;
- prim.Metadata["count"] = 7;
- prim.Metadata["huge"] = 9_000_000_000L;
- prim.Metadata["scale"] = 2.5;
+ prim.Metadata["author"] = UsdValue.FromString("Ada");
+ prim.Metadata["visible"] = UsdValue.From(true);
+ prim.Metadata["count"] = UsdValue.From(7L);
+ prim.Metadata["huge"] = UsdValue.From(9_000_000_000L);
+ prim.Metadata["scale"] = UsdValue.From(2.5);
stage.AddRootPrim(prim);
MaterializedScene ms = MaterializationHarness.Materialize(stage);
UsdStage exported = ms.Context.ExportUsdStage(ms.Result);
- IDictionary md = PrimOf(exported, "P").Metadata;
+ IDictionary md = PrimOf(exported, "P").Metadata;
Assert.Multiple(() =>
{
- Assert.That(md["author"], Is.EqualTo("Ada"));
- Assert.That(md["visible"], Is.True);
- Assert.That(md["count"], Is.EqualTo(7));
- Assert.That(md["huge"], Is.EqualTo(9_000_000_000L));
- Assert.That(md["scale"], Is.EqualTo(2.5));
+ UsdTestHelpers.AssertString(md["author"], "Ada");
+ UsdTestHelpers.AssertBoolean(md["visible"], true);
+ UsdTestHelpers.AssertInteger(md["count"], 7L);
+ UsdTestHelpers.AssertInteger(md["huge"], 9_000_000_000L);
+ UsdTestHelpers.AssertDouble(md["scale"], 2.5);
});
}
@@ -80,10 +82,10 @@ public void Metadata_IsMaterializedTyped_NotStringified()
// authored kind rather than an opaque string.
var stage = new UsdStage("S") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- prim.Metadata["count"] = 7;
- prim.Metadata["scale"] = 2.5;
- prim.Metadata["visible"] = true;
- prim.Metadata["author"] = "Ada";
+ prim.Metadata["count"] = UsdValue.From(7L);
+ prim.Metadata["scale"] = UsdValue.From(2.5);
+ prim.Metadata["visible"] = UsdValue.From(true);
+ prim.Metadata["author"] = UsdValue.FromString("Ada");
stage.AddRootPrim(prim);
MaterializedScene ms = MaterializationHarness.Materialize(stage);
@@ -91,8 +93,8 @@ public void Metadata_IsMaterializedTyped_NotStringified()
Assert.Multiple(() =>
{
PropertyState count = MetaProperty(ms, "/P", "count");
- Assert.That(count.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Int32));
- Assert.That(count.Value.AsBoxedObject(), Is.EqualTo(7));
+ Assert.That(count.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Int64));
+ Assert.That(count.Value.AsBoxedObject(), Is.EqualTo(7L));
PropertyState scale = MetaProperty(ms, "/P", "scale");
Assert.That(scale.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Double));
@@ -113,19 +115,21 @@ public void Metadata_IntArray_RoundTrips_AsTypedSequence()
{
var stage = new UsdStage("S") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- prim.Metadata["order"] = new int[] { 3, 1, 2 };
+ prim.Metadata["order"] = UsdTestHelpers.IntegerArray(3L, 1L, 2L);
stage.AddRootPrim(prim);
MaterializedScene ms = MaterializationHarness.Materialize(stage);
- // The materialized value is a typed one-dimensional Int32 array, not text.
+ // The materialized value is a typed one-dimensional Int64 array, not text.
PropertyState order = MetaProperty(ms, "/P", "order");
- Assert.That(order.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Int32));
+ Assert.That(order.DataType, Is.EqualTo(Opc.Ua.DataTypeIds.Int64));
Assert.That(order.ValueRank, Is.EqualTo(ValueRanks.OneDimension));
UsdStage exported = ms.Context.ExportUsdStage(ms.Result);
- IDictionary md = PrimOf(exported, "P").Metadata;
- Assert.That(md["order"], Is.EqualTo(new[] { 3, 1, 2 }),
+ IDictionary md = PrimOf(exported, "P").Metadata;
+ Assert.That(md["order"].TryGetArray(out ArrayOf values), Is.True);
+ Assert.That(values.ToArray()!.Select(v => v.TryGetInteger(out long integer) ? integer : 0L).ToArray(),
+ Is.EqualTo(new[] { 3L, 1L, 2L }),
"The array must round-trip element-wise, preserving order.");
}
@@ -136,16 +140,17 @@ public void Metadata_NestedDictionary_RoundTrips()
{
var stage = new UsdStage("S") { DefaultPrim = "P" };
var prim = new UsdPrim("P", "Xform");
- var customData = new Dictionary(StringComparer.Ordinal)
+ var revisionData = new Dictionary(StringComparer.Ordinal)
{
- ["author"] = "Ada",
- ["revision"] = new Dictionary(StringComparer.Ordinal)
- {
- ["major"] = 1,
- ["minor"] = 2
- }
+ ["major"] = UsdValue.From(1L),
+ ["minor"] = UsdValue.From(2L)
+ };
+ var customData = new Dictionary(StringComparer.Ordinal)
+ {
+ ["author"] = UsdValue.FromString("Ada"),
+ ["revision"] = UsdValue.FromDictionary(revisionData)
};
- prim.Metadata["customData"] = customData;
+ prim.Metadata["customData"] = UsdValue.FromDictionary(customData);
stage.AddRootPrim(prim);
MaterializedScene ms = MaterializationHarness.Materialize(stage);
@@ -160,18 +165,18 @@ public void Metadata_NestedDictionary_RoundTrips()
"The nested dictionary must materialize as one nested Metadata sub-folder.");
UsdStage exported = ms.Context.ExportUsdStage(ms.Result);
- IDictionary md = PrimOf(exported, "P").Metadata;
+ IDictionary md = PrimOf(exported, "P").Metadata;
- Assert.That(md["customData"], Is.InstanceOf>());
- var exportedCustom = (IDictionary)md["customData"]!;
- Assert.That(exportedCustom["author"], Is.EqualTo("Ada"));
+ Assert.That(md["customData"].TryGetDictionary(out IReadOnlyDictionary exportedCustom),
+ Is.True);
+ UsdTestHelpers.AssertString(exportedCustom["author"], "Ada");
- Assert.That(exportedCustom["revision"], Is.InstanceOf>());
- var revision = (IDictionary)exportedCustom["revision"]!;
+ Assert.That(exportedCustom["revision"].TryGetDictionary(out IReadOnlyDictionary revision),
+ Is.True);
Assert.Multiple(() =>
{
- Assert.That(revision["major"], Is.EqualTo(1));
- Assert.That(revision["minor"], Is.EqualTo(2));
+ UsdTestHelpers.AssertInteger(revision["major"], 1L);
+ UsdTestHelpers.AssertInteger(revision["minor"], 2L);
});
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/RobotAssetContractTests.cs b/tests/Opc.Ua.OpenUsd.Tests/RobotAssetContractTests.cs
index a6a6fa4ea0..374b3a73f9 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/RobotAssetContractTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/RobotAssetContractTests.cs
@@ -31,6 +31,7 @@
using System.IO;
using System.Linq;
using NUnit.Framework;
+using Opc.Ua;
using Opc.Ua.OpenUsdScene.Conversion;
using Opc.Ua.OpenUsdScene.Scene;
@@ -77,12 +78,26 @@ private static string SamplePath(string name) =>
private static string Flatten(object? value) => value switch
{
null => string.Empty,
+ UsdValue usdValue => Flatten(usdValue),
string text => text,
System.Collections.IEnumerable items =>
string.Join(",", items.Cast().Select(Flatten)),
_ => value.ToString() ?? string.Empty
};
+ private static string Flatten(UsdValue value)
+ {
+ if (value.TryGetText(out string text))
+ {
+ return text;
+ }
+ if (value.TryGetItems(out ArrayOf items))
+ {
+ return string.Join(",", items.ToArray()!.Select(Flatten));
+ }
+ return value.ToString();
+ }
+
[Test]
[TestCase("robot.usda", "Robot")]
[TestCase("tool.usda", "Gripper")]
@@ -143,8 +158,7 @@ public void RobotExposesTheEmergencyStopWarningVisibility()
warning!.Attributes.FirstOrDefault(a => a.Name == "visibility");
Assert.That(visibility, Is.Not.Null, "/Robot/Warning has no visibility attribute.");
Assert.That(visibility!.TypeName, Is.EqualTo("token"));
- Assert.That(visibility.Value, Is.EqualTo("invisible"),
- "The warning halo must start hidden.");
+ UsdTestHelpers.AssertText(visibility.Value, "invisible");
}
[Test]
@@ -182,8 +196,7 @@ public void CellExposesTheSafetyBeaconVisibility()
UsdAttribute? visibility =
beacon!.Attributes.FirstOrDefault(a => a.Name == "visibility");
Assert.That(visibility, Is.Not.Null, "/Cell/SafetyBeacon has no visibility attribute.");
- Assert.That(visibility!.Value, Is.EqualTo("invisible"),
- "The beacon must start hidden.");
+ UsdTestHelpers.AssertText(visibility!.Value, "invisible");
}
[Test]
@@ -255,9 +268,10 @@ private static double TranslateX(UsdStage stage, string primPath)
prim!.Attributes.FirstOrDefault(a => a.Name == "xformOp:translate");
Assert.That(translate, Is.Not.Null, $"{primPath} has no xformOp:translate.");
- object?[]? components = translate!.Value as object?[];
- Assert.That(components, Is.Not.Null.And.Length.EqualTo(3));
- return Convert.ToDouble(components![0], System.Globalization.CultureInfo.InvariantCulture);
+ Assert.That(translate!.Value.TryGetItems(out ArrayOf components), Is.True);
+ Assert.That(components.Count, Is.EqualTo(3));
+ Assert.That(components[0].TryGetNumber(out double x), Is.True);
+ return x;
}
}
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/SceneFidelityRoundTripTests.cs b/tests/Opc.Ua.OpenUsd.Tests/SceneFidelityRoundTripTests.cs
index 3efa5f47ce..b8aa8cec3a 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/SceneFidelityRoundTripTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/SceneFidelityRoundTripTests.cs
@@ -124,9 +124,9 @@ public void ExportedScene_RecoversSamples_AndBranches_Structurally()
// Time samples: the default and every sample survive the address-space round trip.
UsdAttribute spin = prim.Attributes.Single(a => a.Name == "spin");
- Assert.That(spin.Value, Is.EqualTo(5.0));
+ UsdTestHelpers.AssertDouble(spin.Value, 5.0);
Assert.That(spin.TimeSamples.Keys, Is.EqualTo(new[] { 0.0, 24.0, 48.0 }));
- Assert.That(spin.TimeSamples[48.0], Is.EqualTo(180.0));
+ UsdTestHelpers.AssertDouble(spin.TimeSamples[48.0], 180.0);
// Variant branches: the selection and every authored branch (with body) survive.
UsdVariantSet set = prim.VariantSets.Single();
@@ -135,10 +135,10 @@ public void ExportedScene_RecoversSamples_AndBranches_Structurally()
Assert.That(set.Variants.Select(v => v.Name), Is.EqualTo(new[] { "high", "low" }));
Assert.That(
set.Variants[0].Attributes.Single(a => a.Name == "resolution").Value,
- Is.EqualTo(1024L));
+ Is.EqualTo(UsdValue.From(1024L)));
Assert.That(
set.Variants[1].Attributes.Single(a => a.Name == "resolution").Value,
- Is.EqualTo(256L));
+ Is.EqualTo(UsdValue.From(256L)));
}
[Test]
diff --git a/tests/Opc.Ua.OpenUsd.Tests/SceneQuery.cs b/tests/Opc.Ua.OpenUsd.Tests/SceneQuery.cs
index c779dc876b..39a3854fdb 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/SceneQuery.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/SceneQuery.cs
@@ -144,7 +144,7 @@ public static Scene.UsdStage NormalizeValuesAndConnections(this Scene.UsdStage s
{
foreach (Scene.UsdAttribute attribute in prim.Attributes)
{
- attribute.Value = null;
+ attribute.Value = Scene.UsdValue.Null;
attribute.Connections.Clear();
}
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/TargetNodeIdAuthoringTests.cs b/tests/Opc.Ua.OpenUsd.Tests/TargetNodeIdAuthoringTests.cs
index 7591fb286b..6c9e78dc79 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/TargetNodeIdAuthoringTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/TargetNodeIdAuthoringTests.cs
@@ -224,7 +224,7 @@ private static UsdStage NamedStage(string name)
{
var stage = new UsdStage(name) { DefaultPrim = "Shared" };
var prim = new UsdPrim("Shared", "Xform");
- prim.Attributes.Add(new UsdAttribute("value", "double") { Value = 1.0 });
+ prim.Attributes.Add(new UsdAttribute("value", "double") { Value = UsdValue.From(1.0) });
stage.AddRootPrim(prim);
return stage;
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/TimeSampleMaterializationTests.cs b/tests/Opc.Ua.OpenUsd.Tests/TimeSampleMaterializationTests.cs
index b8f0e23030..855102e17d 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/TimeSampleMaterializationTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/TimeSampleMaterializationTests.cs
@@ -52,9 +52,9 @@ public class TimeSampleMaterializationTests
[Test]
public void SampledAttribute_MaterializesDefaultAsValue_AndHistorizes()
{
- var attr = new UsdAttribute("angle", "double") { Value = 5.0 };
- attr.TimeSamples[0.0] = 0.0;
- attr.TimeSamples[24.0] = 90.0;
+ var attr = new UsdAttribute("angle", "double") { Value = UsdValue.From(5.0) };
+ attr.TimeSamples[0.0] = UsdValue.From(0.0);
+ attr.TimeSamples[24.0] = UsdValue.From(90.0);
MaterializedScene ms = MaterializeAttr(attr);
UsdAttributeState node = ms.Attr("/P.angle");
@@ -70,8 +70,8 @@ public void SampledAttribute_MaterializesDefaultAsValue_AndHistorizes()
public void SampledAttribute_WithoutDefault_HasNoValue_ButStillHistorizes()
{
var attr = new UsdAttribute("angle", "double");
- attr.TimeSamples[0.0] = 0.0;
- attr.TimeSamples[24.0] = 90.0;
+ attr.TimeSamples[0.0] = UsdValue.From(0.0);
+ attr.TimeSamples[24.0] = UsdValue.From(90.0);
MaterializedScene ms = MaterializeAttr(attr);
UsdAttributeState node = ms.Attr("/P.angle");
@@ -84,10 +84,10 @@ public void SampledAttribute_WithoutDefault_HasNoValue_ButStillHistorizes()
[Test]
public void HistoricalAccess_ExposesOrderedSamples_KeyedByComposedPath()
{
- var attr = new UsdAttribute("angle", "double") { Value = 5.0 };
- attr.TimeSamples[48.0] = 180.0;
- attr.TimeSamples[0.0] = 0.0;
- attr.TimeSamples[24.0] = 90.0;
+ var attr = new UsdAttribute("angle", "double") { Value = UsdValue.From(5.0) };
+ attr.TimeSamples[48.0] = UsdValue.From(180.0);
+ attr.TimeSamples[0.0] = UsdValue.From(0.0);
+ attr.TimeSamples[24.0] = UsdValue.From(90.0);
MaterializedScene ms = MaterializeAttr(attr);
Assert.That(ms.Result.HistoricalAccessByPath.ContainsKey("/P.angle"), Is.True);
@@ -98,36 +98,37 @@ public void HistoricalAccess_ExposesOrderedSamples_KeyedByComposedPath()
Assert.That(
ha.Samples.Select(s => s.TimeCode), Is.EqualTo(new[] { 0.0, 24.0, 48.0 }));
Assert.That(
- ha.Samples.Select(s => s.Value), Is.EqualTo(new object?[] { 0.0, 90.0, 180.0 }));
+ ha.Samples.Select(s => s.Value),
+ Is.EqualTo(new[] { UsdValue.From(0.0), UsdValue.From(90.0), UsdValue.From(180.0) }));
}
[Test]
public void CoauthoredDefaultAndSamples_AreIndependent()
{
- var attr = new UsdAttribute("angle", "double") { Value = 42.0 };
- attr.TimeSamples[0.0] = 7.0;
+ var attr = new UsdAttribute("angle", "double") { Value = UsdValue.From(42.0) };
+ attr.TimeSamples[0.0] = UsdValue.From(7.0);
MaterializedScene ms = MaterializeAttr(attr);
// The default and the first sample differ, proving Value is the default, not sample[0].
Assert.That(ms.Attr("/P.angle").BoxedValue(), Is.EqualTo(42.0));
Assert.That(
ms.Result.HistoricalAccessByPath["/P.angle"].Samples.Single().Value,
- Is.EqualTo(7.0));
+ Is.EqualTo(UsdValue.From(7.0)));
}
[Test]
public void NegativeAndFractionalTimeCodes_ArePreserved()
{
var attr = new UsdAttribute("angle", "double");
- attr.TimeSamples[-12.0] = -1.0;
- attr.TimeSamples[0.5] = 5.0;
- attr.TimeSamples[2.25] = 7.5;
+ attr.TimeSamples[-12.0] = UsdValue.From(-1.0);
+ attr.TimeSamples[0.5] = UsdValue.From(5.0);
+ attr.TimeSamples[2.25] = UsdValue.From(7.5);
MaterializedScene ms = MaterializeAttr(attr);
IReadOnlyList samples =
ms.Result.HistoricalAccessByPath["/P.angle"].Samples;
Assert.That(samples.Select(s => s.TimeCode), Is.EqualTo(new[] { -12.0, 0.5, 2.25 }));
- Assert.That(samples[0].Value, Is.EqualTo(-1.0));
+ UsdTestHelpers.AssertDouble(samples[0].Value, -1.0);
}
[Test]
@@ -136,15 +137,17 @@ public void UnknownValueType_Samples_AreHistorized_AndPreservedOpaquely()
// §8.4: an unrecognized SdfValueTypeName is carried opaquely. Its samples must still be
// recorded verbatim — the materializer never guesses at or drops an unknown value.
var attr = new UsdAttribute("mystery", "customType");
- attr.TimeSamples[0.0] = "opaque-a";
- attr.TimeSamples[10.0] = "opaque-b";
+ attr.TimeSamples[0.0] = UsdValue.FromString("opaque-a");
+ attr.TimeSamples[10.0] = UsdValue.FromString("opaque-b");
MaterializedScene ms = MaterializeAttr(attr);
UsdAttributeState node = ms.Attr("/P.mystery");
Assert.That(node.Historizing, Is.True);
IReadOnlyList samples =
ms.Result.HistoricalAccessByPath["/P.mystery"].Samples;
- Assert.That(samples.Select(s => s.Value), Is.EqualTo(new object?[] { "opaque-a", "opaque-b" }));
+ Assert.That(
+ samples.Select(s => s.Value),
+ Is.EqualTo(new[] { UsdValue.FromString("opaque-a"), UsdValue.FromString("opaque-b") }));
}
// ---- regression: unsampled attributes are untouched ----------------------------
@@ -152,7 +155,7 @@ public void UnknownValueType_Samples_AreHistorized_AndPreservedOpaquely()
[Test]
public void UnsampledAttribute_IsNotHistorizing_AndAbsentFromHistoricalAccess()
{
- var attr = new UsdAttribute("angle", "double") { Value = 5.0 };
+ var attr = new UsdAttribute("angle", "double") { Value = UsdValue.From(5.0) };
MaterializedScene ms = MaterializeAttr(attr);
Assert.That(ms.Attr("/P.angle").Historizing, Is.False);
@@ -165,7 +168,7 @@ public void UnsampledAttribute_IsNotHistorizing_AndAbsentFromHistoricalAccess()
public void ResolveUtc_WithoutEpoch_ReturnsNull()
{
var attr = new UsdAttribute("angle", "double");
- attr.TimeSamples[24.0] = 90.0;
+ attr.TimeSamples[24.0] = UsdValue.From(90.0);
// No epoch option and stage declares TimeCodesPerSecond: the timeline is Server-defined.
MaterializedScene ms = MaterializeAttr(attr, tcps: 24.0, epochUtc: null);
@@ -179,7 +182,7 @@ public void ResolveUtc_WithEpochAndTimeCodesPerSecond_MapsToWallClock()
{
var epoch = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var attr = new UsdAttribute("angle", "double");
- attr.TimeSamples[48.0] = 180.0;
+ attr.TimeSamples[48.0] = UsdValue.From(180.0);
MaterializedScene ms = MaterializeAttr(attr, tcps: 24.0, epochUtc: epoch);
UsdHistoricalAccess ha = ms.Result.HistoricalAccessByPath["/P.angle"];
@@ -194,7 +197,7 @@ public void ResolveUtc_WithEpochButNoTimeCodesPerSecond_ReturnsNull()
{
var epoch = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
var attr = new UsdAttribute("angle", "double");
- attr.TimeSamples[24.0] = 90.0;
+ attr.TimeSamples[24.0] = UsdValue.From(90.0);
// Epoch declared but the stage has no TimeCodesPerSecond: the rate is unknown, so the
// mapping stays undefined rather than assuming a rate (fail closed).
MaterializedScene ms = MaterializeAttr(attr, tcps: null, epochUtc: epoch);
@@ -207,18 +210,18 @@ public void ResolveUtc_WithEpochButNoTimeCodesPerSecond_ReturnsNull()
[Test]
public void SampledAttribute_RoundTripsThroughExport()
{
- var attr = new UsdAttribute("angle", "double") { Value = 5.0 };
- attr.TimeSamples[-6.0] = -1.0;
- attr.TimeSamples[0.0] = 0.0;
- attr.TimeSamples[24.0] = 90.0;
+ var attr = new UsdAttribute("angle", "double") { Value = UsdValue.From(5.0) };
+ attr.TimeSamples[-6.0] = UsdValue.From(-1.0);
+ attr.TimeSamples[0.0] = UsdValue.From(0.0);
+ attr.TimeSamples[24.0] = UsdValue.From(90.0);
MaterializedScene ms = MaterializeAttr(attr);
UsdStage exported = ms.Context.ExportUsdStage(ms.Result);
UsdAttribute exportedAttr = exported.Find("/P")!.Attributes.Single();
- Assert.That(exportedAttr.Value, Is.EqualTo(5.0));
+ UsdTestHelpers.AssertDouble(exportedAttr.Value, 5.0);
Assert.That(exportedAttr.TimeSamples.Keys, Is.EqualTo(new[] { -6.0, 0.0, 24.0 }));
- Assert.That(exportedAttr.TimeSamples[24.0], Is.EqualTo(90.0));
+ UsdTestHelpers.AssertDouble(exportedAttr.TimeSamples[24.0], 90.0);
}
[Test]
@@ -226,14 +229,14 @@ public void Exporter_WithoutSampleMap_KeepsDefault_ButOmitsSamples()
{
// Exporting from the stage node alone (no samples map) cannot recover the samples —
// they live on the result, not the node — but the authored default still round-trips.
- var attr = new UsdAttribute("angle", "double") { Value = 5.0 };
- attr.TimeSamples[0.0] = 0.0;
+ var attr = new UsdAttribute("angle", "double") { Value = UsdValue.From(5.0) };
+ attr.TimeSamples[0.0] = UsdValue.From(0.0);
MaterializedScene ms = MaterializeAttr(attr);
UsdStage exported = ms.Context.ExportUsdStage(ms.Stage);
UsdAttribute exportedAttr = exported.Find("/P")!.Attributes.Single();
- Assert.That(exportedAttr.Value, Is.EqualTo(5.0));
+ UsdTestHelpers.AssertDouble(exportedAttr.Value, 5.0);
Assert.That(exportedAttr.TimeSamples, Is.Empty);
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdSceneDiscoveryTests.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdSceneDiscoveryTests.cs
index 1a21a6abca..70674c49a9 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/UsdSceneDiscoveryTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdSceneDiscoveryTests.cs
@@ -204,7 +204,7 @@ private static UsdStage SingleStage()
{
var stage = new UsdStage("Solo") { DefaultPrim = "Shared" };
var prim = new UsdPrim("Shared", "Xform");
- prim.Attributes.Add(new UsdAttribute("value", "double") { Value = 1.0 });
+ prim.Attributes.Add(new UsdAttribute("value", "double") { Value = UsdValue.From(1.0) });
stage.AddRootPrim(prim);
return stage;
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdSceneExporterFallbackTests.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdSceneExporterFallbackTests.cs
index b199bfac78..2fafba0efd 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/UsdSceneExporterFallbackTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdSceneExporterFallbackTests.cs
@@ -113,7 +113,7 @@ public void ConnectionsAreRebuiltFromTheBrowsableEdgesForABareStageNode()
public void AMetadataEntryWithoutABrowseNameIsSkipped()
{
var prim = new UsdPrim("Mesh", "Mesh");
- prim.Metadata["author"] = "Ada";
+ prim.Metadata["author"] = UsdValue.FromString("Ada");
var stage = new UsdStage("Test");
stage.AddRootPrim(prim);
MaterializedScene scene = MaterializationHarness.Materialize(stage);
@@ -166,8 +166,8 @@ public void AnEnumMemberHoldingAnUnreadableValueFallsBackToItsDefault()
private static UsdStage ConnectedScene()
{
var prim = new UsdPrim("Mesh", "Mesh") { Kind = UsdPrimKindEnum.Component };
- prim.Attributes.Add(new UsdAttribute("size", "double") { Value = 2.0 });
- var radius = new UsdAttribute("radius", "double") { Value = 1.0 };
+ prim.Attributes.Add(new UsdAttribute("size", "double") { Value = UsdValue.From(2.0) });
+ var radius = new UsdAttribute("radius", "double") { Value = UsdValue.From(1.0) };
radius.Connections.Add("/Mesh.size");
radius.Connections.Add("/Elsewhere.size");
prim.Attributes.Add(radius);
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdSceneSignatureTests.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdSceneSignatureTests.cs
index d0ff93d1a9..3542dfa3c6 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/UsdSceneSignatureTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdSceneSignatureTests.cs
@@ -65,11 +65,11 @@ public void Signature_DetectsChangedAttributeValue()
{
var a = new UsdStage("S");
UsdPrim pa = a.AddRootPrim(new UsdPrim("X", "Xform"));
- pa.Attributes.Add(new UsdAttribute("v", "int") { Value = 1L });
+ pa.Attributes.Add(new UsdAttribute("v", "int") { Value = UsdValue.From(1L) });
var b = new UsdStage("S");
UsdPrim pb = b.AddRootPrim(new UsdPrim("X", "Xform"));
- pb.Attributes.Add(new UsdAttribute("v", "int") { Value = 2L });
+ pb.Attributes.Add(new UsdAttribute("v", "int") { Value = UsdValue.From(2L) });
Assert.That(UsdSceneSignature.Compute(b), Is.Not.EqualTo(UsdSceneSignature.Compute(a)));
Assert.That(UsdSceneSignature.FirstDifference(a, b), Is.Not.Null);
@@ -80,13 +80,13 @@ public void Signature_IsIndependentOfAttributeOrder()
{
var a = new UsdStage("S");
UsdPrim pa = a.AddRootPrim(new UsdPrim("X", "Xform"));
- pa.Attributes.Add(new UsdAttribute("a", "int") { Value = 1L });
- pa.Attributes.Add(new UsdAttribute("b", "int") { Value = 2L });
+ pa.Attributes.Add(new UsdAttribute("a", "int") { Value = UsdValue.From(1L) });
+ pa.Attributes.Add(new UsdAttribute("b", "int") { Value = UsdValue.From(2L) });
var b = new UsdStage("S");
UsdPrim pb = b.AddRootPrim(new UsdPrim("X", "Xform"));
- pb.Attributes.Add(new UsdAttribute("b", "int") { Value = 2L });
- pb.Attributes.Add(new UsdAttribute("a", "int") { Value = 1L });
+ pb.Attributes.Add(new UsdAttribute("b", "int") { Value = UsdValue.From(2L) });
+ pb.Attributes.Add(new UsdAttribute("a", "int") { Value = UsdValue.From(1L) });
Assert.That(UsdSceneSignature.Compute(b), Is.EqualTo(UsdSceneSignature.Compute(a)));
}
@@ -107,6 +107,74 @@ public void Signature_IsSensitiveToCompositionArcOrder()
Assert.That(UsdSceneSignature.Compute(b), Is.Not.EqualTo(UsdSceneSignature.Compute(a)));
}
+ [Test]
+ public void Signature_NormalizesEveryValueKind()
+ {
+ // Every kind must reach the normalizer: an unnormalized kind would make two
+ // different scenes sign identically.
+ var a = new UsdStage("S");
+ UsdPrim pa = a.AddRootPrim(new UsdPrim("X", "Xform"));
+ pa.Attributes.Add(new UsdAttribute("flag", "bool") { Value = UsdValue.From(true) });
+
+ var b = new UsdStage("S");
+ UsdPrim pb = b.AddRootPrim(new UsdPrim("X", "Xform"));
+ pb.Attributes.Add(new UsdAttribute("flag", "bool") { Value = UsdValue.From(false) });
+
+ Assert.That(UsdSceneSignature.Compute(b), Is.Not.EqualTo(UsdSceneSignature.Compute(a)));
+ Assert.That(UsdSceneSignature.FirstDifference(a, b), Is.Not.Null);
+ }
+
+ [Test]
+ public void Signature_OfADictionaryValueIsIndependentOfEntryOrder()
+ {
+ var a = new UsdStage("S");
+ UsdPrim pa = a.AddRootPrim(new UsdPrim("X", "Xform"));
+ pa.Attributes.Add(new UsdAttribute("d", "dictionary")
+ {
+ Value = Dictionary(("author", UsdValue.FromString("acme")), ("order", UsdValue.From(3L)))
+ });
+
+ var b = new UsdStage("S");
+ UsdPrim pb = b.AddRootPrim(new UsdPrim("X", "Xform"));
+ pb.Attributes.Add(new UsdAttribute("d", "dictionary")
+ {
+ Value = Dictionary(("order", UsdValue.From(3L)), ("author", UsdValue.FromString("acme")))
+ });
+
+ Assert.That(UsdSceneSignature.Compute(b), Is.EqualTo(UsdSceneSignature.Compute(a)));
+ }
+
+ [Test]
+ public void Signature_DetectsAChangedDictionaryEntry()
+ {
+ var a = new UsdStage("S");
+ UsdPrim pa = a.AddRootPrim(new UsdPrim("X", "Xform"));
+ pa.Attributes.Add(new UsdAttribute("d", "dictionary")
+ {
+ Value = Dictionary(("nested", Dictionary(("depth", UsdValue.From(1L)))))
+ });
+
+ var b = new UsdStage("S");
+ UsdPrim pb = b.AddRootPrim(new UsdPrim("X", "Xform"));
+ pb.Attributes.Add(new UsdAttribute("d", "dictionary")
+ {
+ Value = Dictionary(("nested", Dictionary(("depth", UsdValue.From(2L)))))
+ });
+
+ Assert.That(UsdSceneSignature.Compute(b), Is.Not.EqualTo(UsdSceneSignature.Compute(a)));
+ }
+
+ private static UsdValue Dictionary(params (string Key, UsdValue Value)[] entries)
+ {
+ var map = new System.Collections.Generic.Dictionary(
+ System.StringComparer.Ordinal);
+ foreach ((string key, UsdValue value) in entries)
+ {
+ map[key] = value;
+ }
+ return UsdValue.FromDictionary(map);
+ }
+
private static UsdStage BuildStage(bool withNonComposedState)
{
var stage = new UsdStage("S")
@@ -121,7 +189,7 @@ private static UsdStage BuildStage(bool withNonComposedState)
};
var attr = new UsdAttribute("v", "int")
{
- Value = 1L,
+ Value = UsdValue.From(1L),
Live = withNonComposedState,
Interpolation = withNonComposedState ? "vertex" : null,
};
@@ -129,7 +197,7 @@ private static UsdStage BuildStage(bool withNonComposedState)
if (withNonComposedState)
{
prim.ApiSchemas.Add(new UsdApiSchema("MaterialBindingAPI"));
- prim.Metadata["hidden"] = true;
+ prim.Metadata["hidden"] = UsdValue.From(true);
}
stage.AddRootPrim(prim);
return stage;
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdTestHelpers.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdTestHelpers.cs
index 6f63f624af..2a4de7bcc9 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/UsdTestHelpers.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdTestHelpers.cs
@@ -28,8 +28,10 @@
* ======================================================================*/
using System;
+using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using Opc.Ua;
using Opc.Ua.OpenUsdScene.Scene;
namespace Opc.Ua.OpenUsdScene.Tests
@@ -62,5 +64,150 @@ public static UsdRelationship RequireRelationship(UsdPrim prim, string name)
Assert.That(rel, Is.Not.Null, "expected relationship " + name + " on " + prim.Path);
return rel!;
}
+
+ public static UsdValue Tuple(params UsdValue[] values)
+ {
+ return UsdValue.FromTuple(values.ToArrayOf());
+ }
+
+ public static UsdValue Array(params UsdValue[] values)
+ {
+ return UsdValue.FromArray(values.ToArrayOf());
+ }
+
+ public static UsdValue Dictionary(params KeyValuePair[] entries)
+ {
+ // The IEnumerable> constructor is not available on net48/net472,
+ // so fill the dictionary explicitly.
+ var map = new Dictionary(StringComparer.Ordinal);
+ foreach (KeyValuePair entry in entries)
+ {
+ map[entry.Key] = entry.Value;
+ }
+ return UsdValue.FromDictionary(map);
+ }
+
+ public static UsdValue NumberArray(params double[] values)
+ {
+ return UsdValue.FromArray(values.Select(UsdValue.From).ToArrayOf());
+ }
+
+ public static UsdValue NumberTuple(params double[] values)
+ {
+ return UsdValue.FromTuple(values.Select(UsdValue.From).ToArrayOf());
+ }
+
+ public static UsdValue IntegerArray(params long[] values)
+ {
+ return UsdValue.FromArray(values.Select(UsdValue.From).ToArrayOf());
+ }
+
+ public static UsdValue IntegerTuple(params long[] values)
+ {
+ return UsdValue.FromTuple(values.Select(UsdValue.From).ToArrayOf());
+ }
+
+ public static UsdValue StringArray(params string[] values)
+ {
+ return UsdValue.FromArray(values.Select(UsdValue.FromString).ToArrayOf());
+ }
+
+ public static UsdValue TokenArray(params string[] values)
+ {
+ return UsdValue.FromArray(values.Select(UsdValue.FromToken).ToArrayOf());
+ }
+
+ public static UsdValue AssetArray(params string[] values)
+ {
+ return UsdValue.FromArray(values.Select(UsdValue.FromAssetPath).ToArrayOf());
+ }
+
+ public static void AssertDouble(UsdValue value, double expected)
+ {
+ Assert.That(value.TryGetDouble(out double actual), Is.True);
+ Assert.That(actual, Is.EqualTo(expected));
+ }
+
+ public static void AssertInteger(UsdValue value, long expected)
+ {
+ Assert.That(value.TryGetInteger(out long actual), Is.True);
+ Assert.That(actual, Is.EqualTo(expected));
+ }
+
+ public static void AssertBoolean(UsdValue value, bool expected)
+ {
+ Assert.That(value.TryGetBoolean(out bool actual), Is.True);
+ Assert.That(actual, Is.EqualTo(expected));
+ }
+
+ public static void AssertString(UsdValue value, string expected)
+ {
+ Assert.That(value.TryGetString(out string actual), Is.True);
+ Assert.That(actual, Is.EqualTo(expected));
+ }
+
+ public static void AssertToken(UsdValue value, string expected)
+ {
+ Assert.That(value.TryGetToken(out string actual), Is.True);
+ Assert.That(actual, Is.EqualTo(expected));
+ }
+
+ public static void AssertAssetPath(UsdValue value, string expected)
+ {
+ Assert.That(value.TryGetAssetPath(out string actual), Is.True);
+ Assert.That(actual, Is.EqualTo(expected));
+ }
+
+ public static void AssertPathReference(UsdValue value, string expected)
+ {
+ Assert.That(value.TryGetPathReference(out string actual), Is.True);
+ Assert.That(actual, Is.EqualTo(expected));
+ }
+
+ public static void AssertText(UsdValue value, string expected)
+ {
+ Assert.That(value.TryGetText(out string actual), Is.True);
+ Assert.That(actual, Is.EqualTo(expected));
+ }
+
+ public static void AssertIntegerItems(UsdValue value, params long[] expected)
+ {
+ Assert.That(value.TryGetItems(out ArrayOf items), Is.True);
+ Assert.That(items.Count, Is.EqualTo(expected.Length));
+ for (int ii = 0; ii < expected.Length; ii++)
+ {
+ AssertInteger(items[ii], expected[ii]);
+ }
+ }
+
+ public static void AssertDoubleItems(UsdValue value, params double[] expected)
+ {
+ Assert.That(value.TryGetItems(out ArrayOf items), Is.True);
+ Assert.That(items.Count, Is.EqualTo(expected.Length));
+ for (int ii = 0; ii < expected.Length; ii++)
+ {
+ AssertDouble(items[ii], expected[ii]);
+ }
+ }
+
+ public static void AssertTextItems(UsdValue value, params string[] expected)
+ {
+ Assert.That(value.TryGetItems(out ArrayOf items), Is.True);
+ Assert.That(items.Count, Is.EqualTo(expected.Length));
+ for (int ii = 0; ii < expected.Length; ii++)
+ {
+ AssertText(items[ii], expected[ii]);
+ }
+ }
+
+ public static void AssertNestedIntegerItems(UsdValue value, params long[][] expected)
+ {
+ Assert.That(value.TryGetItems(out ArrayOf rows), Is.True);
+ Assert.That(rows.Count, Is.EqualTo(expected.Length));
+ for (int ii = 0; ii < expected.Length; ii++)
+ {
+ AssertIntegerItems(rows[ii], expected[ii]);
+ }
+ }
}
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdTimeSampleEqualityTests.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdTimeSampleEqualityTests.cs
index 20e1096ba7..fbdf9ab456 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/UsdTimeSampleEqualityTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdTimeSampleEqualityTests.cs
@@ -28,6 +28,7 @@
* ======================================================================*/
using NUnit.Framework;
+using Opc.Ua.OpenUsdScene.Scene;
using Opc.Ua.OpenUsdScene.Server;
namespace Opc.Ua.OpenUsdScene.Tests
@@ -44,8 +45,8 @@ public sealed class UsdTimeSampleEqualityTests
[Test]
public void SamplesWithTheSameTimeCodeAndValueAreEqual()
{
- var left = new UsdTimeSample(1.5, 42.0);
- var right = new UsdTimeSample(1.5, 42.0);
+ var left = new UsdTimeSample(1.5, UsdValue.From(42.0));
+ var right = new UsdTimeSample(1.5, UsdValue.From(42.0));
bool viaOperator = left == right;
bool viaInequality = left != right;
@@ -58,8 +59,8 @@ public void SamplesWithTheSameTimeCodeAndValueAreEqual()
[Test]
public void SamplesWithADifferentTimeCodeAreNotEqual()
{
- var left = new UsdTimeSample(1.5, 42.0);
- var right = new UsdTimeSample(2.5, 42.0);
+ var left = new UsdTimeSample(1.5, UsdValue.From(42.0));
+ var right = new UsdTimeSample(2.5, UsdValue.From(42.0));
bool viaOperator = left == right;
bool viaInequality = left != right;
@@ -71,8 +72,8 @@ public void SamplesWithADifferentTimeCodeAreNotEqual()
[Test]
public void SamplesWithADifferentValueAreNotEqual()
{
- var left = new UsdTimeSample(1.5, 42.0);
- var right = new UsdTimeSample(1.5, "42");
+ var left = new UsdTimeSample(1.5, UsdValue.From(42.0));
+ var right = new UsdTimeSample(1.5, UsdValue.FromString("42"));
bool viaEquatable = left.Equals(right);
Assert.That(viaEquatable, Is.False);
@@ -82,8 +83,8 @@ public void SamplesWithADifferentValueAreNotEqual()
[Test]
public void ASampleWithoutAValueEqualsAnotherWithoutAValue()
{
- var left = new UsdTimeSample(-3.25, null);
- var right = new UsdTimeSample(-3.25, null);
+ var left = new UsdTimeSample(-3.25, UsdValue.Null);
+ var right = new UsdTimeSample(-3.25, UsdValue.Null);
bool viaEquatable = left.Equals(right);
Assert.That(viaEquatable, Is.True);
@@ -93,8 +94,8 @@ public void ASampleWithoutAValueEqualsAnotherWithoutAValue()
[Test]
public void ASampleEqualsABoxedSampleWithTheSameContent()
{
- var sample = new UsdTimeSample(0.5, 7);
- object boxed = new UsdTimeSample(0.5, 7);
+ var sample = new UsdTimeSample(0.5, UsdValue.From(7L));
+ object boxed = new UsdTimeSample(0.5, UsdValue.From(7L));
bool equal = sample.Equals(boxed);
Assert.That(equal, Is.True);
@@ -103,7 +104,7 @@ public void ASampleEqualsABoxedSampleWithTheSameContent()
[Test]
public void ASampleDoesNotEqualAnObjectOfAnotherType()
{
- var sample = new UsdTimeSample(0.5, 7);
+ var sample = new UsdTimeSample(0.5, UsdValue.From(7L));
bool equalsText = sample.Equals("0.5");
bool equalsNothing = sample.Equals(null);
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdTimeSampleTests.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdTimeSampleTests.cs
index af1f90889d..8b676f63d9 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/UsdTimeSampleTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdTimeSampleTests.cs
@@ -95,9 +95,14 @@ public void MultiLineBlock_ParsesScalarSamples_SeparateFromDefault()
" 48: 180.0,",
" }");
- Assert.That(attr.Value, Is.Null, "no authored default was declared");
+ Assert.That(attr.Value.IsNull, Is.True, "no authored default was declared");
Assert.That(attr.TimeSamples.Keys, Is.EqualTo(new[] { 0.0, 24.0, 48.0 }));
- Assert.That(attr.TimeSamples.Values, Is.EqualTo(new object?[] { 0.0, 90.0, 180.0 }));
+ Assert.That(attr.TimeSamples.Values, Is.EqualTo(new[]
+ {
+ UsdValue.From(0.0),
+ UsdValue.From(90.0),
+ UsdValue.From(180.0)
+ }));
}
[Test]
@@ -108,7 +113,7 @@ public void SingleLineBlock_ParsesSamples()
" double a.timeSamples = { 0: 0.0, 24: 90.0 }");
Assert.That(attr.TimeSamples.Keys, Is.EqualTo(new[] { 0.0, 24.0 }));
- Assert.That(attr.TimeSamples.Values, Is.EqualTo(new object?[] { 0.0, 90.0 }));
+ Assert.That(attr.TimeSamples.Values, Is.EqualTo(new[] { UsdValue.From(0.0), UsdValue.From(90.0) }));
}
[Test]
@@ -123,8 +128,8 @@ public void NegativeAndFractionalTimeCodes_AreParsed()
" }");
Assert.That(attr.TimeSamples.Keys, Is.EqualTo(new[] { -12.0, 0.5, 2.25 }));
- Assert.That(attr.TimeSamples[-12.0], Is.EqualTo(-1.0));
- Assert.That(attr.TimeSamples[0.5], Is.EqualTo(5.0));
+ UsdTestHelpers.AssertDouble(attr.TimeSamples[-12.0], -1.0);
+ UsdTestHelpers.AssertDouble(attr.TimeSamples[0.5], 5.0);
}
[Test]
@@ -151,8 +156,8 @@ public void TupleValuedSamples_AreParsed()
" 24: (1, 2, 3),",
" }");
- Assert.That(attr.TimeSamples[0.0], Is.EqualTo(new object?[] { 0L, 0L, 0L }));
- Assert.That(attr.TimeSamples[24.0], Is.EqualTo(new object?[] { 1L, 2L, 3L }));
+ UsdTestHelpers.AssertIntegerItems(attr.TimeSamples[0.0], 0L, 0L, 0L);
+ UsdTestHelpers.AssertIntegerItems(attr.TimeSamples[24.0], 1L, 2L, 3L);
}
[Test]
@@ -165,8 +170,8 @@ public void ArrayValuedSamples_AreParsed()
" 24: [4, 5, 6],",
" }");
- Assert.That(attr.TimeSamples[0.0], Is.EqualTo(new List { 1L, 2L, 3L }));
- Assert.That(attr.TimeSamples[24.0], Is.EqualTo(new List { 4L, 5L, 6L }));
+ UsdTestHelpers.AssertIntegerItems(attr.TimeSamples[0.0], 1L, 2L, 3L);
+ UsdTestHelpers.AssertIntegerItems(attr.TimeSamples[24.0], 4L, 5L, 6L);
}
[Test]
@@ -179,8 +184,8 @@ public void AssetValuedSamples_AreUnwrapped()
" 24: @./b.usda@,",
" }");
- Assert.That(attr.TimeSamples[0.0], Is.EqualTo("./a.usda"));
- Assert.That(attr.TimeSamples[24.0], Is.EqualTo("./b.usda"));
+ UsdTestHelpers.AssertAssetPath(attr.TimeSamples[0.0], "./a.usda");
+ UsdTestHelpers.AssertAssetPath(attr.TimeSamples[24.0], "./b.usda");
}
[Test]
@@ -193,8 +198,8 @@ public void TokenValuedSamples_AreUnwrapped()
" 24: \"closed\",",
" }");
- Assert.That(attr.TimeSamples[0.0], Is.EqualTo("open"));
- Assert.That(attr.TimeSamples[24.0], Is.EqualTo("closed"));
+ UsdTestHelpers.AssertString(attr.TimeSamples[0.0], "open");
+ UsdTestHelpers.AssertString(attr.TimeSamples[24.0], "closed");
}
[Test]
@@ -208,7 +213,7 @@ public void DefaultAndSamples_CoexistOnOneAttribute()
" 24: 90.0,",
" }");
- Assert.That(attr.Value, Is.EqualTo(5.0));
+ UsdTestHelpers.AssertDouble(attr.Value, 5.0);
Assert.That(attr.TimeSamples.Keys, Is.EqualTo(new[] { 0.0, 24.0 }));
}
@@ -322,15 +327,15 @@ public void Signature_DistinguishesDifferingSampleValues()
var a = new UsdStage("S");
UsdPrim pa = a.AddRootPrim(new UsdPrim("X", "Xform"));
var attrA = new UsdAttribute("v", "double");
- attrA.TimeSamples[0.0] = 0.0;
- attrA.TimeSamples[24.0] = 90.0;
+ attrA.TimeSamples[0.0] = UsdValue.From(0.0);
+ attrA.TimeSamples[24.0] = UsdValue.From(90.0);
pa.Attributes.Add(attrA);
var b = new UsdStage("S");
UsdPrim pb = b.AddRootPrim(new UsdPrim("X", "Xform"));
var attrB = new UsdAttribute("v", "double");
- attrB.TimeSamples[0.0] = 0.0;
- attrB.TimeSamples[24.0] = 91.0;
+ attrB.TimeSamples[0.0] = UsdValue.From(0.0);
+ attrB.TimeSamples[24.0] = UsdValue.From(91.0);
pb.Attributes.Add(attrB);
Assert.That(UsdSceneSignature.Compute(b), Is.Not.EqualTo(UsdSceneSignature.Compute(a)));
@@ -342,13 +347,13 @@ public void Signature_DistinguishesPresenceOfSamples()
{
var withSamples = new UsdStage("S");
UsdPrim ps = withSamples.AddRootPrim(new UsdPrim("X", "Xform"));
- var sampled = new UsdAttribute("v", "double") { Value = 1.0 };
- sampled.TimeSamples[0.0] = 0.0;
+ var sampled = new UsdAttribute("v", "double") { Value = UsdValue.From(1.0) };
+ sampled.TimeSamples[0.0] = UsdValue.From(0.0);
ps.Attributes.Add(sampled);
var withoutSamples = new UsdStage("S");
UsdPrim pn = withoutSamples.AddRootPrim(new UsdPrim("X", "Xform"));
- pn.Attributes.Add(new UsdAttribute("v", "double") { Value = 1.0 });
+ pn.Attributes.Add(new UsdAttribute("v", "double") { Value = UsdValue.From(1.0) });
Assert.That(
UsdSceneSignature.Compute(withoutSamples),
@@ -361,15 +366,15 @@ public void Signature_EqualForIdenticalSamples()
var a = new UsdStage("S");
UsdPrim pa = a.AddRootPrim(new UsdPrim("X", "Xform"));
var attrA = new UsdAttribute("v", "double");
- attrA.TimeSamples[0.0] = 0.0;
- attrA.TimeSamples[24.0] = 90.0;
+ attrA.TimeSamples[0.0] = UsdValue.From(0.0);
+ attrA.TimeSamples[24.0] = UsdValue.From(90.0);
pa.Attributes.Add(attrA);
var b = new UsdStage("S");
UsdPrim pb = b.AddRootPrim(new UsdPrim("X", "Xform"));
var attrB = new UsdAttribute("v", "double");
- attrB.TimeSamples[0.0] = 0.0;
- attrB.TimeSamples[24.0] = 90.0;
+ attrB.TimeSamples[0.0] = UsdValue.From(0.0);
+ attrB.TimeSamples[24.0] = UsdValue.From(90.0);
pb.Attributes.Add(attrB);
Assert.That(UsdSceneSignature.Compute(b), Is.EqualTo(UsdSceneSignature.Compute(a)));
@@ -382,7 +387,7 @@ public void Signature_OfSampleLessAttribute_IsUnchangedByTheFeature()
// samples existed, so the existing round-trip corpus cannot be perturbed.
var stage = new UsdStage("S");
UsdPrim prim = stage.AddRootPrim(new UsdPrim("X", "Xform"));
- prim.Attributes.Add(new UsdAttribute("v", "int") { Value = 1L });
+ prim.Attributes.Add(new UsdAttribute("v", "int") { Value = UsdValue.From(1L) });
string signature = UsdSceneSignature.Compute(stage);
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdValueCoercionTests.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdValueCoercionTests.cs
new file mode 100644
index 0000000000..1e346acd6d
--- /dev/null
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdValueCoercionTests.cs
@@ -0,0 +1,589 @@
+/* ========================================================================
+ * Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
+ *
+ * OPC Foundation MIT License 1.00
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The complete license agreement can be found here:
+ * http://opcfoundation.org/License/MIT/1.00/
+ * ======================================================================*/
+
+using System;
+using NUnit.Framework;
+using Opc.Ua.OpenUsdScene.Conversion;
+using Opc.Ua.OpenUsdScene.Scene;
+
+namespace Opc.Ua.OpenUsdScene.Tests
+{
+ ///
+ /// Covers across every element type the §6.2 bindings can
+ /// name, in both directions and at all three ranks (scalar, array and matrix). The two
+ /// directions are separately typed switches, so a per-element-type case is the only way to
+ /// prove that neither drops a type or crosses two of them, and that both fail closed rather
+ /// than coerce a value that does not fit.
+ ///
+ [TestFixture]
+ [Category("OpenUsd")]
+ [Parallelizable]
+ public class UsdValueCoercionTests
+ {
+ private static readonly BuiltInType[] s_elementTypes =
+ [
+ BuiltInType.Boolean,
+ BuiltInType.SByte,
+ BuiltInType.Int32,
+ BuiltInType.Int64,
+ BuiltInType.UInt32,
+ BuiltInType.UInt64,
+ BuiltInType.Float,
+ BuiltInType.Double,
+ BuiltInType.String
+ ];
+
+ private static UsdValueTypeMapping Mapping(BuiltInType elementType, int valueRank)
+ {
+ return new UsdValueTypeMapping(
+ Opc.Ua.DataTypeIds.BaseDataType, valueRank, null, elementType, isOpaque: false);
+ }
+
+ ///
+ /// The USD value a scalar of the given element type is authored as, chosen so it is
+ /// representable in every type (a small non-negative integer, or text for a string).
+ ///
+ private static UsdValue Authored(BuiltInType elementType)
+ {
+ switch (elementType)
+ {
+ case BuiltInType.Boolean:
+ return UsdValue.From(true);
+ case BuiltInType.Float:
+ case BuiltInType.Double:
+ return UsdValue.From(2.5);
+ case BuiltInType.String:
+ return UsdValue.FromString("authored");
+ default:
+ return UsdValue.From(7L);
+ }
+ }
+
+ private static void AssertScalar(BuiltInType elementType, in Variant value)
+ {
+ switch (elementType)
+ {
+ case BuiltInType.Boolean:
+ Assert.That(value.TryGetValue(out bool b), Is.True);
+ Assert.That(b, Is.True);
+ break;
+ case BuiltInType.SByte:
+ Assert.That(value.TryGetValue(out sbyte sb), Is.True);
+ Assert.That(sb, Is.EqualTo((sbyte)7));
+ break;
+ case BuiltInType.Int32:
+ Assert.That(value.TryGetValue(out int i), Is.True);
+ Assert.That(i, Is.EqualTo(7));
+ break;
+ case BuiltInType.Int64:
+ Assert.That(value.TryGetValue(out long l), Is.True);
+ Assert.That(l, Is.EqualTo(7L));
+ break;
+ case BuiltInType.UInt32:
+ Assert.That(value.TryGetValue(out uint ui), Is.True);
+ Assert.That(ui, Is.EqualTo(7U));
+ break;
+ case BuiltInType.UInt64:
+ Assert.That(value.TryGetValue(out ulong ul), Is.True);
+ Assert.That(ul, Is.EqualTo(7UL));
+ break;
+ case BuiltInType.Float:
+ Assert.That(value.TryGetValue(out float f), Is.True);
+ Assert.That(f, Is.EqualTo(2.5f));
+ break;
+ case BuiltInType.Double:
+ Assert.That(value.TryGetValue(out double d), Is.True);
+ Assert.That(d, Is.EqualTo(2.5));
+ break;
+ default:
+ Assert.That(value.TryGetValue(out string s), Is.True);
+ Assert.That(s, Is.EqualTo("authored"));
+ break;
+ }
+ }
+
+ [Test]
+ public void CoerceReadsAScalarOfEveryElementType(
+ [ValueSource(nameof(s_elementTypes))] BuiltInType elementType)
+ {
+ bool ok = UsdValueCoercion.TryCoerce(
+ Authored(elementType), Mapping(elementType, ValueRanks.Scalar), 0, out Variant v);
+
+ Assert.That(ok, Is.True);
+ AssertScalar(elementType, v);
+ }
+
+ [Test]
+ public void CoerceReadsAnArrayOfEveryElementType(
+ [ValueSource(nameof(s_elementTypes))] BuiltInType elementType)
+ {
+ UsdValue authored = UsdTestHelpers.Array(Authored(elementType), Authored(elementType));
+
+ bool ok = UsdValueCoercion.TryCoerce(
+ authored, Mapping(elementType, ValueRanks.OneDimension), 0, out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TypeInfo.BuiltInType, Is.EqualTo(elementType));
+ Assert.That(v.TypeInfo.ValueRank, Is.EqualTo(ValueRanks.OneDimension));
+ }
+
+ [Test]
+ public void CoerceReadsAMatrixOfEveryElementType(
+ [ValueSource(nameof(s_elementTypes))] BuiltInType elementType)
+ {
+ // Two rows of two components each, which is the array-of-tuples shape USD authors a
+ // rectangular value with.
+ UsdValue row = UsdTestHelpers.Tuple(Authored(elementType), Authored(elementType));
+ UsdValue authored = UsdTestHelpers.Array(row, row);
+
+ bool ok = UsdValueCoercion.TryCoerce(
+ authored, Mapping(elementType, ValueRanks.TwoDimensions), 2, out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TypeInfo.BuiltInType, Is.EqualTo(elementType));
+ Assert.That(v.TypeInfo.ValueRank, Is.EqualTo(ValueRanks.TwoDimensions));
+ }
+
+ [Test]
+ public void CoerceFailsClosedForAnUnsupportedElementType()
+ {
+ // DateTime has no USD spelling, so every rank must leave the attribute unresolved
+ // rather than invent a value.
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdValue.From(1L), Mapping(BuiltInType.DateTime, ValueRanks.Scalar), 0, out _),
+ Is.False);
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdTestHelpers.IntegerArray(1L),
+ Mapping(BuiltInType.DateTime, ValueRanks.OneDimension),
+ 0,
+ out _),
+ Is.False);
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdTestHelpers.Array(UsdTestHelpers.IntegerTuple(1L)),
+ Mapping(BuiltInType.DateTime, ValueRanks.TwoDimensions),
+ 1,
+ out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceFailsClosedForAnUnsupportedValueRank()
+ {
+ const int threeDimensions = 3;
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdValue.From(1L),
+ Mapping(BuiltInType.Int32, threeDimensions),
+ 0,
+ out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceRejectsANullMapping()
+ {
+ Assert.That(
+ () => UsdValueCoercion.TryCoerce(UsdValue.From(1L), null!, 0, out _),
+ Throws.TypeOf());
+ }
+
+ [Test]
+ public void CoerceRejectsAnAbsentValue()
+ {
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdValue.Null, Mapping(BuiltInType.Int32, ValueRanks.Scalar), 0, out _),
+ Is.False);
+ }
+
+ [TestCase(BuiltInType.SByte, 300L)]
+ [TestCase(BuiltInType.SByte, -300L)]
+ [TestCase(BuiltInType.Int32, long.MaxValue)]
+ [TestCase(BuiltInType.Int32, long.MinValue)]
+ [TestCase(BuiltInType.UInt32, -1L)]
+ [TestCase(BuiltInType.UInt32, 4294967296L)]
+ [TestCase(BuiltInType.UInt64, -1L)]
+ public void CoerceFailsClosedForAnIntegerOutsideTheElementRange(
+ BuiltInType elementType, long authored)
+ {
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdValue.From(authored), Mapping(elementType, ValueRanks.Scalar), 0, out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceFailsClosedForAnOutOfRangeArrayElement()
+ {
+ // One unrepresentable element must fail the whole array, not be silently defaulted.
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdTestHelpers.IntegerArray(1L, 300L),
+ Mapping(BuiltInType.SByte, ValueRanks.OneDimension),
+ 0,
+ out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceFailsClosedForAnOutOfRangeMatrixElement()
+ {
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdTestHelpers.Array(UsdTestHelpers.IntegerTuple(1L, 300L)),
+ Mapping(BuiltInType.SByte, ValueRanks.TwoDimensions),
+ 2,
+ out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceFailsClosedForAStructuredLeafInATextArray()
+ {
+ // A dictionary has no faithful scalar text, so a string array holding one is left
+ // unresolved rather than rendered to a plausible-but-wrong element.
+ UsdValue nested = UsdTestHelpers.Dictionary(
+ new System.Collections.Generic.KeyValuePair(
+ "k", UsdValue.From(1L)));
+
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdTestHelpers.Array(UsdValue.FromString("a"), nested),
+ Mapping(BuiltInType.String, ValueRanks.OneDimension),
+ 0,
+ out _),
+ Is.False);
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdTestHelpers.Array(UsdTestHelpers.Tuple(UsdValue.FromString("a"), nested)),
+ Mapping(BuiltInType.String, ValueRanks.TwoDimensions),
+ 2,
+ out _),
+ Is.False);
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ nested, Mapping(BuiltInType.String, ValueRanks.Scalar), 0, out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceRendersEveryLeafKindIntoText()
+ {
+ // A string-bound attribute accepts any leaf with a well-defined textual form.
+ UsdValue authored = UsdTestHelpers.Array(
+ UsdValue.Null,
+ UsdValue.FromToken("token"),
+ UsdValue.From(true),
+ UsdValue.From(3L),
+ UsdValue.From(0.5));
+
+ bool ok = UsdValueCoercion.TryCoerce(
+ authored, Mapping(BuiltInType.String, ValueRanks.OneDimension), 0, out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TryGetValue(out ArrayOf text), Is.True);
+ Assert.That(text.ToArray(), Is.EqualTo(new[] { string.Empty, "token", "true", "3", "0.5" }));
+ }
+
+ [TestCase("2.5", 2.5)]
+ [TestCase("-0.25", -0.25)]
+ public void CoerceReadsANumberAuthoredAsText(string authored, double expected)
+ {
+ bool ok = UsdValueCoercion.TryCoerce(
+ UsdValue.FromString(authored),
+ Mapping(BuiltInType.Double, ValueRanks.Scalar),
+ 0,
+ out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TryGetValue(out double d), Is.True);
+ Assert.That(d, Is.EqualTo(expected));
+ }
+
+ [Test]
+ public void CoerceFailsClosedForTextThatIsNotANumber()
+ {
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdValue.FromString("not a number"),
+ Mapping(BuiltInType.Double, ValueRanks.Scalar),
+ 0,
+ out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceReadsABooleanAuthoredAsANumberOrAsText()
+ {
+ bool fromNumber = UsdValueCoercion.TryCoerce(
+ UsdValue.From(1L), Mapping(BuiltInType.Boolean, ValueRanks.Scalar), 0, out Variant a);
+ bool fromText = UsdValueCoercion.TryCoerce(
+ UsdValue.FromToken("true"),
+ Mapping(BuiltInType.Boolean, ValueRanks.Scalar),
+ 0,
+ out Variant b);
+
+ Assert.That(fromNumber, Is.True);
+ Assert.That(a.TryGetValue(out bool first), Is.True);
+ Assert.That(first, Is.True);
+ Assert.That(fromText, Is.True);
+ Assert.That(b.TryGetValue(out bool second), Is.True);
+ Assert.That(second, Is.True);
+ }
+
+ [Test]
+ public void CoerceFailsClosedForTextThatIsNotABoolean()
+ {
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdValue.FromToken("maybe"),
+ Mapping(BuiltInType.Boolean, ValueRanks.Scalar),
+ 0,
+ out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceFailsClosedForAFloatThatOverflowsTheElement()
+ {
+ // A double that does not fit a float must not be published as an infinity.
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdValue.From(1e300),
+ Mapping(BuiltInType.Float, ValueRanks.Scalar),
+ 0,
+ out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceKeepsAnInfinityThatWasAuthoredAsOne()
+ {
+ bool ok = UsdValueCoercion.TryCoerce(
+ UsdValue.From(double.PositiveInfinity),
+ Mapping(BuiltInType.Float, ValueRanks.Scalar),
+ 0,
+ out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TryGetValue(out float f), Is.True);
+ Assert.That(float.IsPositiveInfinity(f), Is.True);
+ }
+
+ [Test]
+ public void CoerceFailsClosedForANumberTooLargeForAnInteger()
+ {
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdValue.From(1e30),
+ Mapping(BuiltInType.Int64, ValueRanks.Scalar),
+ 0,
+ out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceFailsClosedForAFixedSizeTypeOfTheWrongArity()
+ {
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdTestHelpers.NumberTuple(1.0, 2.0),
+ Mapping(BuiltInType.Double, ValueRanks.OneDimension),
+ 3,
+ out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceFailsClosedForAMatrixRowOfTheWrongArity()
+ {
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdTestHelpers.Array(UsdTestHelpers.NumberTuple(1.0, 2.0)),
+ Mapping(BuiltInType.Double, ValueRanks.TwoDimensions),
+ 3,
+ out _),
+ Is.False);
+ }
+
+ [Test]
+ public void CoerceTreatsAScalarAsASingleElementSequence()
+ {
+ bool ok = UsdValueCoercion.TryCoerce(
+ UsdValue.From(4L), Mapping(BuiltInType.Int32, ValueRanks.OneDimension), 0, out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TryGetValue(out ArrayOf items), Is.True);
+ Assert.That(items.ToArray(), Is.EqualTo(new[] { 4 }));
+ }
+
+ [Test]
+ public void CoerceCarriesAnAbsentArrayElementAsTheElementDefault()
+ {
+ bool ok = UsdValueCoercion.TryCoerce(
+ UsdTestHelpers.Array(UsdValue.From(1L), UsdValue.Null),
+ Mapping(BuiltInType.Int32, ValueRanks.OneDimension),
+ 0,
+ out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TryGetValue(out ArrayOf items), Is.True);
+ Assert.That(items.ToArray(), Is.EqualTo(new[] { 1, 0 }));
+ }
+
+ [Test]
+ public void CoerceReadsABooleanAsANumber()
+ {
+ // A bool authored where a number is bound widens to 1/0 rather than failing.
+ bool ok = UsdValueCoercion.TryCoerce(
+ UsdValue.From(true), Mapping(BuiltInType.Int32, ValueRanks.Scalar), 0, out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TryGetValue(out int i), Is.True);
+ Assert.That(i, Is.EqualTo(1));
+ }
+
+ [Test]
+ public void CoerceReadsAnIntegerAuthoredAsADouble()
+ {
+ bool ok = UsdValueCoercion.TryCoerce(
+ UsdValue.From(3.0), Mapping(BuiltInType.Int64, ValueRanks.Scalar), 0, out Variant v);
+
+ Assert.That(ok, Is.True);
+ Assert.That(v.TryGetValue(out long l), Is.True);
+ Assert.That(l, Is.EqualTo(3L));
+ }
+
+ [TestCase(BuiltInType.Boolean)]
+ [TestCase(BuiltInType.SByte)]
+ [TestCase(BuiltInType.Int32)]
+ [TestCase(BuiltInType.Int64)]
+ [TestCase(BuiltInType.UInt32)]
+ [TestCase(BuiltInType.UInt64)]
+ [TestCase(BuiltInType.Float)]
+ [TestCase(BuiltInType.Double)]
+ public void CoerceFailsClosedForAStructuredValueBoundToAScalar(BuiltInType elementType)
+ {
+ // A dictionary is neither a number nor text, so no scalar element type may invent a
+ // value for it.
+ UsdValue structured = UsdTestHelpers.Dictionary(
+ new System.Collections.Generic.KeyValuePair(
+ "k", UsdValue.From(1L)));
+
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ structured, Mapping(elementType, ValueRanks.Scalar), 0, out _),
+ Is.False);
+ }
+
+ [Test]
+ public void DecoerceReadsAScalarOfEveryElementType(
+ [ValueSource(nameof(s_elementTypes))] BuiltInType elementType)
+ {
+ bool ok = UsdValueCoercion.TryCoerce(
+ Authored(elementType), Mapping(elementType, ValueRanks.Scalar), 0, out Variant v);
+ Assert.That(ok, Is.True);
+
+ UsdValue read = UsdValueCoercion.Decoerce(v);
+
+ Assert.That(read.IsNull, Is.False);
+ // Round trips through the same binding, which is what an export has to reproduce.
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ read, Mapping(elementType, ValueRanks.Scalar), 0, out Variant again),
+ Is.True);
+ AssertScalar(elementType, again);
+ }
+
+ [Test]
+ public void DecoerceReadsAnArrayOfEveryElementType(
+ [ValueSource(nameof(s_elementTypes))] BuiltInType elementType)
+ {
+ UsdValue authored = UsdTestHelpers.Array(Authored(elementType), Authored(elementType));
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ authored, Mapping(elementType, ValueRanks.OneDimension), 0, out Variant v),
+ Is.True);
+
+ UsdValue read = UsdValueCoercion.Decoerce(v);
+
+ Assert.That(read.TryGetArray(out ArrayOf items), Is.True);
+ Assert.That(items.Count, Is.EqualTo(2));
+ }
+
+ [Test]
+ public void DecoerceRegroupsAMatrixOfEveryElementTypeIntoRows(
+ [ValueSource(nameof(s_elementTypes))] BuiltInType elementType)
+ {
+ UsdValue row = UsdTestHelpers.Tuple(Authored(elementType), Authored(elementType));
+ Assert.That(
+ UsdValueCoercion.TryCoerce(
+ UsdTestHelpers.Array(row, row),
+ Mapping(elementType, ValueRanks.TwoDimensions),
+ 2,
+ out Variant v),
+ Is.True);
+
+ UsdValue read = UsdValueCoercion.Decoerce(v);
+
+ // A matrix is handed back as one tuple per row so the writer can author "[(a, b), …]".
+ Assert.That(read.TryGetArray(out ArrayOf rows), Is.True);
+ Assert.That(rows.Count, Is.EqualTo(2));
+ Assert.That(rows[0].TryGetTuple(out ArrayOf cells), Is.True);
+ Assert.That(cells.Count, Is.EqualTo(2));
+ }
+
+ [Test]
+ public void DecoerceReturnsNullForAnUnrepresentableValue()
+ {
+ // Every rank of a type with no USD spelling must decoerce to an absent value rather
+ // than a CLR rendering of it.
+ // UInt16 is a valid Variant type with no USD spelling: every rank must decoerce to an
+ // absent value rather than a CLR rendering of it.
+ Assert.That(UsdValueCoercion.Decoerce(Variant.From((ushort)1)).IsNull, Is.True);
+ Assert.That(
+ UsdValueCoercion.Decoerce(
+ Variant.From((ArrayOf)new ushort[] { 1 })).IsNull,
+ Is.True);
+ Assert.That(
+ UsdValueCoercion.Decoerce(
+ Variant.From((MatrixOf)new ushort[,] { { 1 } })).IsNull,
+ Is.True);
+ }
+
+ [Test]
+ public void DecoerceReturnsNullForAnAbsentValue()
+ {
+ Assert.That(UsdValueCoercion.Decoerce(default).IsNull, Is.True);
+ }
+ }
+}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdValueTests.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdValueTests.cs
new file mode 100644
index 0000000000..955604279e
--- /dev/null
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdValueTests.cs
@@ -0,0 +1,454 @@
+/* ========================================================================
+ * Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved.
+ *
+ * OPC Foundation MIT License 1.00
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * The complete license agreement can be found here:
+ * http://opcfoundation.org/License/MIT/1.00/
+ * ======================================================================*/
+
+using System.Collections.Generic;
+using NUnit.Framework;
+using Opc.Ua.OpenUsdScene.Scene;
+
+namespace Opc.Ua.OpenUsdScene.Tests
+{
+ ///
+ /// Tests for , the union that scopes an authored USD value to the
+ /// shapes a .usda document can express.
+ ///
+ [TestFixture]
+ [Category("OpenUsd")]
+ [Parallelizable]
+ public class UsdValueTests
+ {
+ [Test]
+ public void DefaultValueIsNull()
+ {
+ UsdValue value = default;
+
+ Assert.That(value.IsNull, Is.True);
+ Assert.That(value.Kind, Is.EqualTo(UsdValueKind.Null));
+ Assert.That(value, Is.EqualTo(UsdValue.Null));
+ }
+
+ [Test]
+ public void BooleanRoundTrips()
+ {
+ UsdValue value = UsdValue.From(true);
+
+ Assert.That(value.Kind, Is.EqualTo(UsdValueKind.Boolean));
+ Assert.That(value.IsNull, Is.False);
+ Assert.That(value.TryGetBoolean(out bool b), Is.True);
+ Assert.That(b, Is.True);
+ }
+
+ [Test]
+ public void IntegerRoundTripsWithoutPrecisionLoss()
+ {
+ const long large = 9007199254740993L;
+ UsdValue value = UsdValue.From(large);
+
+ Assert.That(value.TryGetInteger(out long l), Is.True);
+ Assert.That(l, Is.EqualTo(large),
+ "An integer must not be widened through a double.");
+ }
+
+ [Test]
+ public void DoubleRoundTrips()
+ {
+ UsdValue value = UsdValue.From(1.5);
+
+ Assert.That(value.TryGetDouble(out double d), Is.True);
+ Assert.That(d, Is.EqualTo(1.5));
+ }
+
+ [Test]
+ public void TryGetNumberWidensAnInteger()
+ {
+ Assert.That(UsdValue.From(7L).TryGetNumber(out double fromInteger), Is.True);
+ Assert.That(fromInteger, Is.EqualTo(7.0));
+ Assert.That(UsdValue.From(2.5).TryGetNumber(out double fromDouble), Is.True);
+ Assert.That(fromDouble, Is.EqualTo(2.5));
+ }
+
+ [Test]
+ public void AccessorsRejectTheWrongKind()
+ {
+ UsdValue value = UsdValue.From(1.5);
+
+ Assert.That(value.TryGetInteger(out _), Is.False);
+ Assert.That(value.TryGetBoolean(out _), Is.False);
+ Assert.That(value.TryGetString(out _), Is.False);
+ Assert.That(value.TryGetArray(out _), Is.False);
+ }
+
+ ///
+ /// A string, a token, an asset path and a path reference all carry text but are printed
+ /// differently by USD, so they must stay distinguishable.
+ ///
+ [Test]
+ public void TextKindsStayDistinct()
+ {
+ UsdValue s = UsdValue.FromString("x");
+ UsdValue token = UsdValue.FromToken("x");
+ UsdValue asset = UsdValue.FromAssetPath("x");
+ UsdValue path = UsdValue.FromPathReference("x");
+
+ Assert.That(s.Kind, Is.EqualTo(UsdValueKind.String));
+ Assert.That(token.Kind, Is.EqualTo(UsdValueKind.Token));
+ Assert.That(asset.Kind, Is.EqualTo(UsdValueKind.AssetPath));
+ Assert.That(path.Kind, Is.EqualTo(UsdValueKind.PathReference));
+
+ Assert.That(s.TryGetToken(out _), Is.False);
+ Assert.That(token.TryGetString(out _), Is.False);
+ Assert.That(s, Is.Not.EqualTo(token));
+
+ foreach (UsdValue value in new[] { s, token, asset, path })
+ {
+ Assert.That(value.TryGetText(out string text), Is.True);
+ Assert.That(text, Is.EqualTo("x"));
+ }
+ }
+
+ [Test]
+ public void NullTextProducesANullValue()
+ {
+ Assert.That(UsdValue.FromString(null).IsNull, Is.True);
+ Assert.That(UsdValue.FromToken(null).IsNull, Is.True);
+ Assert.That(UsdValue.FromAssetPath(null).IsNull, Is.True);
+ Assert.That(UsdValue.FromPathReference(null).IsNull, Is.True);
+ }
+
+ ///
+ /// A tuple prints as (a, b) and an array as [a, b], so the two must not
+ /// compare equal even when they carry the same components.
+ ///
+ [Test]
+ public void TupleAndArrayStayDistinct()
+ {
+ ArrayOf items = new[] { UsdValue.From(1L), UsdValue.From(2L) }.ToArrayOf();
+ UsdValue tuple = UsdValue.FromTuple(items);
+ UsdValue array = UsdValue.FromArray(items);
+
+ Assert.That(tuple.Kind, Is.EqualTo(UsdValueKind.Tuple));
+ Assert.That(array.Kind, Is.EqualTo(UsdValueKind.Array));
+ Assert.That(tuple, Is.Not.EqualTo(array));
+ Assert.That(tuple.TryGetArray(out _), Is.False);
+ Assert.That(array.TryGetTuple(out _), Is.False);
+ }
+
+ [Test]
+ public void TryGetItemsAcceptsEveryCompositeKind()
+ {
+ ArrayOf items = new[] { UsdValue.From(1L) }.ToArrayOf();
+
+ Assert.That(UsdValue.FromTuple(items).TryGetItems(out _), Is.True);
+ Assert.That(UsdValue.FromArray(items).TryGetItems(out _), Is.True);
+ Assert.That(UsdValue.FromMatrix(items).TryGetItems(out _), Is.True);
+ Assert.That(UsdValue.From(1L).TryGetItems(out _), Is.False);
+ }
+
+ ///
+ /// The nesting a cannot express - an array whose elements are
+ /// themselves tuples, as authored for color3f[].
+ ///
+ [Test]
+ public void ArrayOfTuplesNests()
+ {
+ UsdValue row = UsdValue.FromTuple(
+ new[] { UsdValue.From(1.0), UsdValue.From(2.0), UsdValue.From(3.0) }.ToArrayOf());
+ UsdValue array = UsdValue.FromArray(new[] { row, row }.ToArrayOf());
+
+ Assert.That(array.TryGetArray(out ArrayOf rows), Is.True);
+ Assert.That(rows.Count, Is.EqualTo(2));
+ Assert.That(rows[0].TryGetTuple(out ArrayOf components), Is.True);
+ Assert.That(components.Count, Is.EqualTo(3));
+ Assert.That(components[2].TryGetDouble(out double third), Is.True);
+ Assert.That(third, Is.EqualTo(3.0));
+ }
+
+ [Test]
+ public void DictionaryRoundTrips()
+ {
+ var entries = new Dictionary(System.StringComparer.Ordinal)
+ {
+ ["author"] = UsdValue.FromString("acme"),
+ ["order"] = UsdValue.From(3L)
+ };
+
+ UsdValue value = UsdValue.FromDictionary(entries);
+
+ Assert.That(value.Kind, Is.EqualTo(UsdValueKind.Dictionary));
+ Assert.That(value.TryGetDictionary(out IReadOnlyDictionary read),
+ Is.True);
+ Assert.That(read, Has.Count.EqualTo(2));
+ Assert.That(read["order"].TryGetInteger(out long order), Is.True);
+ Assert.That(order, Is.EqualTo(3L));
+ }
+
+ [Test]
+ public void EqualValuesShareAHashCode()
+ {
+ UsdValue first = UsdValue.FromTuple(
+ new[] { UsdValue.From(1.0), UsdValue.FromString("a") }.ToArrayOf());
+ UsdValue second = UsdValue.FromTuple(
+ new[] { UsdValue.From(1.0), UsdValue.FromString("a") }.ToArrayOf());
+
+ Assert.That(first, Is.EqualTo(second));
+ bool operatorEquals = first == second;
+ bool operatorNotEquals = first != second;
+ Assert.That(operatorEquals, Is.True);
+ Assert.That(operatorNotEquals, Is.False);
+ Assert.That(first.GetHashCode(), Is.EqualTo(second.GetHashCode()));
+ }
+
+ [Test]
+ public void DifferingComponentsAreNotEqual()
+ {
+ UsdValue first = UsdValue.FromTuple(new[] { UsdValue.From(1.0) }.ToArrayOf());
+ UsdValue second = UsdValue.FromTuple(new[] { UsdValue.From(2.0) }.ToArrayOf());
+
+ Assert.That(first, Is.Not.EqualTo(second));
+ }
+
+ [Test]
+ public void EqualDictionariesShareAHashCodeWhateverTheEntryOrder()
+ {
+ UsdValue first = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal)
+ {
+ ["author"] = UsdValue.FromString("acme"),
+ ["order"] = UsdValue.From(3L)
+ });
+ UsdValue second = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal)
+ {
+ ["order"] = UsdValue.From(3L),
+ ["author"] = UsdValue.FromString("acme")
+ });
+
+ Assert.That(first, Is.EqualTo(second));
+ Assert.That(first.GetHashCode(), Is.EqualTo(second.GetHashCode()));
+ }
+
+ [Test]
+ public void DictionariesOfTheSameSizeDoNotShareAHashCode()
+ {
+ // The hash must take the entries into account, not only their count, or every
+ // dictionary of the same size would collide in a hash based collection.
+ UsdValue first = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal)
+ {
+ ["author"] = UsdValue.FromString("acme"),
+ ["order"] = UsdValue.From(3L)
+ });
+ UsdValue second = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal)
+ {
+ ["author"] = UsdValue.FromString("globex"),
+ ["order"] = UsdValue.From(4L)
+ });
+
+ Assert.That(first, Is.Not.EqualTo(second));
+ Assert.That(first.GetHashCode(), Is.Not.EqualTo(second.GetHashCode()));
+ }
+
+ [Test]
+ public void DictionaryRendersItsEntriesOrderedByKey()
+ {
+ UsdValue value = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal)
+ {
+ ["order"] = UsdValue.From(3L),
+ ["author"] = UsdValue.FromString("acme"),
+ ["nested"] = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal)
+ {
+ ["depth"] = UsdValue.From(1L)
+ })
+ });
+
+ // A dictionary must not stringify to the empty string: a caller that falls back to
+ // the textual form would silently drop the authored entries.
+ Assert.That(value.ToString(), Is.EqualTo("{author: acme, nested: {depth: 1}, order: 3}"));
+ }
+
+ [Test]
+ public void TryGetMatrixReadsTheRows()
+ {
+ UsdValue value = UsdValue.FromMatrix(
+ new[]
+ {
+ UsdValue.FromTuple(new[] { UsdValue.From(1.0), UsdValue.From(0.0) }.ToArrayOf()),
+ UsdValue.FromTuple(new[] { UsdValue.From(0.0), UsdValue.From(1.0) }.ToArrayOf())
+ }.ToArrayOf());
+
+ Assert.That(value.Kind, Is.EqualTo(UsdValueKind.Matrix));
+ Assert.That(value.TryGetMatrix(out ArrayOf rows), Is.True);
+ Assert.That(rows.Count, Is.EqualTo(2));
+ Assert.That(value.TryGetArray(out ArrayOf _), Is.False);
+ }
+
+ [Test]
+ public void TryGetNumberRejectsANonNumericKind()
+ {
+ Assert.That(UsdValue.FromString("1.5").TryGetNumber(out double value), Is.False);
+ Assert.That(value, Is.Zero);
+ Assert.That(UsdValue.Null.TryGetNumber(out double absent), Is.False);
+ Assert.That(absent, Is.Zero);
+ }
+
+ [Test]
+ public void ScalarsRenderTheirInvariantForm()
+ {
+ Assert.That(UsdValue.Null.ToString(), Is.Empty);
+ Assert.That(UsdValue.From(true).ToString(), Is.EqualTo("true"));
+ Assert.That(UsdValue.From(false).ToString(), Is.EqualTo("false"));
+ Assert.That(UsdValue.From(-3L).ToString(), Is.EqualTo("-3"));
+ Assert.That(UsdValue.From(0.5).ToString(), Is.EqualTo("0.5"));
+ Assert.That(UsdValue.FromToken("vertex").ToString(), Is.EqualTo("vertex"));
+ Assert.That(UsdValue.FromAssetPath("./a.usda").ToString(), Is.EqualTo("./a.usda"));
+ Assert.That(UsdValue.FromPathReference("/P/A").ToString(), Is.EqualTo("/P/A"));
+ }
+
+ [Test]
+ public void CompositesRenderTheirItems()
+ {
+ UsdValue tuple = UsdValue.FromTuple(
+ new[] { UsdValue.From(1.0), UsdValue.FromString("a") }.ToArrayOf());
+ UsdValue array = UsdValue.FromArray(
+ new[] { UsdValue.From(1L), UsdValue.From(2L) }.ToArrayOf());
+ UsdValue matrix = UsdValue.FromMatrix(new[] { tuple }.ToArrayOf());
+
+ Assert.That(tuple.ToString(), Is.EqualTo("(1, a)"));
+ Assert.That(array.ToString(), Is.EqualTo("[1, 2]"));
+ Assert.That(matrix.ToString(), Is.EqualTo("((1, a))"));
+ Assert.That(UsdValue.FromArray(default).ToString(), Is.EqualTo("[]"));
+ }
+
+ [Test]
+ public void AnEmptyDictionaryRendersAsEmptyBraces()
+ {
+ UsdValue value = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal));
+
+ Assert.That(value.ToString(), Is.EqualTo("{}"));
+ }
+
+ [Test]
+ public void CompositesOfDifferentLengthsAreNotEqual()
+ {
+ UsdValue first = UsdValue.FromArray(new[] { UsdValue.From(1L) }.ToArrayOf());
+ UsdValue second = UsdValue.FromArray(
+ new[] { UsdValue.From(1L), UsdValue.From(2L) }.ToArrayOf());
+
+ Assert.That(first, Is.Not.EqualTo(second));
+ }
+
+ [Test]
+ public void DictionariesOfDifferentSizesAreNotEqual()
+ {
+ UsdValue first = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal)
+ {
+ ["a"] = UsdValue.From(1L)
+ });
+ UsdValue second = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal)
+ {
+ ["a"] = UsdValue.From(1L),
+ ["b"] = UsdValue.From(2L)
+ });
+
+ Assert.That(first, Is.Not.EqualTo(second));
+ }
+
+ [Test]
+ public void DictionariesThatDifferInAKeyAreNotEqual()
+ {
+ UsdValue first = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal)
+ {
+ ["a"] = UsdValue.From(1L)
+ });
+ UsdValue second = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal)
+ {
+ ["b"] = UsdValue.From(1L)
+ });
+
+ Assert.That(first, Is.Not.EqualTo(second));
+ }
+
+ [Test]
+ public void EmptyDictionariesAreEqualAndShareAHashCode()
+ {
+ UsdValue first = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal));
+ UsdValue second = UsdValue.FromDictionary(
+ new Dictionary(System.StringComparer.Ordinal));
+
+ Assert.That(first, Is.EqualTo(second));
+ Assert.That(first.GetHashCode(), Is.EqualTo(second.GetHashCode()));
+ }
+
+ [Test]
+ public void EveryKindProducesAHashCode()
+ {
+ // An absent value and every text kind must hash without reading an unset payload.
+ UsdValue absent = UsdValue.Null;
+ UsdValue alsoAbsent = default;
+ UsdValue path = UsdValue.FromPathReference("/P");
+ UsdValue samePath = UsdValue.FromPathReference("/P");
+ UsdValue number = UsdValue.From(1.5);
+ UsdValue sameNumber = UsdValue.From(1.5);
+ UsdValue flag = UsdValue.From(true);
+ UsdValue sameFlag = UsdValue.From(true);
+
+ Assert.That(absent.GetHashCode(), Is.EqualTo(alsoAbsent.GetHashCode()));
+ Assert.That(path.GetHashCode(), Is.EqualTo(samePath.GetHashCode()));
+ Assert.That(number.GetHashCode(), Is.EqualTo(sameNumber.GetHashCode()));
+ Assert.That(flag.GetHashCode(), Is.EqualTo(sameFlag.GetHashCode()));
+ }
+
+ [Test]
+ public void EqualsAcceptsABoxedValueAndRejectsAnotherType()
+ {
+ object boxed = UsdValue.From(1L);
+
+ bool matchesBoxed = UsdValue.From(1L).Equals(boxed);
+ bool matchesOtherType = UsdValue.From(1L).Equals("1");
+
+ Assert.That(matchesBoxed, Is.True);
+ Assert.That(matchesOtherType, Is.False);
+ }
+
+ [Test]
+ public void ValuesOfDifferentKindsAreNotEqual()
+ {
+ Assert.That(UsdValue.From(1L), Is.Not.EqualTo(UsdValue.From(1.0)));
+ Assert.That(UsdValue.From(true), Is.Not.EqualTo(UsdValue.From(1L)));
+ }
+ }
+}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdaReaderCellTests.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdaReaderCellTests.cs
index 5be0b5c193..9d03624072 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/UsdaReaderCellTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdaReaderCellTests.cs
@@ -30,6 +30,7 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using Opc.Ua;
using Opc.Ua.OpenUsdScene.Scene;
namespace Opc.Ua.OpenUsdScene.Tests
@@ -71,7 +72,7 @@ public void CellRoot_HasCustomOverrideAttribute()
UsdAttribute over = UsdTestHelpers.RequireAttribute(cell, "inputs:speedOverride");
Assert.That(over.Custom, Is.True);
Assert.That(over.TypeName, Is.EqualTo("double"));
- Assert.That(over.Value, Is.EqualTo(100L));
+ UsdTestHelpers.AssertInteger(over.Value, 100L);
}
[Test]
@@ -82,12 +83,14 @@ public void SafetyBeacon_TokenAndColorValues()
UsdAttribute visibility = UsdTestHelpers.RequireAttribute(beacon, "visibility");
Assert.That(visibility.TypeName, Is.EqualTo("token"));
- Assert.That(visibility.Value, Is.EqualTo("invisible"));
+ UsdTestHelpers.AssertText(visibility.Value, "invisible");
UsdAttribute color = UsdTestHelpers.RequireAttribute(beacon, "primvars:displayColor");
- var outer = color.Value as List;
- Assert.That(outer, Is.Not.Null);
- Assert.That(outer![0] as object?[], Is.EqualTo(new object?[] { 1L, 0L, 0L }));
+ Assert.That(color.Value.TryGetArray(out ArrayOf outer), Is.True);
+ Assert.That(outer.Count, Is.EqualTo(1));
+ Assert.That(outer[0].TryGetTuple(out ArrayOf tuple), Is.True);
+ Assert.That(tuple.ToArray()!.Select(v => v.TryGetInteger(out long integer) ? integer : -1L).ToArray(),
+ Is.EqualTo(new[] { 1L, 0L, 0L }));
}
[Test]
@@ -98,11 +101,13 @@ public void KeyLight_NumericAndTupleValues()
UsdAttribute intensity = UsdTestHelpers.RequireAttribute(key, "intensity");
Assert.That(intensity.TypeName, Is.EqualTo("float"));
- Assert.That(intensity.Value, Is.EqualTo(650L));
+ UsdTestHelpers.AssertInteger(intensity.Value, 650L);
UsdAttribute rotate = UsdTestHelpers.RequireAttribute(key, "xformOp:rotateXYZ");
Assert.That(rotate.TypeName, Is.EqualTo("double3"));
- Assert.That(rotate.Value as object?[], Is.EqualTo(new object?[] { -45L, 0L, 35L }));
+ Assert.That(rotate.Value.TryGetTuple(out ArrayOf rotateValues), Is.True);
+ Assert.That(rotateValues.ToArray()!.Select(v => v.TryGetInteger(out long integer) ? integer : 0L).ToArray(),
+ Is.EqualTo(new[] { -45L, 0L, 35L }));
}
[TestCase(R1)]
@@ -169,7 +174,7 @@ public void MergedJoint_RotateAttributeIsLive()
UsdPrim j2 = UsdTestHelpers.RequirePrim(_stage, R1 + "/Base/J1/J2");
UsdAttribute rotateY = UsdTestHelpers.RequireAttribute(j2, "xformOp:rotateY");
Assert.That(rotateY.Live, Is.True);
- Assert.That(rotateY.Value, Is.EqualTo(-30L));
+ UsdTestHelpers.AssertInteger(rotateY.Value, -30L);
}
[Test]
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdaReaderPlantTests.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdaReaderPlantTests.cs
index 4a4c2285e4..5142bf235e 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/UsdaReaderPlantTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdaReaderPlantTests.cs
@@ -30,6 +30,7 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
+using Opc.Ua;
using Opc.Ua.OpenUsdScene.Scene;
namespace Opc.Ua.OpenUsdScene.Tests
@@ -126,25 +127,26 @@ public void Body_AttributeTypeNamesAndValues()
UsdAttribute axis = UsdTestHelpers.RequireAttribute(body, "axis");
Assert.That(axis.TypeName, Is.EqualTo("token"));
Assert.That(axis.Variability, Is.EqualTo(UsdVariabilityEnum.Uniform));
- Assert.That(axis.Value, Is.EqualTo("Z"));
+ UsdTestHelpers.AssertText(axis.Value, "Z");
UsdAttribute radius = UsdTestHelpers.RequireAttribute(body, "radius");
Assert.That(radius.TypeName, Is.EqualTo("double"));
- Assert.That(radius.Value, Is.EqualTo(0.5));
+ UsdTestHelpers.AssertDouble(radius.Value, 0.5);
UsdAttribute color = UsdTestHelpers.RequireAttribute(body, "primvars:displayColor");
Assert.That(color.TypeName, Is.EqualTo("color3f[]"));
- var outer = color.Value as List;
- Assert.That(outer, Is.Not.Null);
- Assert.That(outer!, Has.Count.EqualTo(1));
- var tuple = outer[0] as object?[];
- Assert.That(tuple, Is.Not.Null);
- Assert.That(tuple!, Is.EqualTo(new object?[] { 0L, 0L, 1L }));
+ Assert.That(color.Value.TryGetArray(out ArrayOf outer), Is.True);
+ Assert.That(outer, Has.Count.EqualTo(1));
+ Assert.That(outer[0].TryGetTuple(out ArrayOf tuple), Is.True);
+ Assert.That(tuple.ToArray()!.Select(v => v.TryGetInteger(out long integer) ? integer : -1L).ToArray(),
+ Is.EqualTo(new[] { 0L, 0L, 1L }));
UsdAttribute order = UsdTestHelpers.RequireAttribute(body, "xformOpOrder");
Assert.That(order.TypeName, Is.EqualTo("token[]"));
Assert.That(order.Variability, Is.EqualTo(UsdVariabilityEnum.Uniform));
- Assert.That(order.Value, Is.EqualTo(new List { "xformOp:translate" }));
+ Assert.That(order.Value.TryGetArray(out ArrayOf orderValues), Is.True);
+ Assert.That(orderValues.ToArray()!.Select(v => v.TryGetText(out string token) ? token : string.Empty).ToArray(),
+ Is.EqualTo(new[] { "xformOp:translate" }));
}
[Test]
@@ -156,15 +158,16 @@ public void Impeller_LiveAndCustomAttributes()
Assert.That(rotateZ.TypeName, Is.EqualTo("double"));
Assert.That(rotateZ.Variability, Is.EqualTo(UsdVariabilityEnum.Varying));
Assert.That(rotateZ.Live, Is.True);
- Assert.That(rotateZ.Value, Is.TypeOf());
- Assert.That(rotateZ.Value, Is.Zero);
+ UsdTestHelpers.AssertInteger(rotateZ.Value, 0L);
UsdAttribute setpoint = UsdTestHelpers.RequireAttribute(impeller, "inputs:speedSetpoint");
Assert.That(setpoint.Custom, Is.True);
Assert.That(setpoint.TypeName, Is.EqualTo("double"));
UsdAttribute order = UsdTestHelpers.RequireAttribute(impeller, "xformOpOrder");
- Assert.That(order.Value, Is.EqualTo(new List { "xformOp:translate", "xformOp:rotateZ" }));
+ Assert.That(order.Value.TryGetArray(out ArrayOf orderValues), Is.True);
+ Assert.That(orderValues.ToArray()!.Select(v => v.TryGetText(out string token) ? token : string.Empty).ToArray(),
+ Is.EqualTo(new[] { "xformOp:translate", "xformOp:rotateZ" }));
}
[Test]
@@ -186,7 +189,7 @@ public void Material_ConnectionIsParsed()
UsdAttribute surface = UsdTestHelpers.RequireAttribute(mat, "outputs:surface");
Assert.That(surface.TypeName, Is.EqualTo("token"));
- Assert.That(surface.Value, Is.Null);
+ Assert.That(surface.Value.IsNull, Is.True);
Assert.That(surface.Connections, Is.EqualTo(new[]
{
"/Plant/Pumps/P101/StatusLight/Mat/Surface.outputs:surface",
@@ -202,17 +205,21 @@ public void Shader_ScalarTupleAndUnvaluedAttributes()
UsdAttribute id = UsdTestHelpers.RequireAttribute(shader, "info:id");
Assert.That(id.TypeName, Is.EqualTo("token"));
Assert.That(id.Variability, Is.EqualTo(UsdVariabilityEnum.Uniform));
- Assert.That(id.Value, Is.EqualTo("UsdPreviewSurface"));
+ UsdTestHelpers.AssertText(id.Value, "UsdPreviewSurface");
UsdAttribute diffuse = UsdTestHelpers.RequireAttribute(shader, "inputs:diffuseColor");
Assert.That(diffuse.TypeName, Is.EqualTo("color3f"));
- Assert.That(diffuse.Value as object?[], Is.EqualTo(new object?[] { 0.1, 0.1, 0.1 }));
+ Assert.That(diffuse.Value.TryGetTuple(out ArrayOf diffuseValues), Is.True);
+ Assert.That(diffuseValues.ToArray()!.Select(v => v.TryGetDouble(out double d) ? d : double.NaN).ToArray(),
+ Is.EqualTo(new[] { 0.1, 0.1, 0.1 }));
UsdAttribute emissive = UsdTestHelpers.RequireAttribute(shader, "inputs:emissiveColor");
- Assert.That(emissive.Value as object?[], Is.EqualTo(new object?[] { 0L, 0L, 0L }));
+ Assert.That(emissive.Value.TryGetTuple(out ArrayOf emissiveValues), Is.True);
+ Assert.That(emissiveValues.ToArray()!.Select(v => v.TryGetInteger(out long integer) ? integer : -1L).ToArray(),
+ Is.EqualTo(new[] { 0L, 0L, 0L }));
UsdAttribute outputs = UsdTestHelpers.RequireAttribute(shader, "outputs:surface");
- Assert.That(outputs.Value, Is.Null);
+ Assert.That(outputs.Value.IsNull, Is.True);
Assert.That(outputs.Connections, Is.Empty);
}
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdaValueParsingTests.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdaValueParsingTests.cs
index b9efee0b99..e5d2bf6677 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/UsdaValueParsingTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdaValueParsingTests.cs
@@ -29,7 +29,9 @@
using System.Collections.Generic;
using NUnit.Framework;
+using Opc.Ua;
using Opc.Ua.OpenUsdScene.Conversion;
+using Opc.Ua.OpenUsdScene.Scene;
namespace Opc.Ua.OpenUsdScene.Tests
{
@@ -42,7 +44,7 @@ public class UsdaValueParsingTests
[TestCase("0", 0L)]
public void Integers_ParseAsLong(string raw, long expected)
{
- Assert.That(UsdaReader.ParseValue(raw), Is.EqualTo(expected));
+ UsdTestHelpers.AssertInteger(UsdaReader.ParseValue(raw), expected);
}
[TestCase("3.14", 3.14)]
@@ -53,82 +55,81 @@ public void Integers_ParseAsLong(string raw, long expected)
[TestCase("6.02E2", 602.0)]
public void Floats_ParseAsDouble(string raw, double expected)
{
- object? value = UsdaReader.ParseValue(raw);
- Assert.That(value, Is.TypeOf());
- Assert.That((double)value!, Is.EqualTo(expected).Within(1e-12));
+ UsdValue value = UsdaReader.ParseValue(raw);
+ Assert.That(value.TryGetDouble(out double actual), Is.True);
+ Assert.That(actual, Is.EqualTo(expected).Within(1e-12));
}
[TestCase("true", true)]
[TestCase("false", false)]
public void Booleans_ParseAsBool(string raw, bool expected)
{
- Assert.That(UsdaReader.ParseValue(raw), Is.EqualTo(expected));
+ UsdTestHelpers.AssertBoolean(UsdaReader.ParseValue(raw), expected);
}
[Test]
public void QuotedString_IsUnwrapped()
{
- Assert.That(UsdaReader.ParseValue("\"hello world\""), Is.EqualTo("hello world"));
+ UsdTestHelpers.AssertString(UsdaReader.ParseValue("\"hello world\""), "hello world");
}
[Test]
public void QuotedString_WithSpecialCharacters()
{
- Assert.That(UsdaReader.ParseValue("\"a, (b) [c] \""), Is.EqualTo("a, (b) [c] "));
+ UsdTestHelpers.AssertString(UsdaReader.ParseValue("\"a, (b) [c] \""), "a, (b) [c] ");
}
[Test]
public void QuotedString_WithEscapedQuotes()
{
- Assert.That(UsdaReader.ParseValue("\"say \\\"hi\\\"\""), Is.EqualTo("say \"hi\""));
+ UsdTestHelpers.AssertString(UsdaReader.ParseValue("\"say \\\"hi\\\"\""), "say \"hi\"");
}
[Test]
public void BareToken_IsReturnedAsString()
{
- Assert.That(UsdaReader.ParseValue("inherited"), Is.EqualTo("inherited"));
+ UsdTestHelpers.AssertToken(UsdaReader.ParseValue("inherited"), "inherited");
}
[TestCase("@pump.usda@", "pump.usda")]
[TestCase("@./sub/robot.usda@", "./sub/robot.usda")]
public void AssetPath_IsUnwrapped(string raw, string expected)
{
- Assert.That(UsdaReader.ParseValue(raw), Is.EqualTo(expected));
+ UsdTestHelpers.AssertAssetPath(UsdaReader.ParseValue(raw), expected);
}
[Test]
public void PathReference_IsUnwrapped()
{
- Assert.That(UsdaReader.ParseValue(""), Is.EqualTo("/Plant/Pumps/P101"));
+ UsdTestHelpers.AssertPathReference(
+ UsdaReader.ParseValue(""),
+ "/Plant/Pumps/P101");
}
[Test]
public void IntegerTuple_ParsesToObjectArray()
{
- Assert.That(UsdaReader.ParseValue("(0, 0, 0)"), Is.EqualTo(new object?[] { 0L, 0L, 0L }));
- Assert.That(UsdaReader.ParseValue("(-45, 0, 35)"), Is.EqualTo(new object?[] { -45L, 0L, 35L }));
+ UsdTestHelpers.AssertIntegerItems(UsdaReader.ParseValue("(0, 0, 0)"), 0L, 0L, 0L);
+ UsdTestHelpers.AssertIntegerItems(UsdaReader.ParseValue("(-45, 0, 35)"), -45L, 0L, 35L);
}
[Test]
public void FloatTuple_ParsesToObjectArray()
{
- Assert.That(UsdaReader.ParseValue("(0.1, 0.1, 0.1)"), Is.EqualTo(new object?[] { 0.1, 0.1, 0.1 }));
+ UsdTestHelpers.AssertDoubleItems(UsdaReader.ParseValue("(0.1, 0.1, 0.1)"), 0.1, 0.1, 0.1);
}
[Test]
public void IntegerArray_ParsesToList()
{
- Assert.That(UsdaReader.ParseValue("[1, 2, 3]"), Is.EqualTo(new List { 1L, 2L, 3L }));
+ UsdTestHelpers.AssertIntegerItems(UsdaReader.ParseValue("[1, 2, 3]"), 1L, 2L, 3L);
}
[Test]
public void NestedTupleArray_ParsesToListOfArray()
{
- object? value = UsdaReader.ParseValue("[(0, 0, 1)]");
- var list = value as List;
- Assert.That(list, Is.Not.Null);
- Assert.That(list!, Has.Count.EqualTo(1));
- Assert.That(list[0] as object?[], Is.EqualTo(new object?[] { 0L, 0L, 1L }));
+ UsdValue value = UsdaReader.ParseValue("[(0, 0, 1)]");
+ UsdTestHelpers.AssertNestedIntegerItems(value, new[] { 0L, 0L, 1L });
}
[Test]
@@ -136,32 +137,34 @@ public void StringArray_ParsesToList()
{
Assert.That(
UsdaReader.ParseValue("[\"xformOp:translate\", \"xformOp:rotateZ\"]"),
- Is.EqualTo(new List { "xformOp:translate", "xformOp:rotateZ" }));
+ Is.EqualTo(UsdTestHelpers.StringArray("xformOp:translate", "xformOp:rotateZ")));
}
[Test]
public void MixedFloatTuple_HandlesLeadingDotAndExponent()
{
- object? value = UsdaReader.ParseValue("(0.5, -1.5e2, .25)");
- var tuple = value as object?[];
- Assert.That(tuple, Is.Not.Null);
- Assert.That(tuple!, Has.Length.EqualTo(3));
- Assert.That((double)tuple[0]!, Is.EqualTo(0.5).Within(1e-12));
- Assert.That((double)tuple[1]!, Is.EqualTo(-150.0).Within(1e-12));
- Assert.That((double)tuple[2]!, Is.EqualTo(0.25).Within(1e-12));
+ UsdValue value = UsdaReader.ParseValue("(0.5, -1.5e2, .25)");
+ Assert.That(value.TryGetTuple(out ArrayOf tuple), Is.True);
+ Assert.That(tuple, Has.Count.EqualTo(3));
+ Assert.That(tuple[0].TryGetDouble(out double first), Is.True);
+ Assert.That(tuple[1].TryGetDouble(out double second), Is.True);
+ Assert.That(tuple[2].TryGetDouble(out double third), Is.True);
+ Assert.That(first, Is.EqualTo(0.5).Within(1e-12));
+ Assert.That(second, Is.EqualTo(-150.0).Within(1e-12));
+ Assert.That(third, Is.EqualTo(0.25).Within(1e-12));
}
[TestCase("")]
[TestCase(" ")]
public void EmptyOrWhitespace_ParsesToNull(string raw)
{
- Assert.That(UsdaReader.ParseValue(raw), Is.Null);
+ Assert.That(UsdaReader.ParseValue(raw).IsNull, Is.True);
}
[Test]
public void Null_ParsesToNull()
{
- Assert.That(UsdaReader.ParseValue(null), Is.Null);
+ Assert.That(UsdaReader.ParseValue(null).IsNull, Is.True);
}
[Test]
diff --git a/tests/Opc.Ua.OpenUsd.Tests/UsdaWriterInjectionTests.cs b/tests/Opc.Ua.OpenUsd.Tests/UsdaWriterInjectionTests.cs
index 4cbf614018..afc2b7cfa7 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/UsdaWriterInjectionTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/UsdaWriterInjectionTests.cs
@@ -171,7 +171,7 @@ private static UsdStage CreateStage(string typeName, string value)
{
var stage = new UsdStage("Injection");
var prim = new UsdPrim("Target");
- prim.Attributes.Add(new UsdAttribute("label", typeName) { Value = value });
+ prim.Attributes.Add(new UsdAttribute("label", typeName) { Value = UsdValue.FromString(value) });
stage.AddRootPrim(prim);
return stage;
}
diff --git a/tests/Opc.Ua.OpenUsd.Tests/VariantBranchConversionTests.cs b/tests/Opc.Ua.OpenUsd.Tests/VariantBranchConversionTests.cs
index 704728be40..0922597162 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/VariantBranchConversionTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/VariantBranchConversionTests.cs
@@ -108,8 +108,8 @@ public void BranchBody_CapturesAttributes()
UsdPrim high = set.Variants.Single();
Assert.That(high.Attributes.Select(a => a.Name), Is.EqualTo(new[] { "resolution", "quality" }));
- Assert.That(high.Attributes[0].Value, Is.EqualTo(1024L));
- Assert.That(high.Attributes[1].Value, Is.EqualTo("best"));
+ UsdTestHelpers.AssertInteger(high.Attributes[0].Value, 1024L);
+ UsdTestHelpers.AssertString(high.Attributes[1].Value, "best");
}
[Test]
@@ -199,16 +199,16 @@ public void Branches_RoundTripThroughWriteAndReparse()
UsdStage stage = BuildStageWithBranches("high", "low");
// Give the branches distinct bodies so a lost or reordered branch is observable.
UsdVariantSet set = stage.Find("/P")!.VariantSets.Single();
- set.Variants[0].Attributes.Add(new UsdAttribute("resolution", "int") { Value = 1024L });
- set.Variants[1].Attributes.Add(new UsdAttribute("resolution", "int") { Value = 256L });
+ set.Variants[0].Attributes.Add(new UsdAttribute("resolution", "int") { Value = UsdValue.From(1024L) });
+ set.Variants[1].Attributes.Add(new UsdAttribute("resolution", "int") { Value = UsdValue.From(256L) });
string written = UsdaWriter.Write(stage);
UsdStage reparsed = UsdaReader.Parse(written, stage.StageName);
UsdVariantSet reSet = reparsed.Find("/P")!.VariantSets.Single();
Assert.That(reSet.Variants.Select(v => v.Name), Is.EqualTo(new[] { "high", "low" }));
- Assert.That(reSet.Variants[0].Attributes.Single().Value, Is.EqualTo(1024L));
- Assert.That(reSet.Variants[1].Attributes.Single().Value, Is.EqualTo(256L));
+ UsdTestHelpers.AssertInteger(reSet.Variants[0].Attributes.Single().Value, 1024L);
+ UsdTestHelpers.AssertInteger(reSet.Variants[1].Attributes.Single().Value, 256L);
}
[Test]
@@ -216,7 +216,7 @@ public void WriterOutputIsAFixedPoint_ForVariantBranches()
{
UsdStage stage = BuildStageWithBranches("high", "low");
UsdVariantSet set = stage.Find("/P")!.VariantSets.Single();
- set.Variants[0].Attributes.Add(new UsdAttribute("resolution", "int") { Value = 1024L });
+ set.Variants[0].Attributes.Add(new UsdAttribute("resolution", "int") { Value = UsdValue.From(1024L) });
set.Variants[0].AddChild(new UsdPrim("Detail", "Xform"));
string firstWrite = UsdaWriter.Write(stage);
@@ -235,14 +235,14 @@ public void Signature_IgnoresNonSelectedBranches()
UsdStage a = BuildStageWithBranches("high", "low");
a.Find("/P")!.VariantSets.Single().Selection = "high";
a.Find("/P")!.VariantSets.Single().Variants[0]
- .Attributes.Add(new UsdAttribute("resolution", "int") { Value = 1024L });
+ .Attributes.Add(new UsdAttribute("resolution", "int") { Value = UsdValue.From(1024L) });
UsdStage b = BuildStageWithBranches("high", "low");
b.Find("/P")!.VariantSets.Single().Selection = "high";
b.Find("/P")!.VariantSets.Single().Variants[0]
- .Attributes.Add(new UsdAttribute("resolution", "int") { Value = 256L });
+ .Attributes.Add(new UsdAttribute("resolution", "int") { Value = UsdValue.From(256L) });
b.Find("/P")!.VariantSets.Single().Variants[1]
- .Attributes.Add(new UsdAttribute("extra", "double") { Value = 3.5 });
+ .Attributes.Add(new UsdAttribute("extra", "double") { Value = UsdValue.From(3.5) });
Assert.That(
UsdSceneSignature.Compute(b),
diff --git a/tests/Opc.Ua.OpenUsd.Tests/VariantBranchTests.cs b/tests/Opc.Ua.OpenUsd.Tests/VariantBranchTests.cs
index a6477de84f..306a502a89 100644
--- a/tests/Opc.Ua.OpenUsd.Tests/VariantBranchTests.cs
+++ b/tests/Opc.Ua.OpenUsd.Tests/VariantBranchTests.cs
@@ -120,7 +120,7 @@ public void SelectionWithoutCapturedBranches_MaterializesNoBranch()
public void BranchBody_MaterializesAttributes()
{
var high = new UsdPrim("high");
- high.Attributes.Add(new UsdAttribute("resolution", "int") { Value = 1024L });
+ high.Attributes.Add(new UsdAttribute("resolution", "int") { Value = UsdValue.From(1024L) });
var set = new UsdVariantSet("lod", "high");
set.Variants.Add(high);
(MaterializedScene ms, UsdVariantSetState node) = MaterializeSet(set);
@@ -208,7 +208,7 @@ public void VariantBranch_IsNotADirectPrimChild()
public void Branches_RoundTripThroughMaterializeAndExport()
{
var high = new UsdPrim("high");
- high.Attributes.Add(new UsdAttribute("resolution", "int") { Value = 1024L });
+ high.Attributes.Add(new UsdAttribute("resolution", "int") { Value = UsdValue.From(1024L) });
var set = new UsdVariantSet("lod", "high");
set.Variants.Add(high);
set.Variants.Add(new UsdPrim("low"));