diff --git a/src/DynamoCore/Graph/Nodes/IValueSchemaProvider.cs b/src/DynamoCore/Graph/Nodes/IValueSchemaProvider.cs new file mode 100644 index 00000000000..c92df8e4e25 --- /dev/null +++ b/src/DynamoCore/Graph/Nodes/IValueSchemaProvider.cs @@ -0,0 +1,22 @@ +namespace Dynamo.Graph.Nodes +{ + /// + /// Implemented by NodeModels that can describe their value type + /// for external consumers (DynamoPlayer, MCP, etc.). + /// Returns a canonical Forge Data Schema type identifier and list context. + /// + public interface IValueSchemaProvider + { + /// + /// Canonical type identifier in Forge Data Schema format. + /// Examples: "autodesk.math:point3d-1.0.0", "String", "Float64". + /// Returns null if the type is unknown or not yet determined. + /// + string ValueTypeId { get; } + + /// + /// Whether the value is a list (array) of the declared type. + /// + bool IsListValue { get; } + } +} diff --git a/src/DynamoCore/PublicAPI.Unshipped.txt b/src/DynamoCore/PublicAPI.Unshipped.txt index 475a38b3c81..fd19c94b62b 100644 --- a/src/DynamoCore/PublicAPI.Unshipped.txt +++ b/src/DynamoCore/PublicAPI.Unshipped.txt @@ -1,3 +1,6 @@ +Dynamo.Graph.Nodes.IValueSchemaProvider +Dynamo.Graph.Nodes.IValueSchemaProvider.IsListValue.get -> bool +Dynamo.Graph.Nodes.IValueSchemaProvider.ValueTypeId.get -> string Dynamo.Models.DynamoModel.DefaultStartConfiguration.EnableUnTrustedLocationsNotifications.get -> bool Dynamo.Models.DynamoModel.DefaultStartConfiguration.EnableUnTrustedLocationsNotifications.set -> void Dynamo.Models.DynamoModel.IStartConfiguration.EnableUnTrustedLocationsNotifications.get -> bool diff --git a/src/Libraries/CoreNodeModels/DefineData.cs b/src/Libraries/CoreNodeModels/DefineData.cs index 1703a43f901..375bf6b7f7c 100644 --- a/src/Libraries/CoreNodeModels/DefineData.cs +++ b/src/Libraries/CoreNodeModels/DefineData.cs @@ -25,8 +25,25 @@ namespace CoreNodeModels [OutPortDescriptions(typeof(Properties.Resources), nameof(Properties.Resources.DefineDataOutputTooltip))] [IsDesignScriptCompatible] [AlsoKnownAs("Data.DefineData")] - public class DefineData : DSDropDownBase + public class DefineData : DSDropDownBase, IValueSchemaProvider { + /// + [JsonIgnore] + public string ValueTypeId + { + get + { + if (SelectedIndex < 0 || SelectedIndex >= Items.Count) + return SelectedString; + + return (Items[SelectedIndex].Item as Data.DataNodeDynamoType)?.TypeId ?? SelectedString; + } + } + + /// + [JsonIgnore] + public bool IsListValue => IsList; + private bool isAutoMode = true; // default start with auto-detect 'on' private bool isList; private string displayValue = Properties.Resources.DefineDataDisplayValueMessage; diff --git a/src/Libraries/CoreNodes/Data.cs b/src/Libraries/CoreNodes/Data.cs index 959586acacc..d28f2e098ac 100644 --- a/src/Libraries/CoreNodes/Data.cs +++ b/src/Libraries/CoreNodes/Data.cs @@ -534,7 +534,7 @@ public static Dictionary Remember([ArbitraryDimensionArrayImport /// /// A class representing a DataType supported by Dynamo /// - internal class DataNodeDynamoType(Type type, string name = null) + internal class DataNodeDynamoType(Type type, string name = null, string typeId = null) { /// /// The underlying Type @@ -545,6 +545,17 @@ internal class DataNodeDynamoType(Type type, string name = null) /// public string Name { get; private set; } = name ?? type.Name; /// + /// Wire-format $typeid as emitted by ProtoGeometry's ToJson() / DSCore.Data.StringifyJSON + /// (e.g. "autodesk.math:point3d-1.0.0", "autodesk.geometry.curve:bcurve-1.0.0"). + /// Null for primitive types (bool, string, Number, etc.) that don't use $typeid serialization. + /// Must match the identifiers recognised by + /// and the actual $typeid values produced by ProtoGeometry serialization. + /// Exposed to external consumers via + /// (implemented by DefineData), which DynamoPlayer reads to populate + /// ValueSchema.TypeId and DynamoMCP uses to resolve JSON Schemas for LLM inputs. + /// + public string TypeId { get; private set; } = typeId; + /// /// The hierarchical level to be displayed in the UI /// public int Level { get; private set; } = 0; @@ -557,8 +568,8 @@ internal class DataNodeDynamoType(Type type, string name = null) /// public DataNodeDynamoType Parent { get; private set; } - public DataNodeDynamoType(Type type, int level, bool isLastChild = false, string name = null, DataNodeDynamoType parent = null) - : this(type, name) + public DataNodeDynamoType(Type type, int level, bool isLastChild = false, string name = null, DataNodeDynamoType parent = null, string typeId = null) + : this(type, name, typeId) { Level = level; IsLastChild = isLastChild; @@ -577,41 +588,41 @@ public DataNodeDynamoType(Type type, int level, bool isLastChild = false, string /// static Data() { - var curve = new DataNodeDynamoType(typeof(Curve), 0, false, null, null); - var polyCurve = new DataNodeDynamoType(typeof(PolyCurve), 1, false, null, curve); - var polygon = new DataNodeDynamoType(typeof(Polygon), 2, false, null, polyCurve); // polygon is subtype of polyCurve - var rectangle = new DataNodeDynamoType(typeof(Autodesk.DesignScript.Geometry.Rectangle), 3, true, null, polyCurve); // rectangle is subtype of polygon - var solid = new DataNodeDynamoType(typeof(Solid), 0, false, null, null); - var cone = new DataNodeDynamoType(typeof(Cone), 1, false, null, solid); // cone is subtype of solid - var cylinder = new DataNodeDynamoType(typeof(Cylinder), 2, false, null, cone); // cylinder is subtype of cone - var cuboid = new DataNodeDynamoType(typeof(Cuboid), 1, false, null, solid); // cuboid is subtype of solid - var sphere = new DataNodeDynamoType(typeof(Sphere), 1, true, null, solid); // sphere is subtype of solid - - var surface = new DataNodeDynamoType(typeof(Surface), 0, false, null, null); + var curve = new DataNodeDynamoType(typeof(Curve), 0, false, null, null, "dynamo.geometry:sab-1.0.0"); + var polyCurve = new DataNodeDynamoType(typeof(PolyCurve), 1, false, null, curve, "autodesk.geometry.curve:compositecurve-1.0.0"); + var polygon = new DataNodeDynamoType(typeof(Polygon), 2, false, null, polyCurve, "autodesk.geometry.curve:polyline-1.0.0"); + var rectangle = new DataNodeDynamoType(typeof(Autodesk.DesignScript.Geometry.Rectangle), 3, true, null, polyCurve, "dynamo.geometry:rectangle-1.0.0"); + var solid = new DataNodeDynamoType(typeof(Solid), 0, false, null, null, "dynamo.geometry:sab-1.0.0"); + var cone = new DataNodeDynamoType(typeof(Cone), 1, false, null, solid, "dynamo.geometry:cone-1.0.0"); + var cylinder = new DataNodeDynamoType(typeof(Cylinder), 2, false, null, cone, "autodesk.geometry.surface:cylinder-2.0.0"); + var cuboid = new DataNodeDynamoType(typeof(Cuboid), 1, false, null, solid, "dynamo.geometry:cuboid-1.0.0"); + var sphere = new DataNodeDynamoType(typeof(Sphere), 1, true, null, solid, "autodesk.geometry.surface:sphere-1.0.0"); + + var surface = new DataNodeDynamoType(typeof(Surface), 0, false, null, null, "dynamo.geometry:sab-1.0.0"); var typeList = new List { new(typeof(bool)), - new(typeof(BoundingBox)), - new(typeof(CoordinateSystem)), + new(typeof(BoundingBox), typeId: "autodesk.geometry:boundingbox3d-1.0.0"), + new(typeof(CoordinateSystem), typeId: "autodesk.math:matrix44d-1.0.0"), curve, - new(typeof(Arc), 1, false, null, curve), - new(typeof(Circle), 1, false, null, curve), - new(typeof(Ellipse), 1, false, null, curve), - new(typeof(EllipseArc), 1, false, null, curve), - new(typeof(Helix), 1, false, null, curve), - new(typeof(Line), 1, false, null, curve), - new(typeof(NurbsCurve), 1, false, null, curve), + new(typeof(Arc), 1, false, null, curve, "autodesk.geometry.curve:circle-1.0.0"), + new(typeof(Circle), 1, false, null, curve, "autodesk.geometry.curve:circle-1.0.0"), + new(typeof(Ellipse), 1, false, null, curve, "autodesk.geometry.curve:ellipse-1.0.0"), + new(typeof(EllipseArc), 1, false, null, curve, "autodesk.geometry.curve:ellipse-1.0.0"), + new(typeof(Helix), 1, false, null, curve, "dynamo.geometry:sab-1.0.0"), + new(typeof(Line), 1, false, null, curve, "autodesk.geometry.curve:line-1.0.0"), + new(typeof(NurbsCurve), 1, false, null, curve, "autodesk.geometry.curve:bcurve-1.0.0"), polyCurve, polygon, rectangle, new(typeof(System.DateTime)), new(typeof(double), "Number"), new(typeof(long), "Integer"), - new(typeof(Location)), - new(typeof(Mesh)), - new(typeof(Plane)), - new(typeof(Autodesk.DesignScript.Geometry.Point)), + new(typeof(Location), typeId: "dynamo.data:location-1.0.0"), + new(typeof(Mesh), typeId: "dynamo.geometry:mesh-1.0.0"), + new(typeof(Plane), typeId: "autodesk.geometry.surface:plane-1.0.0"), + new(typeof(Autodesk.DesignScript.Geometry.Point), typeId: "autodesk.math:point3d-1.0.0"), solid, cone, cylinder, @@ -619,11 +630,11 @@ static Data() sphere, new(typeof(string)), surface, - new(typeof(NurbsSurface), 1, false, null, surface), - new(typeof(PolySurface), 1, true, null, surface), + new(typeof(NurbsSurface), 1, false, null, surface, "autodesk.geometry.curve:bsurface-1.0.0"), + new(typeof(PolySurface), 1, true, null, surface, "dynamo.geometry:sab-1.0.0"), new(typeof(System.TimeSpan)), - new(typeof(UV)), - new(typeof(Vector)) + new(typeof(UV), typeId: "autodesk.math:uv-1.0.0"), + new(typeof(Vector), typeId: "autodesk.math:vector3d-1.0.0") }; DataNodeDynamoTypeList = new ReadOnlyCollection(typeList); diff --git a/test/DynamoCoreTests/Nodes/DefineDataTests.cs b/test/DynamoCoreTests/Nodes/DefineDataTests.cs new file mode 100644 index 00000000000..982930d3913 --- /dev/null +++ b/test/DynamoCoreTests/Nodes/DefineDataTests.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Linq; +using CoreNodeModels; +using NUnit.Framework; + +namespace Dynamo.Tests.Nodes +{ + [TestFixture] + internal class DefineDataTests : DynamoModelTestBase + { + protected override void GetLibrariesToPreload(List libraries) + { + libraries.Add("DSCoreNodes.dll"); + base.GetLibrariesToPreload(libraries); + } + + [Test] + [Category("UnitTests")] + public void ValueSchemaProviderReturnsTypeId() + { + var node = new DefineData(); + CurrentDynamoModel.CurrentWorkspace.AddAndRegisterNode(node, false); + + // Find a type with a non-null TypeId for testing + var pointType = DSCore.Data.DataNodeDynamoTypeList.First(t => t.Type == typeof(Autodesk.DesignScript.Geometry.Point)); + Assert.IsNotNull(pointType.TypeId, "Point type should have a valid TypeId"); + + // Set the node to Point type + node.SelectedIndex = DSCore.Data.DataNodeDynamoTypeList.IndexOf(pointType); + + Assert.AreEqual(pointType.TypeId, node.ValueTypeId); + Assert.IsFalse(node.IsListValue); + } + + [Test] + [Category("UnitTests")] + public void ValueTypeIdSafeWhenNoSelection() + { + var node = new DefineData(); + Assert.DoesNotThrow(() => { var _ = node.ValueTypeId; }); + Assert.AreEqual(node.SelectedString, node.ValueTypeId); + } + + [Test] + [Category("UnitTests")] + public void ValueTypeIdHandlesNullTypeId() + { + var node = new DefineData(); + CurrentDynamoModel.CurrentWorkspace.AddAndRegisterNode(node, false); + + // Default selection (bool) has null TypeId + var firstType = DSCore.Data.DataNodeDynamoTypeList.First(); + Assert.IsNull(firstType.TypeId, "First type (bool) should have null TypeId"); + + // Should fall back to SelectedString when TypeId is null + Assert.AreEqual(node.SelectedString, node.ValueTypeId); + } + } +}