From 8a201695485bcbad5f9d92f971768fc6c5d24763 Mon Sep 17 00:00:00 2001 From: Mathieu Bilodeau-Roy Date: Fri, 20 Jul 2018 13:44:26 -0400 Subject: [PATCH 1/7] -Added Pack and UnPack to the Dynamo core library --- .../AssemblySharedInfo.cs | 4 +- src/Dynamo.All.sln | 14 + src/DynamoCore/Graph/Nodes/NodeCategories.cs | 1 + src/Libraries/CoreNodes/CoreNodes.csproj | 1 + src/Libraries/CoreNodes/Packing.cs | 93 ++++++ src/Libraries/PackingNodeModels/Pack/Pack.cs | 100 +++++++ .../Pack/Validation/PortValidator.cs | 163 +++++++++++ .../Pack/Validation/ValidationManager.cs | 67 +++++ .../PackingNodeModels/PackingNode.cs | 166 +++++++++++ .../PackingNodeModels.csproj | 134 +++++++++ .../Properties/AssemblyInfo.cs | 38 +++ .../Properties/Resource.Designer.cs | 135 +++++++++ .../Properties/Resource.en-US.resx | 144 ++++++++++ .../Properties/Resource.resx | 144 ++++++++++ .../PackingNodeModels/TypeDefinition.cs | 85 ++++++ .../PackingNodeModels/UnPack/UnPack.cs | 70 +++++ .../PackingNodeModels/packages.config | 5 + .../web/library/layoutSpecs.json | 15 + .../CoreNodesTests/CoreNodesTests.csproj | 5 + test/Libraries/CoreNodesTests/PackingTests.cs | 157 +++++++++++ .../PackingNodeTests/Pack/PackTests.cs | 266 ++++++++++++++++++ .../Pack/Validation/PortValidatorTests.cs | 187 ++++++++++++ .../Pack/Validation/ValidationManagerTests.cs | 133 +++++++++ .../PackingNodeTests/PackingNodeTests.cs | 233 +++++++++++++++ .../PackingNodeTests/PackingNodeTests.csproj | 86 ++++++ .../Properties/AssemblyInfo.cs | 36 +++ .../PackingNodeTests/UnPack/UnPackTests.cs | 124 ++++++++ test/core/PackingNode/packingnodes.dyn | 121 ++++++++ 28 files changed, 2725 insertions(+), 2 deletions(-) create mode 100644 src/Libraries/CoreNodes/Packing.cs create mode 100644 src/Libraries/PackingNodeModels/Pack/Pack.cs create mode 100644 src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs create mode 100644 src/Libraries/PackingNodeModels/Pack/Validation/ValidationManager.cs create mode 100644 src/Libraries/PackingNodeModels/PackingNode.cs create mode 100644 src/Libraries/PackingNodeModels/PackingNodeModels.csproj create mode 100644 src/Libraries/PackingNodeModels/Properties/AssemblyInfo.cs create mode 100644 src/Libraries/PackingNodeModels/Properties/Resource.Designer.cs create mode 100644 src/Libraries/PackingNodeModels/Properties/Resource.en-US.resx create mode 100644 src/Libraries/PackingNodeModels/Properties/Resource.resx create mode 100644 src/Libraries/PackingNodeModels/TypeDefinition.cs create mode 100644 src/Libraries/PackingNodeModels/UnPack/UnPack.cs create mode 100644 src/Libraries/PackingNodeModels/packages.config create mode 100644 test/Libraries/CoreNodesTests/PackingTests.cs create mode 100644 test/Libraries/PackingNodeTests/Pack/PackTests.cs create mode 100644 test/Libraries/PackingNodeTests/Pack/Validation/PortValidatorTests.cs create mode 100644 test/Libraries/PackingNodeTests/Pack/Validation/ValidationManagerTests.cs create mode 100644 test/Libraries/PackingNodeTests/PackingNodeTests.cs create mode 100644 test/Libraries/PackingNodeTests/PackingNodeTests.csproj create mode 100644 test/Libraries/PackingNodeTests/Properties/AssemblyInfo.cs create mode 100644 test/Libraries/PackingNodeTests/UnPack/UnPackTests.cs create mode 100644 test/core/PackingNode/packingnodes.dyn diff --git a/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs b/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs index fc7e696bd65..54fa221d12b 100644 --- a/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs +++ b/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs @@ -45,7 +45,7 @@ // to distinguish one build from another. AssemblyFileVersion is specified // in AssemblyVersionInfo.cs so that it can be easily incremented by the // automated build process. -[assembly: AssemblyVersion("2.1.0.4395")] +[assembly: AssemblyVersion("2.1.0.5655")] // By default, the "Product version" shown in the file properties window is @@ -64,4 +64,4 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("2.1.0.4395")] +[assembly: AssemblyFileVersion("2.1.0.5655")] diff --git a/src/Dynamo.All.sln b/src/Dynamo.All.sln index cb8f2c941a9..5642cdb82ea 100644 --- a/src/Dynamo.All.sln +++ b/src/Dynamo.All.sln @@ -223,6 +223,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamoPackagesWPF", "Dynamo EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DynamoWPFCLI", "DynamoWPFCLI\DynamoWPFCLI.csproj", "{F7FD9395-35D5-474D-8AD1-9904817E70D3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackingNodeModels", "Libraries\PackingNodeModels\PackingNodeModels.csproj", "{D915633B-8BD6-4860-B0A7-4D689DF445FA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackingNodeTests", "..\test\Libraries\PackingNodeTests\PackingNodeTests.csproj", "{79F2ADE0-11C4-47F7-9CE4-ACFEFC25CAAF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -513,6 +517,14 @@ Global {F7FD9395-35D5-474D-8AD1-9904817E70D3}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7FD9395-35D5-474D-8AD1-9904817E70D3}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7FD9395-35D5-474D-8AD1-9904817E70D3}.Release|Any CPU.Build.0 = Release|Any CPU + {D915633B-8BD6-4860-B0A7-4D689DF445FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D915633B-8BD6-4860-B0A7-4D689DF445FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D915633B-8BD6-4860-B0A7-4D689DF445FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D915633B-8BD6-4860-B0A7-4D689DF445FA}.Release|Any CPU.Build.0 = Release|Any CPU + {79F2ADE0-11C4-47F7-9CE4-ACFEFC25CAAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79F2ADE0-11C4-47F7-9CE4-ACFEFC25CAAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79F2ADE0-11C4-47F7-9CE4-ACFEFC25CAAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79F2ADE0-11C4-47F7-9CE4-ACFEFC25CAAF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -579,6 +591,8 @@ Global {F8D2E3E5-4505-4463-9367-2EB2629D7DCD} = {0E492D35-2310-4849-9694-A2A53C09F21B} {AE7F2579-104A-4AF4-AA7B-614AE9E79279} = {24A1EF6B-5A24-4536-B8BD-C879ABA26389} {C0D6DEE5-5532-4345-9C66-4C00D7FDB8BE} = {FA7BE306-A3B0-45FA-9D87-0C69E6932C13} + {D915633B-8BD6-4860-B0A7-4D689DF445FA} = {FA7BE306-A3B0-45FA-9D87-0C69E6932C13} + {79F2ADE0-11C4-47F7-9CE4-ACFEFC25CAAF} = {0E492D35-2310-4849-9694-A2A53C09F21B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {89CB19C6-BF0A-4E6A-BFDA-79D143EAB59D} diff --git a/src/DynamoCore/Graph/Nodes/NodeCategories.cs b/src/DynamoCore/Graph/Nodes/NodeCategories.cs index 3593c7738b9..996c4014ece 100644 --- a/src/DynamoCore/Graph/Nodes/NodeCategories.cs +++ b/src/DynamoCore/Graph/Nodes/NodeCategories.cs @@ -30,6 +30,7 @@ public static class BuiltinNodeCategories public const string CORE_SCRIPTING = "Core.Scripting"; public const string CORE_IO = "Core.File"; public const string CORE_UNITS = "Core.Units"; + public const string CORE_PACKING = "Core.Packing"; public const string LOGIC = "Core.Logic"; public const string LOGIC_MATH_OPTIMIZE = "Logic.Math.Optimize"; diff --git a/src/Libraries/CoreNodes/CoreNodes.csproj b/src/Libraries/CoreNodes/CoreNodes.csproj index 07f2f2385ec..78aaf2e32da 100644 --- a/src/Libraries/CoreNodes/CoreNodes.csproj +++ b/src/Libraries/CoreNodes/CoreNodes.csproj @@ -77,6 +77,7 @@ + True diff --git a/src/Libraries/CoreNodes/Packing.cs b/src/Libraries/CoreNodes/Packing.cs new file mode 100644 index 00000000000..6b42957e449 --- /dev/null +++ b/src/Libraries/CoreNodes/Packing.cs @@ -0,0 +1,93 @@ +using Autodesk.DesignScript.Runtime; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DSCore +{ + [IsVisibleInDynamoLibrary(false)] + public class PackFunctions + { + public static object PackOutputAsDictionary(List keys, List isCollection, [ArbitraryDimensionArrayImport] object data) + { + if (keys == null || keys.Count == 0 || isCollection == null || isCollection.Count == 0 || data == null) + return null; + + var result = new List>(); + + var inputs = data as ArrayList; + + var outputCount = GetOuputCountFrom(isCollection, inputs); + + for (int outputIndex = 0; outputIndex < outputCount; ++outputIndex) + result.Add(CreateOuputDictionary(keys, isCollection, inputs, outputIndex)); + + return result; + } + + private static Dictionary CreateOuputDictionary(List keys, List isCollection, ArrayList inputs, int outputIndex) + { + var dictionary = new Dictionary(); + + for (int inputIndex = 0; inputIndex < inputs.Count; ++inputIndex) + { + object value = inputs[inputIndex]; + + if (value is ArrayList) + { + var array = value as ArrayList; + if (!isCollection[inputIndex] || array[0] is ArrayList) + { + int lastIndex = array.Count - 1; + value = lastIndex < outputIndex ? array[lastIndex] : array[outputIndex]; + } + } + + dictionary[keys[inputIndex]] = value; + } + + return dictionary; + } + + /// + /// Returns the number of dictionaries that should be output by the node. + /// LacingStrategy "Longest" is used, which means the biggest list is the count of dictionaries that should be output. + /// + /// + /// + /// + private static int GetOuputCountFrom(List isCollection, ArrayList inputs) + { + var subArrays = inputs.Cast() + .Where(i => i is ArrayList) + .Cast(); + + if (!subArrays.Any()) + return 1; + + return subArrays.Max(subArray => + { + var index = inputs.IndexOf(subArray); + if (!isCollection[index] || (subArray[0] is ArrayList)) + return subArray.Count; + + return 1; + }); + } + } + + [IsVisibleInDynamoLibrary(false)] + public class UnPackFunctions + { + public static object UnPackOutputByKey(DesignScript.Builtin.Dictionary dictionary, string key) + { + if (dictionary == null) + return null; + + return dictionary.ValueAtKey(key); + } + } +} diff --git a/src/Libraries/PackingNodeModels/Pack/Pack.cs b/src/Libraries/PackingNodeModels/Pack/Pack.cs new file mode 100644 index 00000000000..00ceb2aebc4 --- /dev/null +++ b/src/Libraries/PackingNodeModels/Pack/Pack.cs @@ -0,0 +1,100 @@ +using Dynamo.Graph.Nodes; +using Newtonsoft.Json; +using PackingNodeModels.Pack.Validation; +using PackingNodeModels.Properties; +using ProtoCore.AST.AssociativeAST; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PackingNodeModels.Pack +{ + [NodeName("Pack")] + [NodeCategory(BuiltinNodeCategories.CORE_PACKING)] + [NodeDescription("PackNodeDescription", typeof(Resource))] + [OutPortNames("Out")] + [OutPortTypes("Dictionary")] + [OutPortDescriptions("Dictionary")] + [IsDesignScriptCompatible] + public class Pack : PackingNode + { + private List cachedValues; + private IValidationManager validationManager; + + public Pack() + { + validationManager = new ValidationManager(this); + } + + [JsonConstructor] + protected Pack(IEnumerable inPorts, IEnumerable outPorts) + : base(inPorts, outPorts) + { + validationManager = new ValidationManager(this); + } + + protected override void RefreshTypeDefinitionPorts() + { + cachedValues = null; + InPorts.Skip(1).ToList().ForEach(port => InPorts.Remove(port)); + + if (TypeDefinition != null) + { + foreach (var property in TypeDefinition.Properties) + { + InPorts.Add(new PortModel(PortType.Input, this, new PortData(property.Key, property.Value.ToString()))); + } + } + } + + protected override void ValidateInputs(List values) + { + var wasInWarningState = validationManager.Warnings.Any(); + if (values.Count > 1) + { + var valuesByIndex = new Dictionary(); + for (int i = 1; i < values.Count; ++i) + { + if (cachedValues == null || cachedValues.Count <= i || values[i] != cachedValues[i]) + valuesByIndex[i] = values[i]; + } + + validationManager.HandleValidation(valuesByIndex); + } + + cachedValues = values; + + //FIXME This doesn't seem right. Is there a way to build a conditional node that would depend on validation inputs, instead of building the output twice? + //Right now, it's ran once, then the DataBridge is invoked, which runs the validation and re-trigger the building of the ouput if warnings are gone/new. + if (wasInWarningState != validationManager.Warnings.Any()) + OnNodeModified(true); + } + + public override IEnumerable BuildOutputAst(List inputAstNodes) + { + var baseOutput = base.BuildOutputAst(inputAstNodes).ToList(); + + if (inputAstNodes == null || !IsValidInputState(inputAstNodes)) + { + baseOutput.Add(AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), AstFactory.BuildNullNode())); + return baseOutput; + } + + inputAstNodes = inputAstNodes.Skip(1).ToList(); + var keys = TypeDefinition.Properties.Select(input => AstFactory.BuildStringNode(input.Key) as AssociativeNode).ToList(); + //FIXME Only way I found to pass in the context to the output function call so it knows how replications should work. + //Maybe a tuple of keys,context would be better? + var isCollectionInputs = TypeDefinition.Properties.Select(input => AstFactory.BuildBooleanNode(input.Value.IsCollection) as AssociativeNode).ToList(); + + var functionCall = AstFactory.BuildFunctionCall( + new Func, List, object, object>(DSCore.PackFunctions.PackOutputAsDictionary), + new List { AstFactory.BuildExprList(keys), AstFactory.BuildExprList(isCollectionInputs), AstFactory.BuildExprList(inputAstNodes) }); + + baseOutput.Add(AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), functionCall)); + + return baseOutput; + } + } +} diff --git a/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs b/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs new file mode 100644 index 00000000000..eca0d520112 --- /dev/null +++ b/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs @@ -0,0 +1,163 @@ +using Dynamo.Graph.Nodes; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PackingNodeModels.Pack.Validation +{ + public class PortValidator + { + private static Dictionary> CompatibleTypes = new Dictionary> + { + { "String", new List() { typeof(string) } }, + { "Float64", new List() { typeof(float), typeof(double), typeof(Int32), typeof(Int64) } }, + { "autodesk.aec.primitive:arc-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Arc) } }, + { "autodesk.aec.primitive:circle-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Circle) } }, + { "autodesk.aec.primitive:coordinatesystem-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.CoordinateSystem) } }, + { "autodesk.aec.primitive:cone-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Cone) } }, + { "autodesk.aec.primitive:curve-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Curve) } }, + { "autodesk.aec.primitive:cuboid-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Cuboid) } }, + { "autodesk.aec.primitive:cylinder-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Cylinder) } }, + { "autodesk.aec.primitive:ellipse-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Ellipse) } }, + { "autodesk.aec.primitive:line-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Line) } }, + { "autodesk.aec.primitive:plane-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Plane) } }, + { "autodesk.aec.primitive:point-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Point) } }, + { "autodesk.aec.primitive:polycurve-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.PolyCurve) } }, + { "autodesk.aec.primitive:polygon-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Polygon) } }, + { "autodesk.aec.primitive:rectangle-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Rectangle) } }, + { "autodesk.aec.primitive:sphere-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Sphere) } }, + { "autodesk.aec.primitive:vector-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Vector) } }, + { "autodesk.aec.primitive:surface-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Surface) } }, + { "autodesk.soliddef:model-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Geometry) } } //TODO double check soliddef's type. + }; + + public static string Validate(KeyValuePair property, object value, PortModel portModel) + { + if (value == null) + return $"Input {property.Key} expected type {property.Value.Type} but received null."; + + if (property.Value.IsCollection) + return ValidateCollection(property, value, portModel); + + return ValidateSingleValue(property, value, portModel); + } + + private static string ValidateSingleValue(KeyValuePair property, object value, PortModel portModel) + { + if (value is ArrayList arrayList) + return ValidateSingleValueLacing(property, arrayList, portModel); + + return ValidateTypeMatch(property, value, portModel); + } + + private static string ValidateSingleValueLacing(KeyValuePair property, ArrayList values, PortModel portModel) + { + if (ContainsCollections(values)) + return $"Input {property.Key} expected a single value of type {property.Value.Type} but received a list of values."; + + return ValidateValues(property, values, portModel); + } + + private static string ValidateCollection(KeyValuePair property, object value, PortModel portModel) + { + if (!(value is ArrayList)) + return $"Input {property.Key} expected an array of type {property.Value.Type} but received a single value."; + + var values = value as ArrayList; + + if (IsMixedCombinationOfCollectionAndSingleValues(values)) + return $"Input {property.Key} expected an array of type {property.Value.Type} but received a mixed combination of single values and arrays."; + + if (ContainsCollections(values)) + return ValidateArrayLacing(property, values, portModel); + + return ValidateValues(property, values, portModel); + } + + private static string ValidateArrayLacing(KeyValuePair property, ArrayList values, PortModel portModel) + { + foreach (var subArray in values) + { + foreach (var subValue in subArray as ArrayList) + { + //Deeper arrays not allowed + if (subValue is ArrayList) + return $"Input {property.Key} expected an array of type {property.Value.Type} but received a nested array."; + else + { + var validation = ValidateTypeMatch(property, subValue, portModel); + if (!string.IsNullOrEmpty(validation)) + return validation; + } + + } + } + + return null; + } + + public static string ValidateTypeMatch(KeyValuePair property, object value, PortModel portModel) + { + if (IsKnownType(property.Value.Type)) + { + if (!IsTypeMatch(value, property.Value.Type)) + return $"Input {property.Key} expected type {property.Value.Type} but received {value?.GetType()}."; + + return null; + } + + return ValidateUnknownType(property, value, portModel); + } + + private static string ValidateValues(KeyValuePair property, ArrayList values, PortModel portModel) + { + foreach (var value in values) + { + var validation = ValidateTypeMatch(property, value, portModel); + + if (!string.IsNullOrEmpty(validation)) + return validation; + } + + return null; + } + + private static string ValidateUnknownType(KeyValuePair property, object value, PortModel portModel) + { + //Assume and expect that an unkwown type comes from another Pack + var owner = portModel.Connectors[0].Start.Owner as Pack; + if (owner == null || !property.Value.Type.Equals(owner.TypeDefinition.Name, StringComparison.InvariantCultureIgnoreCase)) + { + return $"Input {property.Key} expected type {property.Value.Type} but received {owner?.TypeDefinition.Name ?? value?.GetType().ToString()}."; + } + + return null; + } + + private static bool ContainsCollections(ArrayList values) + { + return values.Cast().Any(x => x is ArrayList); + } + + private static bool IsMixedCombinationOfCollectionAndSingleValues(ArrayList values) + { + return values.Cast().Select(x => x is ArrayList).Distinct().Count() > 1; + } + + private static bool IsKnownType(string type) + { + return CompatibleTypes.ContainsKey(type); + } + + private static bool IsTypeMatch(object value, string expectedType) + { + if (!CompatibleTypes.ContainsKey(expectedType)) + return true; + + return CompatibleTypes[expectedType].Exists(x => x == value?.GetType()); + } + } +} diff --git a/src/Libraries/PackingNodeModels/Pack/Validation/ValidationManager.cs b/src/Libraries/PackingNodeModels/Pack/Validation/ValidationManager.cs new file mode 100644 index 00000000000..3df1acc1bd3 --- /dev/null +++ b/src/Libraries/PackingNodeModels/Pack/Validation/ValidationManager.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PackingNodeModels.Pack.Validation +{ + internal interface IValidationManager + { + void HandleValidation(Dictionary valuesByIndex); + ReadOnlyCollection Warnings { get; } + } + + internal class ValidationManager : IValidationManager + { + private List warnings; + + public ReadOnlyCollection Warnings { get { return warnings.AsReadOnly(); } } + + public Pack Node { get; private set; } + + public ValidationManager(Pack node) + { + Node = node; + warnings = new List(); + } + + public void HandleValidation(Dictionary valuesByIndex) + { + var properties = Node.TypeDefinition.Properties.ToList(); + foreach (var pair in valuesByIndex) + { + ClearWarningForPortIndex(pair.Key); + + if (!Node.InPorts[pair.Key].Connectors.Any()) + continue; + + var validation = PortValidator.Validate(properties[pair.Key - 1], pair.Value, Node.InPorts[pair.Key]); //i - 1 because we're skipping the Type port. + + if (!string.IsNullOrEmpty(validation)) + warnings.Add(new Validation { Message = validation, PortIndex = pair.Key }); + } + + ComputeWarnings(); + } + + private void ClearWarningForPortIndex(int index) + { + warnings.RemoveAll(w => w.PortIndex == index); + } + + private void ComputeWarnings() + { + Node.ClearErrorsAndWarnings(); + if (warnings.Any()) + Node.Warning(String.Join(String.Empty, warnings.Select(w => w.Message).ToArray()), true); + } + } + + internal class Validation + { + public string Message { get; set; } + public int PortIndex { get; set; } + } +} diff --git a/src/Libraries/PackingNodeModels/PackingNode.cs b/src/Libraries/PackingNodeModels/PackingNode.cs new file mode 100644 index 00000000000..568b5c96422 --- /dev/null +++ b/src/Libraries/PackingNodeModels/PackingNode.cs @@ -0,0 +1,166 @@ +using Dynamo.Controls; +using Dynamo.Graph.Nodes; +using Dynamo.Models; +using Dynamo.Scheduler; +using Dynamo.ViewModels; +using Dynamo.Wpf; +using Newtonsoft.Json; +using PackingNodeModels.Properties; +using ProtoCore.AST.AssociativeAST; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Threading; +using VMDataBridge; + +namespace PackingNodeModels +{ + public abstract class PackingNode : NodeModel + { + protected string TypeDefinitionPortName = Resource.TypePortName; + + public event Action RequestScheduledTask; + + private string _cachedTypeDefinition; + private TypeDefinition _typeDefinition; + + [JsonProperty("TypeDefinition")] + public TypeDefinition TypeDefinition + { + get + { + return _typeDefinition; + } + protected set + { + if (value == null) + _cachedTypeDefinition = null; + _typeDefinition = value; + ClearErrorsAndWarnings(); + RequestScheduledTask?.Invoke(RefreshTypeDefinitionPorts); + } + } + + [JsonIgnore] + public bool IsInValidState + { + get + { + return !IsInErrorState && State != ElementState.Warning && State != ElementState.PersistentWarning; + } + } + + public PackingNode() + { + InPorts.Add(new PortModel(PortType.Input, this, new PortData(TypeDefinitionPortName, Resource.TypePortTooltip))); + + RegisterAllPorts(); + } + + protected PackingNode(IEnumerable inPorts, IEnumerable outPorts) + : base(inPorts, outPorts) { } + + protected override void OnBuilt() + { + base.OnBuilt(); + DataBridge.Instance.RegisterCallback(GUID.ToString(), OnEvaluationComplete); + } + + public override IEnumerable BuildOutputAst(List inputAstNodes) + { + return new List() { + AstFactory.BuildAssignment( + AstFactory.BuildIdentifier(AstIdentifierBase + "_dummy"), + DataBridge.GenerateBridgeDataAst(GUID.ToString(), AstFactory.BuildExprList(inputAstNodes ?? new List()))) + }; + } + + protected virtual void ValidateInputs(List values) { } + + protected abstract void RefreshTypeDefinitionPorts(); + + private void OnEvaluationComplete(object obj) + { + if (obj is ArrayList inputValues) + { + CheckTypeDefinition(inputValues); + ValidateInputs(inputValues.Cast().ToList()); + } + } + + private void CheckTypeDefinition(ArrayList inputValues) + { + if (InputNodes.Count == 0 || !InputNodes.ContainsKey(0) || InputNodes[0] == null) + TypeDefinition = null; + else + { + if (inputValues[0] is string typeDef) + { + if (typeDef != _cachedTypeDefinition) + { + try + { + TypeDefinition = TypeDefinitionParser.ParseType(typeDef); + _cachedTypeDefinition = typeDef; + } + catch (Sprache.ParseException e) + { + Warning(e.Message, true); + } + } + } + else + Warning(Resource.TypePortWarning, true); + } + } + + protected bool IsValidInputState(List inputAstNodes) + { + return InPorts[0].Connectors.Any() && inputAstNodes.Count > 1 && IsInValidState && !inputAstNodes.Exists(node => node is NullNode); + } + + public override void Dispose() + { + base.Dispose(); + DataBridge.Instance.UnregisterCallback(GUID.ToString()); + } + } + + public class PackingNodeView : INodeViewCustomization + { + private DynamoModel dynamoModel; + private DynamoViewModel dynamoViewModel; + private DispatcherSynchronizationContext syncContext; + private PackingNode node; + + public void CustomizeView(PackingNode nodeModel, NodeView nodeView) + { + dynamoModel = nodeView.ViewModel.DynamoViewModel.Model; + dynamoViewModel = nodeView.ViewModel.DynamoViewModel; + syncContext = new DispatcherSynchronizationContext(nodeView.Dispatcher); + node = nodeModel; + node.RequestScheduledTask += OnRequestScheduledTask; + } + + private void OnRequestScheduledTask(Action action) + { + var s = dynamoViewModel.Model.Scheduler; + + var t = new DelegateBasedAsyncTask(s, () => + { + }); + + t.ThenSend((_) => + { + action(); + }, syncContext); + + s.ScheduleForExecution(t); + } + + public void Dispose() + { + } + } +} diff --git a/src/Libraries/PackingNodeModels/PackingNodeModels.csproj b/src/Libraries/PackingNodeModels/PackingNodeModels.csproj new file mode 100644 index 00000000000..9bbea25907c --- /dev/null +++ b/src/Libraries/PackingNodeModels/PackingNodeModels.csproj @@ -0,0 +1,134 @@ + + + + + + + + Debug + AnyCPU + {D915633B-8BD6-4860-B0A7-4D689DF445FA} + Library + Properties + PackingNodeModels + PackingNodeModels + v4.5 + 512 + + + true + full + false + $(OutputPath)\nodes + DEBUG;TRACE + prompt + 4 + ..\..\..\bin\AnyCPU\Debug\en-US\PackingNodeModels.xml + + + pdbonly + true + $(OutputPath) + TRACE + prompt + 4 + ..\..\..\bin\AnyCPU\Release\en-US\PackingNodeModels.xml + + + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + False + + + + + ..\..\packages\Sprache.2.1.2\lib\net45\Sprache.dll + True + + + False + ..\..\..\extern\ProtoGeometry\ProtoGeometry.dll + False + + + + + + + + + + + + + + + + + + + True + True + Resource.resx + + + + + + + {51BB6014-43F7-4F31-B8D3-E3C37EBEDAF4} + DynamoCoreWpf + False + + + {7858FA8C-475F-4B8E-B468-1F8200778CF8} + DynamoCore + False + + + {7A9E0314-966F-4584-BAA3-7339CBB849D1} + ProtoCore + False + + + {ef879a10-041d-4c68-83e7-3192685f1bae} + DynamoServices + + + {87550b2b-6cb8-461e-8965-dfafe3aafb5c} + CoreNodes + False + + + {c0d6dee5-5532-4345-9c66-4c00d7fdb8be} + DesignScriptBuiltin + False + + + {CCB6E56B-2DA1-4EBA-A1F9-E8510E129D12} + VMDataBridge + False + + + + + + + + + ResXFileCodeGenerator + Resource.Designer.cs + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Libraries/PackingNodeModels/Properties/AssemblyInfo.cs b/src/Libraries/PackingNodeModels/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..7b829cee0b8 --- /dev/null +++ b/src/Libraries/PackingNodeModels/Properties/AssemblyInfo.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PackingNodeModels")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PackingNodeModels")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d915633b-8bd6-4860-b0a7-4d689df445fa")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: InternalsVisibleTo("PackingNodeTests")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] diff --git a/src/Libraries/PackingNodeModels/Properties/Resource.Designer.cs b/src/Libraries/PackingNodeModels/Properties/Resource.Designer.cs new file mode 100644 index 00000000000..3d291ecca8b --- /dev/null +++ b/src/Libraries/PackingNodeModels/Properties/Resource.Designer.cs @@ -0,0 +1,135 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PackingNodeModels.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PackingNodeModels.Properties.Resource", typeof(Resource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Creates a dictionary given a TypeDefinition and its corresponding input values. + /// + internal static string PackNodeDescription { + get { + return ResourceManager.GetString("PackNodeDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Out. + /// + internal static string PackNodeOutputPortName { + get { + return ResourceManager.GetString("PackNodeOutputPortName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Type. + /// + internal static string TypePortName { + get { + return ResourceManager.GetString("TypePortName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to TypeDefinition as a string. + /// + internal static string TypePortTooltip { + get { + return ResourceManager.GetString("TypePortTooltip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Input Type must be of type string. + /// + internal static string TypePortWarning { + get { + return ResourceManager.GetString("TypePortWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to In. + /// + internal static string UnPackNodeDataInputName { + get { + return ResourceManager.GetString("UnPackNodeDataInputName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dictionary. + /// + internal static string UnPackNodeDataInputType { + get { + return ResourceManager.GetString("UnPackNodeDataInputType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Takes a dictionary and a TypeDefinition and returns the data matching the given TypeDefinition. + /// + internal static string UnPackNodeDescription { + get { + return ResourceManager.GetString("UnPackNodeDescription", resourceCulture); + } + } + } +} diff --git a/src/Libraries/PackingNodeModels/Properties/Resource.en-US.resx b/src/Libraries/PackingNodeModels/Properties/Resource.en-US.resx new file mode 100644 index 00000000000..f35a933ea6e --- /dev/null +++ b/src/Libraries/PackingNodeModels/Properties/Resource.en-US.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Creates a dictionary given a TypeDefinition and its corresponding input values + + + Out + + + Type + + + TypeDefinition as a string + + + Input Type must be of type string + + + In + + + Dictionary + + + Takes a dictionary and a TypeDefinition and returns the data matching the given TypeDefinition + + \ No newline at end of file diff --git a/src/Libraries/PackingNodeModels/Properties/Resource.resx b/src/Libraries/PackingNodeModels/Properties/Resource.resx new file mode 100644 index 00000000000..f35a933ea6e --- /dev/null +++ b/src/Libraries/PackingNodeModels/Properties/Resource.resx @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Creates a dictionary given a TypeDefinition and its corresponding input values + + + Out + + + Type + + + TypeDefinition as a string + + + Input Type must be of type string + + + In + + + Dictionary + + + Takes a dictionary and a TypeDefinition and returns the data matching the given TypeDefinition + + \ No newline at end of file diff --git a/src/Libraries/PackingNodeModels/TypeDefinition.cs b/src/Libraries/PackingNodeModels/TypeDefinition.cs new file mode 100644 index 00000000000..2c2c21cb9f7 --- /dev/null +++ b/src/Libraries/PackingNodeModels/TypeDefinition.cs @@ -0,0 +1,85 @@ +using Autodesk.DesignScript.Runtime; +using Dynamo.Graph.Nodes; +using Sprache; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PackingNodeModels +{ + [IsVisibleInDynamoLibrary(false)] + public class TypeDefinition + { + public string Name { get; set; } + + public Dictionary Properties { get; set; } + } + + [IsVisibleInDynamoLibrary(false)] + public class PropertyType + { + public string Type { get; set; } + public bool IsCollection { get; set; } + + public override string ToString() + { + return Type + (IsCollection ? "[]" : ""); + } + } + + [IsVisibleInDynamoLibrary(false)] + internal class TypeDefinitionParser + { + private const string OpeningKeyword = "Type"; + + private static readonly Parser TypeKeyword = + from leading in Parse.WhiteSpace.Many() + from keyword in Parse.String(OpeningKeyword) + from trailing in Parse.WhiteSpace.Many() + select new string(keyword.ToArray()); + + private static readonly Parser TypeName = + from type in Parse.CharExcept(new[] { '{', '}', ',', '[', ']' }).AtLeastOnce() + from trailing in Parse.WhiteSpace.Many() + select new string(type.ToArray()).Trim(); + + private static readonly Parser> Property = + from leading in Parse.WhiteSpace.Many() + from firstLetter in Parse.Letter.AtLeastOnce() + from rest in Parse.LetterOrDigit.Many() + from seperator in Colon + from type in TypeName + from isCollection in Parse.String("[]").Optional() + from trailing in Parse.WhiteSpace.Many() + select new KeyValuePair(new string(firstLetter.ToArray()) + new string(rest.ToArray()), new PropertyType { Type = new string(type.ToArray()).Trim(), IsCollection = !isCollection.IsEmpty }); + + private static readonly Parser OpeningCurlyBracket = Operator('{'); + private static readonly Parser ClosingCurlyBracket = Operator('}'); + private static readonly Parser Colon = Operator(':'); + private static readonly Parser Comma = Operator(','); + + private static Parser Operator(char op) + { + return + from leading in Parse.WhiteSpace.Many() + from bracket in Parse.Char(op) + from trailing in Parse.WhiteSpace.Many() + select bracket; + } + + private static Parser TypeDefinition = + from keyword in TypeKeyword + from name in TypeName + from oB in OpeningCurlyBracket + from properties in Property.DelimitedBy(Comma) + from cB in ClosingCurlyBracket.End() + select new TypeDefinition { Name = name, Properties = properties.ToDictionary(pair => pair.Key, pair => pair.Value) }; + + public static TypeDefinition ParseType(string text) + { + return TypeDefinition.Parse(text); + } + } +} diff --git a/src/Libraries/PackingNodeModels/UnPack/UnPack.cs b/src/Libraries/PackingNodeModels/UnPack/UnPack.cs new file mode 100644 index 00000000000..e93e862d1cf --- /dev/null +++ b/src/Libraries/PackingNodeModels/UnPack/UnPack.cs @@ -0,0 +1,70 @@ +using Dynamo.Graph.Nodes; +using Newtonsoft.Json; +using PackingNodeModels.Properties; +using ProtoCore.AST.AssociativeAST; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PackingNodeModels.UnPack +{ + [NodeName("UnPack")] + [NodeCategory(BuiltinNodeCategories.CORE_PACKING)] + [NodeDescription("UnPackNodeDescription", typeof(Resource))] + [IsDesignScriptCompatible] + public class UnPack : PackingNode + { + public UnPack() + { + InPorts.Add(new PortModel(PortType.Input, this, new PortData(Resource.UnPackNodeDataInputName, Resource.UnPackNodeDataInputType))); + + ArgumentLacing = LacingStrategy.Longest; + } + + [JsonConstructor] + protected UnPack(IEnumerable inPorts, IEnumerable outPorts) + : base(inPorts, outPorts) + { + ArgumentLacing = LacingStrategy.Longest; + } + + protected override void RefreshTypeDefinitionPorts() + { + OutPorts.ToList().ForEach(portModel => OutPorts.Remove(portModel)); + + if (TypeDefinition != null) + { + foreach (var property in TypeDefinition.Properties) + { + OutPorts.Add(new PortModel(PortType.Output, this, new PortData(property.Key, property.Value.ToString()))); + } + } + } + + public override IEnumerable BuildOutputAst(List inputAstNodes) + { + var baseOutput = base.BuildOutputAst(inputAstNodes).ToList(); + + if (!IsValidInputState(inputAstNodes)) + { + baseOutput.Add(AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), AstFactory.BuildNullNode())); + return baseOutput; + } + + UseLevelAndReplicationGuide(inputAstNodes); + + foreach (var property in TypeDefinition.Properties) + { + int index = TypeDefinition.Properties.ToList().IndexOf(property); + var functionCall = AstFactory.BuildFunctionCall( + new Func(DSCore.UnPackFunctions.UnPackOutputByKey), + new List { inputAstNodes[1], AstFactory.BuildStringNode(property.Key) }); + baseOutput.Add(AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(index), functionCall)); + } + + return baseOutput; + } + } +} diff --git a/src/Libraries/PackingNodeModels/packages.config b/src/Libraries/PackingNodeModels/packages.config new file mode 100644 index 00000000000..ac123f2e374 --- /dev/null +++ b/src/Libraries/PackingNodeModels/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/LibraryViewExtension/web/library/layoutSpecs.json b/src/LibraryViewExtension/web/library/layoutSpecs.json index 96c4a218f7f..c1098e8abf5 100644 --- a/src/LibraryViewExtension/web/library/layoutSpecs.json +++ b/src/LibraryViewExtension/web/library/layoutSpecs.json @@ -884,6 +884,21 @@ } ] }, + { + "text": "Packing", + "iconUrl": "", + "elementType": "category", + "include": [ + { + "path": "Core.Packing.Pack" + }, + { + "path": "Core.Packing.UnPack" + } + ], + "childElements": [ + ] + }, { "text": "ImportExport", "iconUrl": "./dist/resources/Category.ImportExport.svg", diff --git a/test/Libraries/CoreNodesTests/CoreNodesTests.csproj b/test/Libraries/CoreNodesTests/CoreNodesTests.csproj index 821ed4e0642..78577ae0ad3 100644 --- a/test/Libraries/CoreNodesTests/CoreNodesTests.csproj +++ b/test/Libraries/CoreNodesTests/CoreNodesTests.csproj @@ -77,6 +77,7 @@ + @@ -116,6 +117,10 @@ CoreNodes False + + {C0D6DEE5-5532-4345-9C66-4C00D7FDB8BE} + DesignScriptBuiltin + {ef879a10-041d-4c68-83e7-3192685f1bae} DynamoServices diff --git a/test/Libraries/CoreNodesTests/PackingTests.cs b/test/Libraries/CoreNodesTests/PackingTests.cs new file mode 100644 index 00000000000..3c4f03c22a3 --- /dev/null +++ b/test/Libraries/CoreNodesTests/PackingTests.cs @@ -0,0 +1,157 @@ +using DesignScript.Builtin; +using DSCore; +using NUnit.Framework; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Dynamo.Tests +{ + public class PackingTests + { + public class UnPackOutputByKeyMethod + { + [Test] + public void WhenDictionaryIsNull_ReturnsNull() + { + var result = UnPackFunctions.UnPackOutputByKey(null, "property1"); + + Assert.Null(result); + } + + [Test] + public void WhenDictionaryIsNotNull_ReturnsValueAtKey() + { + var dictionary = Dictionary.ByKeysValues(new List { "property1" }, new List { "value" }); + + var result = UnPackFunctions.UnPackOutputByKey(dictionary, "property1"); + + Assert.AreEqual("value", result); + } + } + + public class PackOutputAsDictionaryMethod + { + [Test] + public void WhenKeysAreNull_ShouldReturnNull() + { + var result = PackFunctions.PackOutputAsDictionary(null, new List { true }, 1); + + Assert.Null(result); + } + + [Test] + public void WhenKeysIsEmptyList_ShouldReturnNull() + { + var result = PackFunctions.PackOutputAsDictionary(new List(), new List { true }, 1); + + Assert.Null(result); + } + + [Test] + public void WhenIsCollectionIsNull_ShouldReturnNull() + { + var result = PackFunctions.PackOutputAsDictionary(new List { "property1" }, null, 1); + + Assert.Null(result); + } + + [Test] + public void WhenIsCollectionIsEmpty_ShouldReturnNull() + { + var result = PackFunctions.PackOutputAsDictionary(new List { "property1" }, new List(), 1); + + Assert.Null(result); + } + + [Test] + public void WhenDataIsNull_ShouldReturnNull() + { + var result = PackFunctions.PackOutputAsDictionary(new List { "property1" }, new List { true }, null); + + Assert.Null(result); + } + + [Test] + public void WhenNoCollections_ReturnsDictionaryMatchingData() + { + var result = PackFunctions.PackOutputAsDictionary(new List { "property1", "property2" }, new List { false, false }, new ArrayList { 1,2 }) as List>; + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result[0].Count, Is.EqualTo(2)); + Assert.That(result[0]["property1"], Is.EqualTo(1)); + Assert.That(result[0]["property2"], Is.EqualTo(2)); + } + + [Test] + public void WhenNoCollectionsWithLacingDataOfSameSize_ReturnsDictionaryMatchingData() + { + var result = PackFunctions.PackOutputAsDictionary(new List { "property1", "property2" }, new List { false, false }, new ArrayList { new ArrayList { 1, 3 }, new ArrayList { 2, 4 } }) as List>; + + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result[0].Count, Is.EqualTo(2)); + Assert.That(result[0]["property1"], Is.EqualTo(1)); + Assert.That(result[0]["property2"], Is.EqualTo(2)); + Assert.That(result[1].Count, Is.EqualTo(2)); + Assert.That(result[1]["property1"], Is.EqualTo(3)); + Assert.That(result[1]["property2"], Is.EqualTo(4)); + } + + [Test] + public void WhenNoCollectionsWithLacingOfDifferentSize_ReturnsDictionaryWithDuplicateDataForShortestArray() + { + var result = PackFunctions.PackOutputAsDictionary(new List { "property1", "property2" }, new List { false, false }, new ArrayList { new ArrayList { 1, 3, 5 }, new ArrayList { 2, 4 } }) as List>; + + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result[0].Count, Is.EqualTo(2)); + Assert.That(result[0]["property1"], Is.EqualTo(1)); + Assert.That(result[0]["property2"], Is.EqualTo(2)); + Assert.That(result[1].Count, Is.EqualTo(2)); + Assert.That(result[1]["property1"], Is.EqualTo(3)); + Assert.That(result[1]["property2"], Is.EqualTo(4)); + Assert.That(result[2].Count, Is.EqualTo(2)); + Assert.That(result[2]["property1"], Is.EqualTo(5)); + Assert.That(result[2]["property2"], Is.EqualTo(4)); + } + + [Test] + public void WhenDataHasCollections_ReturnsDictionaryMatchingData() + { + var result = PackFunctions.PackOutputAsDictionary(new List { "property1", "property2" }, new List { true, false }, new ArrayList { new ArrayList { 1, 3, 5 }, 10 }) as List>; + + Assert.That(result.Count, Is.EqualTo(1)); + Assert.That(result[0]["property1"], Is.EqualTo(new ArrayList { 1, 3, 5 })); + Assert.That(result[0]["property2"], Is.EqualTo(10)); + } + + [Test] + public void WhenDataHasCollectionsWithLacingOfSameSize_ReturnsDictionaryMatchingData() + { + var result = PackFunctions.PackOutputAsDictionary(new List { "property1", "property2" }, new List { true, false }, new ArrayList { new ArrayList { new ArrayList { 1, 3, 5 }, new ArrayList { 2, 4, 6 } }, new ArrayList { 10, 15 } }) as List>; + + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result[0]["property1"], Is.EqualTo(new ArrayList { 1, 3, 5 })); + Assert.That(result[0]["property2"], Is.EqualTo(10)); + Assert.That(result[1]["property1"], Is.EqualTo(new ArrayList { 2, 4, 6 })); + Assert.That(result[1]["property2"], Is.EqualTo(15)); + } + + [Test] + public void WhenDataHasCollectionsWithLacingOfDifferntSize_ReturnsDictionaryWithDuplicatedShortestValue() + { + var result = PackFunctions.PackOutputAsDictionary(new List { "property1", "property2" }, new List { true, false }, new ArrayList { new ArrayList { new ArrayList { 1, 3, 5 }, new ArrayList { 2, 4, 6 } }, new ArrayList { 10, 15, 20 } }) as List>; + + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result[0]["property1"], Is.EqualTo(new ArrayList { 1, 3, 5 })); + Assert.That(result[0]["property2"], Is.EqualTo(10)); + Assert.That(result[1]["property1"], Is.EqualTo(new ArrayList { 2, 4, 6 })); + Assert.That(result[1]["property2"], Is.EqualTo(15)); + Assert.That(result[2]["property1"], Is.EqualTo(new ArrayList { 2, 4, 6 })); + Assert.That(result[2]["property2"], Is.EqualTo(20)); + } + } + } +} diff --git a/test/Libraries/PackingNodeTests/Pack/PackTests.cs b/test/Libraries/PackingNodeTests/Pack/PackTests.cs new file mode 100644 index 00000000000..9eb8b3da47a --- /dev/null +++ b/test/Libraries/PackingNodeTests/Pack/PackTests.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using PackingNodeModels.Pack; +using VMDataBridge; +using System.Collections; +using NUnit.Framework; +using Dynamo.Graph.Nodes; +using PackingNodeModels.Pack.Validation; +using Moq; +using ProtoCore.AST.AssociativeAST; + +namespace Dynamo.Tests +{ + public class PackTests : DynamoModelTestBase + { + private string testFileWithPackUnPackNodes = Path.Combine(TestDirectory, @"core\PackingNode", "packingnodes.dyn"); + private const string typeDefinitionString = "Type A { prop:Type1 }"; + + private Pack GetPackNode() + { + return CurrentDynamoModel.CurrentWorkspace.Nodes.OfType().First(); + } + + private void HookUpTypeDefinition(Pack node, string typeString = typeDefinitionString) + { + node.InputNodes[0] = new Tuple(0, null); + DataBridge.BridgeData(node.GUID.ToString(), new ArrayList { typeString }); + } + + private void SetValidationManager(Pack node, IValidationManager validationManager) + { + var prop = node.GetType().GetField("validationManager", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + prop.SetValue(node, validationManager); + } + + public class Constructor : PackTests + { + [Test] + public void DefaultConstructor_ShouldAddDefaultOutport() + { + OpenModel(testFileWithPackUnPackNodes); + + var packNode = GetPackNode(); + + Assert.That(packNode.OutPorts.Count, Is.EqualTo(1)); + Assert.That(packNode.OutPorts[0].Name, Is.EqualTo("Out")); + Assert.That(packNode.OutPorts[0].ToolTip, Is.EqualTo("Dictionary")); + Assert.That(packNode.OutPorts[0].PortType, Is.EqualTo(PortType.Output)); + } + } + + public class DataBridgeCallBack : PackTests + { + [Test] + public void TypeDefinitionChange_ShouldAddPorts() + { + OpenModel(testFileWithPackUnPackNodes); + + var packNode = GetPackNode(); + + packNode.RequestScheduledTask += (action) => action(); + + Assert.That(packNode.InPorts.Count, Is.EqualTo(1)); + + HookUpTypeDefinition(packNode, "Type Something { prop1: Type1, prop2: Type2[] }"); + + Assert.That(packNode.InPorts.Count, Is.EqualTo(3)); + Assert.That(packNode.InPorts[1].Name, Is.EqualTo("prop1")); + Assert.That(packNode.InPorts[1].ToolTip, Is.EqualTo("Type1")); + Assert.That(packNode.InPorts[2].Name, Is.EqualTo("prop2")); + Assert.That(packNode.InPorts[2].ToolTip, Is.EqualTo("Type2[]")); + } + + [Test] + public void TypeDefinitionChangeToNull_ShouldRemovePorts() + { + OpenModel(testFileWithPackUnPackNodes); + + var packNode = GetPackNode(); + + packNode.RequestScheduledTask += (action) => action(); + + HookUpTypeDefinition(packNode, "Type Something { prop1: Type1, prop2: Type2[] }"); + + Assert.That(packNode.InPorts.Count, Is.EqualTo(3)); + + packNode.InputNodes.Clear(); + DataBridge.BridgeData(packNode.GUID.ToString(), new ArrayList { "" }); + + Assert.That(packNode.InPorts.Count, Is.EqualTo(1)); + } + } + + public class ValidateInputsMethod : PackTests + { + [Test] + public void NoCachedValues_ShouldCallHandleValidationForEveryValue() + { + OpenModel(testFileWithPackUnPackNodes); + + var packNode = GetPackNode(); + var validationManagerMock = new Mock(); + validationManagerMock.Setup(mock => mock.HandleValidation(It.IsAny>())); + validationManagerMock.Setup(mock => mock.Warnings).Returns(new List().AsReadOnly()); + SetValidationManager(packNode, validationManagerMock.Object); + + HookUpTypeDefinition(packNode, typeDefinitionString); + + DataBridge.BridgeData(packNode.GUID.ToString(), new ArrayList { typeDefinitionString, "someValue"}); + + validationManagerMock.Verify(mock => mock.HandleValidation(It.Is>(d => d.Count == 1)), Times.Once()); + } + + [Test] + public void WithCachedValues_ShouldCallHandleValidationForUnCachedValues() + { + OpenModel(testFileWithPackUnPackNodes); + + var packNode = GetPackNode(); + var validationManagerMock = new Mock(); + validationManagerMock.Setup(mock => mock.HandleValidation(It.IsAny>())); + validationManagerMock.Setup(mock => mock.Warnings).Returns(new List().AsReadOnly()); + SetValidationManager(packNode, validationManagerMock.Object); + var typeDefinitionString = "Type Something { prop1: Type1, prop2: Type2 }"; + + HookUpTypeDefinition(packNode, typeDefinitionString); + + DataBridge.BridgeData(packNode.GUID.ToString(), new ArrayList { typeDefinitionString, "someValue" }); + validationManagerMock.ResetCalls(); + + DataBridge.BridgeData(packNode.GUID.ToString(), new ArrayList { typeDefinitionString, "someValue", "newValue" }); + + validationManagerMock.Verify(mock => mock.HandleValidation(It.Is>(d => d.Count == 1)), Times.Once()); + } + + [Test] + public void WithCachedValuesWithEdits_ShouldCallHandleValidationForUnCachedValues() + { + OpenModel(testFileWithPackUnPackNodes); + + var packNode = GetPackNode(); + var validationManagerMock = new Mock(); + validationManagerMock.Setup(mock => mock.HandleValidation(It.IsAny>())); + validationManagerMock.Setup(mock => mock.Warnings).Returns(new List().AsReadOnly()); + SetValidationManager(packNode, validationManagerMock.Object); + var typeDefinitionString = "Type Something { prop1: Type1, prop2: Type2 }"; + + HookUpTypeDefinition(packNode, typeDefinitionString); + + DataBridge.BridgeData(packNode.GUID.ToString(), new ArrayList { typeDefinitionString, "someValue" }); + validationManagerMock.ResetCalls(); + + DataBridge.BridgeData(packNode.GUID.ToString(), new ArrayList { typeDefinitionString, "someNewValue", "newValue" }); + + validationManagerMock.Verify(mock => mock.HandleValidation(It.Is>(d => d.Count == 2)), Times.Once()); + } + + [Test] + public void WarningStateChange_ShouldCallOnNodeModified() + { + var onNodeModifiedCalled = false; + OpenModel(testFileWithPackUnPackNodes); + + var packNode = GetPackNode(); + packNode.Modified += (x) => onNodeModifiedCalled = true; + + var validationManagerMock = new Mock(); + validationManagerMock.Setup(mock => mock.HandleValidation(It.IsAny>())); + validationManagerMock.SetupSequence(mock => mock.Warnings) + .Returns(new List().AsReadOnly()) + .Returns(new List() { new Validation() }.AsReadOnly()); + + SetValidationManager(packNode, validationManagerMock.Object); + + HookUpTypeDefinition(packNode, typeDefinitionString); + + Assert.True(onNodeModifiedCalled); + } + } + + public class BuildOutPutAstMethod : PackTests + { + [Test] + public void NullNodeList_ReturnsNullNode() + { + OpenModel(testFileWithPackUnPackNodes); + + var packNode = GetPackNode(); + packNode.InPorts[0].Connectors.Add(new Graph.Connectors.ConnectorModel(new PortModel(PortType.Input, packNode, new PortData(null, null)), new PortModel(PortType.Input, packNode, new PortData(null, null)), Guid.NewGuid())); + + HookUpTypeDefinition(packNode); + + packNode.State = ElementState.Active; + + var output = packNode.BuildOutputAst(null); + + Assert.That(output.ElementAt(1), Is.InstanceOf()); + var binaryOutput = output.ElementAt(1) as BinaryExpressionNode; + Assert.That(binaryOutput.RightNode, Is.InstanceOf()); + } + + [Test] + public void NullNodeInsideNodeList_ReturnsNullNode() + { + OpenModel(testFileWithPackUnPackNodes); + + var packNode = GetPackNode(); + packNode.InPorts[0].Connectors.Add(new Graph.Connectors.ConnectorModel(new PortModel(PortType.Input, packNode, new PortData(null, null)), new PortModel(PortType.Input, packNode, new PortData(null, null)), Guid.NewGuid())); + + HookUpTypeDefinition(packNode); + + packNode.State = ElementState.Active; + + var output = packNode.BuildOutputAst(new List { AstFactory.BuildStringNode("DST"), AstFactory.BuildNullNode() }); + + Assert.That(output.ElementAt(1), Is.InstanceOf()); + var binaryOutput = output.ElementAt(1) as BinaryExpressionNode; + Assert.That(binaryOutput.RightNode, Is.InstanceOf()); + } + + [Test] + public void NodeInWarningState_ReturnsNullNode() + { + OpenModel(testFileWithPackUnPackNodes); + + var packNode = GetPackNode(); + packNode.InPorts[0].Connectors.Add(new Graph.Connectors.ConnectorModel(new PortModel(PortType.Input, packNode, new PortData(null, null)), new PortModel(PortType.Input, packNode, new PortData(null, null)), Guid.NewGuid())); + + HookUpTypeDefinition(packNode); + + packNode.State = ElementState.Warning; + + var output = packNode.BuildOutputAst(new List { AstFactory.BuildStringNode("DST"), AstFactory.BuildStringNode("a") }); + + Assert.That(output.ElementAt(1), Is.InstanceOf()); + var binaryOutput = output.ElementAt(1) as BinaryExpressionNode; + Assert.That(binaryOutput.RightNode, Is.InstanceOf()); + } + + [Test] + public void ValidDateAndInputs_ReturnFunctionCallNode() + { + OpenModel(testFileWithPackUnPackNodes); + + var packNode = GetPackNode(); + packNode.InPorts[0].Connectors.Add(new Graph.Connectors.ConnectorModel(new PortModel(PortType.Input, packNode, new PortData(null, null)), new PortModel(PortType.Input, packNode, new PortData(null, null)), Guid.NewGuid())); + + HookUpTypeDefinition(packNode); + + packNode.State = ElementState.Active; + + var output = packNode.BuildOutputAst(new List { AstFactory.BuildStringNode("DST"), AstFactory.BuildStringNode("a") }); + + Assert.That(output.ElementAt(1), Is.InstanceOf()); + var binaryOutput = output.ElementAt(1) as BinaryExpressionNode; + + Assert.That(binaryOutput.RightNode, Is.InstanceOf()); + var identifierListNode = binaryOutput.RightNode as IdentifierListNode; + + Assert.That(identifierListNode.RightNode, Is.InstanceOf()); + } + } + } +} diff --git a/test/Libraries/PackingNodeTests/Pack/Validation/PortValidatorTests.cs b/test/Libraries/PackingNodeTests/Pack/Validation/PortValidatorTests.cs new file mode 100644 index 00000000000..12834a3a6d5 --- /dev/null +++ b/test/Libraries/PackingNodeTests/Pack/Validation/PortValidatorTests.cs @@ -0,0 +1,187 @@ +using Dynamo.Graph.Nodes; +using NUnit.Framework; +using PackingNodeModels; +using PackingNodeModels.Pack; +using PackingNodeModels.Pack.Validation; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Dynamo.Tests +{ + public class PortValidatorTests + { + public class ValidateMethod + { + [Test] + public void WhenValueIsNull_ShouldReturnErrorMessage() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String" }); + var result = PortValidator.Validate(property, null, null); + + Assert.That(result, Is.EqualTo("Input prop1 expected type String but received null.")); + } + + public class WhenPropertyIsCollection + { + [Test] + public void WhenValueIsNotAnArrayList_ShouldReturnErrorMessage() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String", IsCollection = true }); + var result = PortValidator.Validate(property, "value", null); + + Assert.That(result, Is.EqualTo("Input prop1 expected an array of type String but received a single value.")); + } + + [Test] + public void WhenValueIsCollection_ValidData_ShouldReturnNull() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String", IsCollection = true }); + var result = PortValidator.Validate(property, new ArrayList { "value1", "value2", "value3" }, null); + + Assert.Null(result); + } + + [Test] + public void WhenValueIsACollectionOfSingleValuesAndCollections_ShouldReturnErrorMessage() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String", IsCollection = true }); + var result = PortValidator.Validate(property, new ArrayList { new ArrayList { "value1", "value2" }, "value3" }, null); + + Assert.That(result, Is.EqualTo("Input prop1 expected an array of type String but received a mixed combination of single values and arrays.")); + } + + [Test] + public void WhenValueIsCollectionOfCollection_ValidData_ShouldReturnNull() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String", IsCollection = true }); + var result = PortValidator.Validate(property, new ArrayList { new ArrayList { "value1", "value2" }, new ArrayList { "value3" } }, null); + + Assert.Null(result); + } + + [Test] + public void WhenValueIsCollectionOfCollection_OneInvalidValue_ShouldReturnErrorMessage() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String", IsCollection = true }); + var result = PortValidator.Validate(property, new ArrayList { new ArrayList { "value1", "value2" }, new ArrayList { 1 } }, null); + + Assert.NotNull(result); + } + + [Test] + public void WhenValueIsCollectionOfCollection_NestedArray_ShouldReturnErrorMessage() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String", IsCollection = true }); + var result = PortValidator.Validate(property, new ArrayList { new ArrayList { "value1", "value2" }, new ArrayList { new ArrayList { "value3" } } }, null); + + Assert.That(result, Is.EqualTo("Input prop1 expected an array of type String but received a nested array.")); + } + } + + public class WhenPropertyIsNotCollection + { + [Test] + public void WhenValueIsArrayList_NestedArrays_ShouldReturnError() //Lacing with subvalues being collections + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String", IsCollection = false }); + var result = PortValidator.Validate(property, new ArrayList { new ArrayList { "value1", "value2" } }, null); + + Assert.That(result, Is.EqualTo("Input prop1 expected a single value of type String but received a list of values.")); + } + + [Test] + public void WhenValueIsArrayList_ValidData_ShouldReturnNull() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String", IsCollection = false }); + var result = PortValidator.Validate(property, new ArrayList { "value1", "value2" }, null); + + Assert.Null(result); + } + + [Test] + public void WhenValueIsSingle_Invalid_ShouldReturnError() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String", IsCollection = false }); + var result = PortValidator.Validate(property, 1, null); + + Assert.NotNull(result); + } + + [Test] + public void WhenValueIsSingle_Valid_ShouldReturnError() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String", IsCollection = false }); + var result = PortValidator.Validate(property, "string", null); + + Assert.Null(result); + } + } + } + public class ValidateTypeMatch + { + [Test] + public void WhenTypeIsKnown_TypeMatch_ShouldReturnNull() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String" }); + var result = PortValidator.ValidateTypeMatch(property, "string", null); + + Assert.Null(result); + } + + [Test] + public void WhenTypeIsKnown_TypeDoesNotMatch_ShouldReturnErrorMessage() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "String" }); + var result = PortValidator.ValidateTypeMatch(property, 10, null); + + Assert.That(result, Is.EqualTo("Input prop1 expected type String but received System.Int32.")); + } + + [Test] + public void WhenTypeIsUnknown_HookedToNonPackNode_ShouldReturnErrorMessage() + { + var property = new KeyValuePair("prop1", new PropertyType { Type = "Something" }); + var portModel = new PortModel(PortType.Input, null, new PortData("prop1","")); + var ownerPortModel = new PortModel(PortType.Output, null, new PortData("out", "")); + portModel.Connectors.Add(new Graph.Connectors.ConnectorModel(ownerPortModel, portModel, Guid.NewGuid())); + var result = PortValidator.ValidateTypeMatch(property, 10, portModel); + + Assert.That(result, Is.EqualTo("Input prop1 expected type Something but received System.Int32.")); + } + + [Test] + public void WhenTypeIsUnknown_HookedToPackNodeButWrongMatch_ShouldReturnErrorMessage() + { + var previousPack = new Pack(); + previousPack.GetType().GetProperty("TypeDefinition").SetValue(previousPack, new TypeDefinition { Name = "NotSomething" }); + + var property = new KeyValuePair("prop1", new PropertyType { Type = "Something" }); + var portModel = new PortModel(PortType.Input, null, new PortData("prop1", "")); + var ownerPortModel = new PortModel(PortType.Output, previousPack, new PortData("out", "")); + portModel.Connectors.Add(new Graph.Connectors.ConnectorModel(ownerPortModel, portModel, Guid.NewGuid())); + var result = PortValidator.ValidateTypeMatch(property, 10, portModel); + + Assert.That(result, Is.EqualTo("Input prop1 expected type Something but received NotSomething.")); + } + + [Test] + public void WhenTypeIsUnknown_HookedToPackNodeWithTypeMatch_ShouldReturnNull() + { + var previousPack = new Pack(); + previousPack.GetType().GetProperty("TypeDefinition").SetValue(previousPack, new TypeDefinition { Name = "Something" }); + + var property = new KeyValuePair("prop1", new PropertyType { Type = "Something" }); + var portModel = new PortModel(PortType.Input, null, new PortData("prop1", "")); + var ownerPortModel = new PortModel(PortType.Output, previousPack, new PortData("out", "")); + portModel.Connectors.Add(new Graph.Connectors.ConnectorModel(ownerPortModel, portModel, Guid.NewGuid())); + var result = PortValidator.ValidateTypeMatch(property, 10, portModel); + + Assert.Null(result); + } + } + } +} diff --git a/test/Libraries/PackingNodeTests/Pack/Validation/ValidationManagerTests.cs b/test/Libraries/PackingNodeTests/Pack/Validation/ValidationManagerTests.cs new file mode 100644 index 00000000000..4ee782ab0c0 --- /dev/null +++ b/test/Libraries/PackingNodeTests/Pack/Validation/ValidationManagerTests.cs @@ -0,0 +1,133 @@ +using Dynamo.Graph.Nodes; +using NUnit.Framework; +using PackingNodeModels; +using PackingNodeModels.Pack; +using PackingNodeModels.Pack.Validation; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Dynamo.Tests +{ + public class ValidationManagerTests + { + private Pack GetNode(TypeDefinition type = null) + { + var pack = new Pack(); + + if (type == null) + { + type = new TypeDefinition(); + type.Name = "Something"; + type.Properties = new Dictionary { { "prop1", new PropertyType { Type = "String" } } }; + pack.InPorts.Add(new Graph.Nodes.PortModel(PortType.Input, pack, new PortData("prop1", ""))); + pack.InPorts[1].Connectors.Add(new Graph.Connectors.ConnectorModel(pack.InPorts[1], pack.InPorts[1], Guid.NewGuid())); + } + + + var prop = pack.GetType().GetProperty("TypeDefinition"); + prop.SetValue(pack, type); + + return pack; + } + + public class HandleValidation: ValidationManagerTests + { + [Test] + public void WhenInvalidDataIsGiven_AddsAWarningToNode() + { + var pack = GetNode(); + var validationManager = new ValidationManager(pack); + + var data = new Dictionary { { 1, 10 } }; //Should be a string value. + + validationManager.HandleValidation(data); + + Assert.That(pack.State, Is.EqualTo(ElementState.PersistentWarning)); + Assert.That(validationManager.Warnings.Count, Is.EqualTo(1)); + } + + [Test] + public void WhenInvalidDataIsDisconnected_ShouldClearWarnings() + { + var pack = GetNode(); + var validationManager = new ValidationManager(pack); + + var data = new Dictionary { { 1, 10 } }; //Should be a string value. + + validationManager.HandleValidation(data); + + try + { + pack.InPorts[1].Connectors.Clear(); + } + catch { } + + var data2 = new Dictionary { { 1, null } }; + + validationManager.HandleValidation(data2); + + Assert.That(pack.State, Is.Not.EqualTo(ElementState.PersistentWarning)); + Assert.That(validationManager.Warnings.Count, Is.EqualTo(0)); + } + + [Test] + public void WhenValidDataIsConnected_ShouldNotAddWarnings() + { + var pack = GetNode(); + var validationManager = new ValidationManager(pack); + + var data = new Dictionary { { 1, "String" } }; + + validationManager.HandleValidation(data); + + Assert.That(pack.State, Is.Not.EqualTo(ElementState.PersistentWarning)); + Assert.That(validationManager.Warnings.Count, Is.EqualTo(0)); + } + + [Test] + public void WhenValidDataIsConnectedOnPreviouslyInvalidValue_ShouldClearWarnings() + { + var pack = GetNode(); + var validationManager = new ValidationManager(pack); + + var data = new Dictionary { { 1, 10 } }; //Should be a string value + + validationManager.HandleValidation(data); + + Assert.That(pack.State, Is.EqualTo(ElementState.PersistentWarning)); + Assert.That(validationManager.Warnings.Count, Is.EqualTo(1)); + + var data2 = new Dictionary { { 1, "String" } }; + + validationManager.HandleValidation(data2); + + Assert.That(pack.State, Is.Not.EqualTo(ElementState.PersistentWarning)); + Assert.That(validationManager.Warnings.Count, Is.EqualTo(0)); + } + + [Test] + public void WhenInValidDataIsConnectedOnPreviouslyInvalidValue_ShouldKeepWarnings() + { + var pack = GetNode(); + var validationManager = new ValidationManager(pack); + + var data = new Dictionary { { 1, 10 } }; //Should be a string value + + validationManager.HandleValidation(data); + + Assert.That(pack.State, Is.EqualTo(ElementState.PersistentWarning)); + Assert.That(validationManager.Warnings.Count, Is.EqualTo(1)); + + var data2 = new Dictionary { { 1, null } }; + + validationManager.HandleValidation(data2); + + Assert.That(pack.State, Is.EqualTo(ElementState.PersistentWarning)); + Assert.That(validationManager.Warnings.Count, Is.EqualTo(1)); + } + } + } +} diff --git a/test/Libraries/PackingNodeTests/PackingNodeTests.cs b/test/Libraries/PackingNodeTests/PackingNodeTests.cs new file mode 100644 index 00000000000..f9c1e8ced5f --- /dev/null +++ b/test/Libraries/PackingNodeTests/PackingNodeTests.cs @@ -0,0 +1,233 @@ +using Dynamo.Graph.Nodes; +using NUnit.Framework; +using PackingNodeModels; +using ProtoCore.AST.AssociativeAST; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using VMDataBridge; + +namespace Dynamo.Tests +{ + public class PackingNodeTests : DynamoModelTestBase + { + private string testFileWithPackUnPackNodes = Path.Combine(TestDirectory, @"core\PackingNode", "packingnodes.dyn"); + private const string typeDefinitionString = "Type A { prop:Type1 }"; + + private PackingNode GetPackingNode() + { + return CurrentDynamoModel.CurrentWorkspace.Nodes.OfType().First(); + } + + private void HookUpTypeDefinition(PackingNode node, string typeString = typeDefinitionString) + { + node.InputNodes[0] = new Tuple(0, null); + DataBridge.BridgeData(node.GUID.ToString(), new ArrayList { typeString }); + } + + public class Constructor : PackingNodeTests + { + [Test] + public void DefaultCtor_ShouldAddTypeDefinitionInport() + { + OpenModel(testFileWithPackUnPackNodes); + + var packingNode = GetPackingNode(); + + Assert.That(packingNode.InPorts.Count, Is.GreaterThan(0)); + Assert.That(packingNode.InPorts[0].Name, Is.EqualTo("Type")); + Assert.That(packingNode.InPorts[0].ToolTip, Is.EqualTo("TypeDefinition as a string")); + Assert.That(packingNode.InPorts[0].PortType, Is.EqualTo(PortType.Input)); + } + } + + public class TypeDefinitionProperty : PackingNodeTests + { + [Test] + public void SettingValue_ShouldClearErrorsAndWarnings() + { + OpenModel(testFileWithPackUnPackNodes); + + var packingNode = GetPackingNode(); + + packingNode.Error("some error"); + + Assert.True(packingNode.IsInErrorState); + + HookUpTypeDefinition(packingNode); + + Assert.False(packingNode.IsInErrorState); + } + + [Test] + public void SettingValue_ShouldRequestScheduledTask_WithRefreshTypeDefinitionPortsMethod() + { + OpenModel(testFileWithPackUnPackNodes); + + var packingNode = GetPackingNode(); + var requested = false; + var methodName = ""; + packingNode.RequestScheduledTask += (action) => { methodName = action.Method.Name; requested = true; }; + + HookUpTypeDefinition(packingNode); + + Assert.IsTrue(requested); + Assert.AreEqual("RefreshTypeDefinitionPorts", methodName); + } + } + + public class IsInValidStateProperty : PackingNodeTests + { + [Test] + public void Getter_ShouldConjunctErrorStateAndWarningStates() + { + OpenModel(testFileWithPackUnPackNodes); + + var packingNode = GetPackingNode(); + + packingNode.State = ElementState.Error; + Assert.False(packingNode.IsInValidState); + + packingNode.State = ElementState.Warning; + Assert.False(packingNode.IsInValidState); + + packingNode.State = ElementState.PersistentWarning; + Assert.False(packingNode.IsInValidState); + + packingNode.State = ElementState.Active; + Assert.True(packingNode.IsInValidState); + } + } + + public class BuildOutputAstMethod : PackingNodeTests + { + [Test] + public void ShouldReturnDataBridgeAst() + { + OpenModel(testFileWithPackUnPackNodes); + + var packingNode = GetPackingNode(); + + var astOutput = packingNode.BuildOutputAst(null); + + Assert.That(astOutput.Count(), Is.GreaterThanOrEqualTo(1)); + Assert.That(astOutput.ElementAt(0), Is.InstanceOf()); + + var binaryExpressionNode = astOutput.ElementAt(0) as BinaryExpressionNode; + Assert.That(binaryExpressionNode.RightNode, Is.InstanceOf()); + + var identifierListNode = binaryExpressionNode.RightNode as IdentifierListNode; + Assert.That(identifierListNode.RightNode, Is.InstanceOf()); + + var functionCallNode = identifierListNode.RightNode as FunctionCallNode; + Assert.That("BridgeData", Is.EqualTo(functionCallNode.Function.Name)); + } + } + + public class DataBridgeCallBackMethod : PackingNodeTests + { + [Test] + public void InputNodesCountIsZero_ShouldSetTypeDefinitionToNull() + { + OpenModel(testFileWithPackUnPackNodes); + + var packingNode = GetPackingNode(); + + Assert.That(packingNode.InputNodes.Count, Is.EqualTo(0)); + + DataBridge.BridgeData(packingNode.GUID.ToString(), new ArrayList { "" }); + + Assert.IsNull(packingNode.TypeDefinition); + } + + [Test] + public void InputNodesIndex0NotDefined_ShouldSetTypeDefinitionToNull() + { + OpenModel(testFileWithPackUnPackNodes); + + var packingNode = GetPackingNode(); + + Assert.That(packingNode.InputNodes.Count, Is.EqualTo(0)); + + packingNode.InputNodes[1] = new Tuple(1, null); + + DataBridge.BridgeData(packingNode.GUID.ToString(), new ArrayList { "" }); + + Assert.IsNull(packingNode.TypeDefinition); + } + + [Test] + public void InputNodesIndex0IsNull_ShouldSetTypeDefinitionToNull() + { + OpenModel(testFileWithPackUnPackNodes); + + var packingNode = GetPackingNode(); + + Assert.That(packingNode.InputNodes.Count, Is.EqualTo(0)); + + packingNode.InputNodes[0] = null; + + DataBridge.BridgeData(packingNode.GUID.ToString(), new ArrayList { "" }); + + Assert.IsNull(packingNode.TypeDefinition); + } + + [Test] + public void TypeValueIsNotString_ShouldAddAWarning() + { + OpenModel(testFileWithPackUnPackNodes); + + var packingNode = GetPackingNode(); + + Assert.That(packingNode.State, Is.Not.EqualTo(ElementState.PersistentWarning)); + + packingNode.InputNodes[0] = new Tuple(0, null); + + DataBridge.BridgeData(packingNode.GUID.ToString(), new ArrayList { 1 }); + + Assert.False(packingNode.IsInValidState); + Assert.That(packingNode.State, Is.EqualTo(ElementState.PersistentWarning)); + } + + [Test] + public void TypeValueIsInvalidTypeString_ShouldAddAWarning() + { + OpenModel(testFileWithPackUnPackNodes); + + var packingNode = GetPackingNode(); + + Assert.That(packingNode.State, Is.Not.EqualTo(ElementState.PersistentWarning)); + + packingNode.InputNodes[0] = new Tuple(0, null); + + DataBridge.BridgeData(packingNode.GUID.ToString(), new ArrayList { "some invalid type definition string" }); + + Assert.False(packingNode.IsInValidState); + Assert.That(packingNode.State, Is.EqualTo(ElementState.PersistentWarning)); + } + + [Test] + public void TypeValueIsValidTypeString_ShouldUpdateTypeDefinition() + { + OpenModel(testFileWithPackUnPackNodes); + + var packingNode = GetPackingNode(); + + Assert.Null(packingNode.TypeDefinition); + + HookUpTypeDefinition(packingNode, "Type Something { prop1: Type1, prop2: Type2[] }"); + + Assert.That(packingNode.TypeDefinition.Name, Is.EqualTo("Something")); + Assert.That(packingNode.TypeDefinition.Properties.Count, Is.EqualTo(2)); + Assert.That(packingNode.TypeDefinition.Properties.ElementAt(0).Key, Is.EqualTo("prop1")); + Assert.That(packingNode.TypeDefinition.Properties.ElementAt(0).Value.Type, Is.EqualTo("Type1")); + Assert.That(packingNode.TypeDefinition.Properties.ElementAt(0).Value.IsCollection, Is.EqualTo(false)); + Assert.That(packingNode.TypeDefinition.Properties.ElementAt(1).Key, Is.EqualTo("prop2")); + Assert.That(packingNode.TypeDefinition.Properties.ElementAt(1).Value.Type, Is.EqualTo("Type2")); + Assert.That(packingNode.TypeDefinition.Properties.ElementAt(1).Value.IsCollection, Is.EqualTo(true)); + } + } + } +} diff --git a/test/Libraries/PackingNodeTests/PackingNodeTests.csproj b/test/Libraries/PackingNodeTests/PackingNodeTests.csproj new file mode 100644 index 00000000000..2a732a47e9e --- /dev/null +++ b/test/Libraries/PackingNodeTests/PackingNodeTests.csproj @@ -0,0 +1,86 @@ + + + + + + + + Debug + AnyCPU + {79F2ADE0-11C4-47F7-9CE4-ACFEFC25CAAF} + Library + Properties + PackingNodeTests + PackingNodeTests + v4.5 + 512 + + + true + full + false + $(OutputPath) + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + $(OutputPath) + TRACE + prompt + 4 + + + + ..\..\..\src\packages\Moq.4.2.1507.0118\lib\net40\Moq.dll + False + True + + + False + ..\..\..\extern\NUnit\nunit.framework.dll + False + + + + + + + + + + + + + + + + + + + + + {7858FA8C-475F-4B8E-B468-1F8200778CF8} + DynamoCore + + + {7A9E0314-966F-4584-BAA3-7339CBB849D1} + ProtoCore + + + {d915633b-8bd6-4860-b0a7-4d689df445fa} + PackingNodeModels + + + {ccb6e56b-2da1-4eba-a1f9-e8510e129d12} + VMDataBridge + + + {472084ed-1067-4b2c-8737-3839a6143eb2} + DynamoCoreTests + + + + \ No newline at end of file diff --git a/test/Libraries/PackingNodeTests/Properties/AssemblyInfo.cs b/test/Libraries/PackingNodeTests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..8cd1cbbcad9 --- /dev/null +++ b/test/Libraries/PackingNodeTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PackingNodeTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PackingNodeTests")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("79f2ade0-11c4-47f7-9ce4-acfefc25caaf")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/test/Libraries/PackingNodeTests/UnPack/UnPackTests.cs b/test/Libraries/PackingNodeTests/UnPack/UnPackTests.cs new file mode 100644 index 00000000000..6773473ae25 --- /dev/null +++ b/test/Libraries/PackingNodeTests/UnPack/UnPackTests.cs @@ -0,0 +1,124 @@ +using Dynamo.Graph.Nodes; +using NUnit.Framework; +using PackingNodeModels.UnPack; +using ProtoCore.AST.AssociativeAST; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VMDataBridge; + +namespace Dynamo.Tests +{ + public class UnPackTests : DynamoModelTestBase + { + private string testFileWithPackUnPackNodes = Path.Combine(TestDirectory, @"core\PackingNode", "packingnodes.dyn"); + private const string typeDefinitionString = "Type A { prop:Type1 }"; + + private UnPack GetUnPackNode() + { + return CurrentDynamoModel.CurrentWorkspace.Nodes.OfType().First(); + } + + private void HookUpTypeDefinition(UnPack node, string typeString = typeDefinitionString) + { + node.InputNodes[0] = new Tuple(0, null); + DataBridge.BridgeData(node.GUID.ToString(), new ArrayList { typeString }); + } + + public class Constructor : UnPackTests + { + [Test] + public void ArgumentLacing_SetToLongest() + { + var node = new UnPack(); + Assert.That(node.ArgumentLacing, Is.EqualTo(LacingStrategy.Longest)); + } + } + + public class DataBridgeCallBack : UnPackTests + { + [Test] + public void TypeDefinitionChange_ShouldAddPorts() + { + OpenModel(testFileWithPackUnPackNodes); + + var unpackNode = GetUnPackNode(); + + unpackNode.RequestScheduledTask += (action) => action(); + + Assert.That(unpackNode.OutPorts.Count, Is.EqualTo(0)); + + HookUpTypeDefinition(unpackNode, "Type Something { prop1: Type1, prop2: Type2[] }"); + + Assert.That(unpackNode.OutPorts.Count, Is.EqualTo(2)); + Assert.That(unpackNode.OutPorts[0].Name, Is.EqualTo("prop1")); + Assert.That(unpackNode.OutPorts[0].ToolTip, Is.EqualTo("Type1")); + Assert.That(unpackNode.OutPorts[1].Name, Is.EqualTo("prop2")); + Assert.That(unpackNode.OutPorts[1].ToolTip, Is.EqualTo("Type2[]")); + } + + [Test] + public void TypeDefinitionChangeToNull_ShouldRemovePorts() + { + OpenModel(testFileWithPackUnPackNodes); + + var unpackNode = GetUnPackNode(); + + unpackNode.RequestScheduledTask += (action) => action(); + + HookUpTypeDefinition(unpackNode, "Type Something { prop1: Type1, prop2: Type2[] }"); + + Assert.That(unpackNode.OutPorts.Count, Is.EqualTo(2)); + + unpackNode.InputNodes.Clear(); + DataBridge.BridgeData(unpackNode.GUID.ToString(), new ArrayList { "" }); + + Assert.That(unpackNode.OutPorts.Count, Is.EqualTo(0)); + } + } + + public class BuildOutPutAstMethod : UnPackTests + { + [Test] + public void NullNodeInsideNodeList_ReturnsNullNode() + { + OpenModel(testFileWithPackUnPackNodes); + + var unpackNode = GetUnPackNode(); + unpackNode.InPorts[0].Connectors.Add(new Graph.Connectors.ConnectorModel(new PortModel(PortType.Input, unpackNode, new PortData(null, null)), new PortModel(PortType.Input, unpackNode, new PortData(null, null)), Guid.NewGuid())); + + HookUpTypeDefinition(unpackNode); + + unpackNode.State = ElementState.Active; + + var output = unpackNode.BuildOutputAst(new List { AstFactory.BuildStringNode("DST"), AstFactory.BuildNullNode() }); + + Assert.That(output.ElementAt(1), Is.InstanceOf()); + var binaryOutput = output.ElementAt(1) as BinaryExpressionNode; + Assert.That(binaryOutput.RightNode, Is.InstanceOf()); + } + + [Test] + public void ValidData_ShouldOutPutOneFunctionCallPerTypeProperty() + { + OpenModel(testFileWithPackUnPackNodes); + + var unpackNode = GetUnPackNode(); + unpackNode.RequestScheduledTask += (action) => action(); + unpackNode.InPorts[0].Connectors.Add(new Graph.Connectors.ConnectorModel(new PortModel(PortType.Input, unpackNode, new PortData(null, null)), new PortModel(PortType.Input, unpackNode, new PortData(null, null)), Guid.NewGuid())); + + HookUpTypeDefinition(unpackNode, "Type Something { prop1: Type1, prop2: Type2[]}"); + + var output = unpackNode.BuildOutputAst(new List { AstFactory.BuildStringNode("DST"), AstFactory.BuildStringNode("In") }); + + Assert.That(output.Count(), Is.EqualTo(3)); //1 DataBridge and 2 properties; + Assert.That(((output.ElementAt(1) as BinaryExpressionNode).RightNode as IdentifierListNode).RightNode, Is.InstanceOf()); + Assert.That(((output.ElementAt(2) as BinaryExpressionNode).RightNode as IdentifierListNode).RightNode, Is.InstanceOf()); + } + } + } +} diff --git a/test/core/PackingNode/packingnodes.dyn b/test/core/PackingNode/packingnodes.dyn new file mode 100644 index 00000000000..7f1a901be1c --- /dev/null +++ b/test/core/PackingNode/packingnodes.dyn @@ -0,0 +1,121 @@ +{ + "Uuid": "1e3be5e6-78bb-4665-af10-7dd3c94e217c", + "IsCustomNode": false, + "Description": null, + "Name": "packingnodes", + "ElementResolver": { + "ResolutionMap": {} + }, + "Inputs": [], + "Outputs": [], + "Nodes": [ + { + "ConcreteType": "PackingNodeModels.Pack.Pack, PackingNodeModels", + "NodeType": "ExtensionNode", + "Id": "19e41724d5bd494ca0fa2c104f34b3ff", + "Inputs": [ + { + "Id": "829d359c072c47f89bf8740359a019fd", + "Name": "Type", + "Description": "TypeDefinition as a string", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Outputs": [ + { + "Id": "65b92271fcfd4bedad04fa1350649810", + "Name": "Out", + "Description": "Dictionary", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Replication": "Disabled", + "Description": "Pack Node" + }, + { + "ConcreteType": "PackingNodeModels.UnPack.UnPack, PackingNodeModels", + "NodeType": "ExtensionNode", + "Id": "71f86e45fe1c4100a6cdec7506bc0119", + "Inputs": [ + { + "Id": "c34caedab5aa44768a40e48dcabb761c", + "Name": "Type", + "Description": "TypeDefinition as a string", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + }, + { + "Id": "dbcda1643c8040c38b6754a9a1bb7c0c", + "Name": "In", + "Description": "Dictionary", + "UsingDefaultValue": false, + "Level": 2, + "UseLevels": false, + "KeepListStructure": false + } + ], + "Outputs": [], + "Replication": "Longest", + "Description": "UnPack Node" + } + ], + "Connectors": [], + "Dependencies": [], + "Bindings": [], + "View": { + "Dynamo": { + "ScaleFactor": 1.0, + "HasRunWithoutCrash": true, + "IsVisibleInDynamoLibrary": true, + "Version": "2.1.0.5634", + "RunType": "Automatic", + "RunPeriod": "1000" + }, + "Camera": { + "Name": "Background Preview", + "EyeX": -17.0, + "EyeY": 24.0, + "EyeZ": 50.0, + "LookX": 12.0, + "LookY": -13.0, + "LookZ": -58.0, + "UpX": 0.0, + "UpY": 1.0, + "UpZ": 0.0 + }, + "NodeViews": [ + { + "ShowGeometry": true, + "Name": "Pack", + "Id": "19e41724d5bd494ca0fa2c104f34b3ff", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "X": 455.5, + "Y": 350.0 + }, + { + "ShowGeometry": true, + "Name": "UnPack", + "Id": "71f86e45fe1c4100a6cdec7506bc0119", + "IsSetAsInput": false, + "IsSetAsOutput": false, + "Excluded": false, + "X": 777.5, + "Y": 344.0 + } + ], + "Annotations": [], + "X": 0.0, + "Y": 0.0, + "Zoom": 1.0 + } +} \ No newline at end of file From 003d51f27f3895d5a306080976997ef7aa14498d Mon Sep 17 00:00:00 2001 From: Mathieu Bilodeau-Roy Date: Fri, 20 Jul 2018 14:32:12 -0400 Subject: [PATCH 2/7] -Cleaned code to follow coding and naming standards -Added xml documentation --- src/Libraries/CoreNodes/Packing.cs | 26 +++++- src/Libraries/PackingNodeModels/Pack/Pack.cs | 14 +++- .../Pack/Validation/PortValidator.cs | 79 +++++++++++++------ .../Pack/Validation/ValidationManager.cs | 15 +++- .../PackingNodeModels/PackingNode.cs | 37 ++++++++- .../PackingNodeModels/TypeDefinition.cs | 20 +++++ .../PackingNodeModels/UnPack/UnPack.cs | 10 ++- 7 files changed, 166 insertions(+), 35 deletions(-) diff --git a/src/Libraries/CoreNodes/Packing.cs b/src/Libraries/CoreNodes/Packing.cs index 6b42957e449..47e01e9d89d 100644 --- a/src/Libraries/CoreNodes/Packing.cs +++ b/src/Libraries/CoreNodes/Packing.cs @@ -11,10 +11,20 @@ namespace DSCore [IsVisibleInDynamoLibrary(false)] public class PackFunctions { + /// + /// Packs data to a dictionary while manually handling lacing in a "Longest" strategy. + /// + /// + /// Ordered list of keys to use in the output dictionary + /// Ordered list of whether a value is a collection or not, which helps differenciating Lacing from a normal array value. + /// Ordered ArrayList or List of ArrayList defining the data to be packed. + /// List of Dictionary matching the ordered keys and data lists. public static object PackOutputAsDictionary(List keys, List isCollection, [ArbitraryDimensionArrayImport] object data) { if (keys == null || keys.Count == 0 || isCollection == null || isCollection.Count == 0 || data == null) + { return null; + } var result = new List>(); @@ -23,7 +33,9 @@ public static object PackOutputAsDictionary(List keys, List isColl var outputCount = GetOuputCountFrom(isCollection, inputs); for (int outputIndex = 0; outputIndex < outputCount; ++outputIndex) + { result.Add(CreateOuputDictionary(keys, isCollection, inputs, outputIndex)); + } return result; } @@ -65,14 +77,15 @@ private static int GetOuputCountFrom(List isCollection, ArrayList inputs) .Where(i => i is ArrayList) .Cast(); - if (!subArrays.Any()) - return 1; + if (!subArrays.Any()) return 1; return subArrays.Max(subArray => { var index = inputs.IndexOf(subArray); if (!isCollection[index] || (subArray[0] is ArrayList)) + { return subArray.Count; + } return 1; }); @@ -82,10 +95,15 @@ private static int GetOuputCountFrom(List isCollection, ArrayList inputs) [IsVisibleInDynamoLibrary(false)] public class UnPackFunctions { + /// + /// Returns the value of dictionary at a given key. + /// + /// Dictionary exposing all available values + /// The key of the value to retrieve + /// The value retrieved from the dictionary at the given key public static object UnPackOutputByKey(DesignScript.Builtin.Dictionary dictionary, string key) { - if (dictionary == null) - return null; + if (dictionary == null) return null; return dictionary.ValueAtKey(key); } diff --git a/src/Libraries/PackingNodeModels/Pack/Pack.cs b/src/Libraries/PackingNodeModels/Pack/Pack.cs index 00ceb2aebc4..756995f1593 100644 --- a/src/Libraries/PackingNodeModels/Pack/Pack.cs +++ b/src/Libraries/PackingNodeModels/Pack/Pack.cs @@ -6,11 +6,12 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace PackingNodeModels.Pack { + /// + /// Creates a node that takes in a TypeDefinition and input values defined by the given TypeDefinition and outputs it as dictionary. + /// [NodeName("Pack")] [NodeCategory(BuiltinNodeCategories.CORE_PACKING)] [NodeDescription("PackNodeDescription", typeof(Resource))] @@ -28,6 +29,11 @@ public Pack() validationManager = new ValidationManager(this); } + /// + /// Private constructor used for serialization. + /// + /// A collection of objects. + /// A collection of objects. [JsonConstructor] protected Pack(IEnumerable inPorts, IEnumerable outPorts) : base(inPorts, outPorts) @@ -58,7 +64,9 @@ protected override void ValidateInputs(List values) for (int i = 1; i < values.Count; ++i) { if (cachedValues == null || cachedValues.Count <= i || values[i] != cachedValues[i]) + { valuesByIndex[i] = values[i]; + } } validationManager.HandleValidation(valuesByIndex); @@ -69,7 +77,9 @@ protected override void ValidateInputs(List values) //FIXME This doesn't seem right. Is there a way to build a conditional node that would depend on validation inputs, instead of building the output twice? //Right now, it's ran once, then the DataBridge is invoked, which runs the validation and re-trigger the building of the ouput if warnings are gone/new. if (wasInWarningState != validationManager.Warnings.Any()) + { OnNodeModified(true); + } } public override IEnumerable BuildOutputAst(List inputAstNodes) diff --git a/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs b/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs index eca0d520112..0f466374ca4 100644 --- a/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs +++ b/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs @@ -8,39 +8,57 @@ namespace PackingNodeModels.Pack.Validation { - public class PortValidator + /// + /// Used to validate that a given value matches the type defined by the PortModel+PropertyType combination of a Pack node InPort. + /// Comes with a basic mapping of known types to Dynamo Types. Assumes that an unknown type is coming from another Pack node. + /// + internal class PortValidator { + /// + /// "Primitive" known to Pack node. + /// private static Dictionary> CompatibleTypes = new Dictionary> { { "String", new List() { typeof(string) } }, { "Float64", new List() { typeof(float), typeof(double), typeof(Int32), typeof(Int64) } }, - { "autodesk.aec.primitive:arc-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Arc) } }, - { "autodesk.aec.primitive:circle-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Circle) } }, - { "autodesk.aec.primitive:coordinatesystem-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.CoordinateSystem) } }, - { "autodesk.aec.primitive:cone-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Cone) } }, - { "autodesk.aec.primitive:curve-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Curve) } }, - { "autodesk.aec.primitive:cuboid-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Cuboid) } }, - { "autodesk.aec.primitive:cylinder-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Cylinder) } }, - { "autodesk.aec.primitive:ellipse-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Ellipse) } }, - { "autodesk.aec.primitive:line-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Line) } }, - { "autodesk.aec.primitive:plane-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Plane) } }, - { "autodesk.aec.primitive:point-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Point) } }, - { "autodesk.aec.primitive:polycurve-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.PolyCurve) } }, - { "autodesk.aec.primitive:polygon-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Polygon) } }, - { "autodesk.aec.primitive:rectangle-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Rectangle) } }, - { "autodesk.aec.primitive:sphere-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Sphere) } }, - { "autodesk.aec.primitive:vector-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Vector) } }, - { "autodesk.aec.primitive:surface-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Surface) } }, - { "autodesk.soliddef:model-1.0.0", new List() { typeof(Autodesk.DesignScript.Geometry.Geometry) } } //TODO double check soliddef's type. + { "Arc", new List() { typeof(Autodesk.DesignScript.Geometry.Arc) } }, + { "Circle", new List() { typeof(Autodesk.DesignScript.Geometry.Circle) } }, + { "CoordinateSystem", new List() { typeof(Autodesk.DesignScript.Geometry.CoordinateSystem) } }, + { "Cone", new List() { typeof(Autodesk.DesignScript.Geometry.Cone) } }, + { "Curve", new List() { typeof(Autodesk.DesignScript.Geometry.Curve) } }, + { "Cuboid", new List() { typeof(Autodesk.DesignScript.Geometry.Cuboid) } }, + { "Cylinder", new List() { typeof(Autodesk.DesignScript.Geometry.Cylinder) } }, + { "Ellipse", new List() { typeof(Autodesk.DesignScript.Geometry.Ellipse) } }, + { "Line", new List() { typeof(Autodesk.DesignScript.Geometry.Line) } }, + { "Plane", new List() { typeof(Autodesk.DesignScript.Geometry.Plane) } }, + { "Point", new List() { typeof(Autodesk.DesignScript.Geometry.Point) } }, + { "PolyCurve", new List() { typeof(Autodesk.DesignScript.Geometry.PolyCurve) } }, + { "Polygon", new List() { typeof(Autodesk.DesignScript.Geometry.Polygon) } }, + { "Rectangle", new List() { typeof(Autodesk.DesignScript.Geometry.Rectangle) } }, + { "Sphere", new List() { typeof(Autodesk.DesignScript.Geometry.Sphere) } }, + { "Vector", new List() { typeof(Autodesk.DesignScript.Geometry.Vector) } }, + { "Surface", new List() { typeof(Autodesk.DesignScript.Geometry.Surface) } }, + { "Geometry", new List() { typeof(Autodesk.DesignScript.Geometry.Geometry) } } //TODO SolidDef or Geometry? }; + /// + /// Validates a value against its port's associated property. + /// + /// + /// + /// + /// public static string Validate(KeyValuePair property, object value, PortModel portModel) { if (value == null) + { return $"Input {property.Key} expected type {property.Value.Type} but received null."; + } if (property.Value.IsCollection) + { return ValidateCollection(property, value, portModel); + } return ValidateSingleValue(property, value, portModel); } @@ -48,7 +66,9 @@ public static string Validate(KeyValuePair property, object private static string ValidateSingleValue(KeyValuePair property, object value, PortModel portModel) { if (value is ArrayList arrayList) + { return ValidateSingleValueLacing(property, arrayList, portModel); + } return ValidateTypeMatch(property, value, portModel); } @@ -56,7 +76,9 @@ private static string ValidateSingleValue(KeyValuePair pro private static string ValidateSingleValueLacing(KeyValuePair property, ArrayList values, PortModel portModel) { if (ContainsCollections(values)) + { return $"Input {property.Key} expected a single value of type {property.Value.Type} but received a list of values."; + } return ValidateValues(property, values, portModel); } @@ -64,15 +86,21 @@ private static string ValidateSingleValueLacing(KeyValuePair property, object value, PortModel portModel) { if (!(value is ArrayList)) + { return $"Input {property.Key} expected an array of type {property.Value.Type} but received a single value."; + } var values = value as ArrayList; if (IsMixedCombinationOfCollectionAndSingleValues(values)) + { return $"Input {property.Key} expected an array of type {property.Value.Type} but received a mixed combination of single values and arrays."; + } if (ContainsCollections(values)) + { return ValidateArrayLacing(property, values, portModel); + } return ValidateValues(property, values, portModel); } @@ -85,12 +113,13 @@ private static string ValidateArrayLacing(KeyValuePair pro { //Deeper arrays not allowed if (subValue is ArrayList) + { return $"Input {property.Key} expected an array of type {property.Value.Type} but received a nested array."; + } else { var validation = ValidateTypeMatch(property, subValue, portModel); - if (!string.IsNullOrEmpty(validation)) - return validation; + if (!string.IsNullOrEmpty(validation)) return validation; } } @@ -104,7 +133,9 @@ public static string ValidateTypeMatch(KeyValuePair proper if (IsKnownType(property.Value.Type)) { if (!IsTypeMatch(value, property.Value.Type)) + { return $"Input {property.Key} expected type {property.Value.Type} but received {value?.GetType()}."; + } return null; } @@ -118,8 +149,7 @@ private static string ValidateValues(KeyValuePair property { var validation = ValidateTypeMatch(property, value, portModel); - if (!string.IsNullOrEmpty(validation)) - return validation; + if (!string.IsNullOrEmpty(validation)) return validation; } return null; @@ -154,8 +184,7 @@ private static bool IsKnownType(string type) private static bool IsTypeMatch(object value, string expectedType) { - if (!CompatibleTypes.ContainsKey(expectedType)) - return true; + if (!CompatibleTypes.ContainsKey(expectedType)) return true; return CompatibleTypes[expectedType].Exists(x => x == value?.GetType()); } diff --git a/src/Libraries/PackingNodeModels/Pack/Validation/ValidationManager.cs b/src/Libraries/PackingNodeModels/Pack/Validation/ValidationManager.cs index 3df1acc1bd3..a009a7dbacd 100644 --- a/src/Libraries/PackingNodeModels/Pack/Validation/ValidationManager.cs +++ b/src/Libraries/PackingNodeModels/Pack/Validation/ValidationManager.cs @@ -13,6 +13,10 @@ internal interface IValidationManager ReadOnlyCollection Warnings { get; } } + /// + /// Manages validation warnings for a given Pack node. + /// Warnings are added to the Pack node and kept track of in the manager. + /// internal class ValidationManager : IValidationManager { private List warnings; @@ -27,6 +31,10 @@ public ValidationManager(Pack node) warnings = new List(); } + /// + /// Validates values from the Pack node and add and/or remove warnings according to the validation result. + /// + /// public void HandleValidation(Dictionary valuesByIndex) { var properties = Node.TypeDefinition.Properties.ToList(); @@ -34,13 +42,14 @@ public void HandleValidation(Dictionary valuesByIndex) { ClearWarningForPortIndex(pair.Key); - if (!Node.InPorts[pair.Key].Connectors.Any()) - continue; + if (!Node.InPorts[pair.Key].Connectors.Any()) continue; var validation = PortValidator.Validate(properties[pair.Key - 1], pair.Value, Node.InPorts[pair.Key]); //i - 1 because we're skipping the Type port. if (!string.IsNullOrEmpty(validation)) + { warnings.Add(new Validation { Message = validation, PortIndex = pair.Key }); + } } ComputeWarnings(); @@ -55,7 +64,9 @@ private void ComputeWarnings() { Node.ClearErrorsAndWarnings(); if (warnings.Any()) + { Node.Warning(String.Join(String.Empty, warnings.Select(w => w.Message).ToArray()), true); + } } } diff --git a/src/Libraries/PackingNodeModels/PackingNode.cs b/src/Libraries/PackingNodeModels/PackingNode.cs index 568b5c96422..7a9f4aab48f 100644 --- a/src/Libraries/PackingNodeModels/PackingNode.cs +++ b/src/Libraries/PackingNodeModels/PackingNode.cs @@ -16,15 +16,28 @@ namespace PackingNodeModels { + /// + /// Base class for nodes that utilize the dynamic-like functionality of taking in a TypeDefinition as an input and redefining InPorts or OutPorts according to this definition. + /// Exposes two methods that should be overriden by extending classes : + /// -ValidateInputs - which will receive InPorts' data. + /// -RefreshTypeDefinitionPorts - which will be called after the TypeDefinition changes. + /// public abstract class PackingNode : NodeModel { protected string TypeDefinitionPortName = Resource.TypePortName; + /// + /// Event made to communicate with the NodeViewCustomization and request a scheduled action. + /// public event Action RequestScheduledTask; private string _cachedTypeDefinition; private TypeDefinition _typeDefinition; + /// + /// A TypeDefinition that will be used by extending classes to define OutPorts and InPorts + /// Modifying the TypeDefinition implies clearing errors and warnings and calling RefreshTypeDefinitionPorts + /// [JsonProperty("TypeDefinition")] public TypeDefinition TypeDefinition { @@ -35,13 +48,19 @@ public TypeDefinition TypeDefinition protected set { if (value == null) + { _cachedTypeDefinition = null; + } + _typeDefinition = value; ClearErrorsAndWarnings(); RequestScheduledTask?.Invoke(RefreshTypeDefinitionPorts); } } + /// + /// Returns whether the node is in a valid state, that is if it does not have errors or warnings. + /// [JsonIgnore] public bool IsInValidState { @@ -51,6 +70,9 @@ public bool IsInValidState } } + /// + /// Base constructor used to define the constant Type InPort. + /// public PackingNode() { InPorts.Add(new PortModel(PortType.Input, this, new PortData(TypeDefinitionPortName, Resource.TypePortTooltip))); @@ -58,6 +80,11 @@ public PackingNode() RegisterAllPorts(); } + /// + /// Private constructor used for serialization. + /// + /// A collection of objects. + /// A collection of objects. protected PackingNode(IEnumerable inPorts, IEnumerable outPorts) : base(inPorts, outPorts) { } @@ -69,7 +96,8 @@ protected override void OnBuilt() public override IEnumerable BuildOutputAst(List inputAstNodes) { - return new List() { + return new List() + { AstFactory.BuildAssignment( AstFactory.BuildIdentifier(AstIdentifierBase + "_dummy"), DataBridge.GenerateBridgeDataAst(GUID.ToString(), AstFactory.BuildExprList(inputAstNodes ?? new List()))) @@ -92,7 +120,9 @@ private void OnEvaluationComplete(object obj) private void CheckTypeDefinition(ArrayList inputValues) { if (InputNodes.Count == 0 || !InputNodes.ContainsKey(0) || InputNodes[0] == null) + { TypeDefinition = null; + } else { if (inputValues[0] is string typeDef) @@ -111,7 +141,9 @@ private void CheckTypeDefinition(ArrayList inputValues) } } else + { Warning(Resource.TypePortWarning, true); + } } } @@ -127,6 +159,9 @@ public override void Dispose() } } + /// + /// ViewCustomization for PackingNodes whose purpose is being able to schedule actions from the PackingNode. + /// public class PackingNodeView : INodeViewCustomization { private DynamoModel dynamoModel; diff --git a/src/Libraries/PackingNodeModels/TypeDefinition.cs b/src/Libraries/PackingNodeModels/TypeDefinition.cs index 2c2c21cb9f7..b1d079b9c16 100644 --- a/src/Libraries/PackingNodeModels/TypeDefinition.cs +++ b/src/Libraries/PackingNodeModels/TypeDefinition.cs @@ -9,6 +9,9 @@ namespace PackingNodeModels { + /// + /// Defines a Type by its name and its properties. + /// [IsVisibleInDynamoLibrary(false)] public class TypeDefinition { @@ -17,6 +20,9 @@ public class TypeDefinition public Dictionary Properties { get; set; } } + /// + /// Define a Type as a string name and whether it is a collection or not. + /// [IsVisibleInDynamoLibrary(false)] public class PropertyType { @@ -29,6 +35,15 @@ public override string ToString() } } + /// + /// Typescript-like parser to create TypeDefinitions out of formatted strings. + /// i.e.: + /// "Type MyType { + /// property1 : String, + /// property2 : Float64, + /// property3 : Point[] + /// }" + /// [IsVisibleInDynamoLibrary(false)] internal class TypeDefinitionParser { @@ -77,6 +92,11 @@ from properties in Property.DelimitedBy(Comma) from cB in ClosingCurlyBracket.End() select new TypeDefinition { Name = name, Properties = properties.ToDictionary(pair => pair.Key, pair => pair.Value) }; + /// + /// Parses a string and returns the corresponding TypeDefinition or throws a Sprache.ParseException. + /// + /// Formatted string definining a TypeDefinition + /// The parsed TypeDefinition public static TypeDefinition ParseType(string text) { return TypeDefinition.Parse(text); diff --git a/src/Libraries/PackingNodeModels/UnPack/UnPack.cs b/src/Libraries/PackingNodeModels/UnPack/UnPack.cs index e93e862d1cf..b8c041b7314 100644 --- a/src/Libraries/PackingNodeModels/UnPack/UnPack.cs +++ b/src/Libraries/PackingNodeModels/UnPack/UnPack.cs @@ -10,6 +10,9 @@ namespace PackingNodeModels.UnPack { + /// + /// Creates a node that takes in a Dictionary (or ArrayList of Dictionary) and a TypeDefinition to output the unpacked data from the dictionary. + /// [NodeName("UnPack")] [NodeCategory(BuiltinNodeCategories.CORE_PACKING)] [NodeDescription("UnPackNodeDescription", typeof(Resource))] @@ -23,13 +26,18 @@ public UnPack() ArgumentLacing = LacingStrategy.Longest; } + /// + /// Private constructor used for serialization. + /// + /// A collection of objects. + /// A collection of objects. [JsonConstructor] protected UnPack(IEnumerable inPorts, IEnumerable outPorts) : base(inPorts, outPorts) { ArgumentLacing = LacingStrategy.Longest; } - + protected override void RefreshTypeDefinitionPorts() { OutPorts.ToList().ForEach(portModel => OutPorts.Remove(portModel)); From f473fda8eab8efdbe7b0e2ef65e62eacd05e818d Mon Sep 17 00:00:00 2001 From: Mathieu Bilodeau-Roy Date: Fri, 20 Jul 2018 14:47:04 -0400 Subject: [PATCH 3/7] -Moving front-facing strings to Resource files --- .../Pack/Validation/PortValidator.cs | 15 +++--- .../Properties/Resource.Designer.cs | 54 +++++++++++++++++++ .../Properties/Resource.en-US.resx | 18 +++++++ .../Properties/Resource.resx | 18 +++++++ 4 files changed, 98 insertions(+), 7 deletions(-) diff --git a/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs b/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs index 0f466374ca4..d7d530a4f20 100644 --- a/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs +++ b/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs @@ -1,4 +1,5 @@ using Dynamo.Graph.Nodes; +using PackingNodeModels.Properties; using System; using System.Collections; using System.Collections.Generic; @@ -52,7 +53,7 @@ public static string Validate(KeyValuePair property, object { if (value == null) { - return $"Input {property.Key} expected type {property.Value.Type} but received null."; + return string.Format(Resource.PortValidatorNullValueMessage, property.Key, property.Value.Type); } if (property.Value.IsCollection) @@ -77,7 +78,7 @@ private static string ValidateSingleValueLacing(KeyValuePair prop { if (!(value is ArrayList)) { - return $"Input {property.Key} expected an array of type {property.Value.Type} but received a single value."; + return string.Format(Resource.PortValidatorSingleValueInsteadOfArrayMessage, property.Key, property.Value.Type); } var values = value as ArrayList; if (IsMixedCombinationOfCollectionAndSingleValues(values)) { - return $"Input {property.Key} expected an array of type {property.Value.Type} but received a mixed combination of single values and arrays."; + return string.Format(Resource.PortValidatorMixedCombinationMessage, property.Key, property.Value.Type); } if (ContainsCollections(values)) @@ -114,7 +115,7 @@ private static string ValidateArrayLacing(KeyValuePair pro //Deeper arrays not allowed if (subValue is ArrayList) { - return $"Input {property.Key} expected an array of type {property.Value.Type} but received a nested array."; + return string.Format(Resource.PortValidatorNestedArrayMessage, property.Key, property.Value.Type); } else { @@ -134,7 +135,7 @@ public static string ValidateTypeMatch(KeyValuePair proper { if (!IsTypeMatch(value, property.Value.Type)) { - return $"Input {property.Key} expected type {property.Value.Type} but received {value?.GetType()}."; + return string.Format(Resource.PortValidatorWrongTypeMessage, property.Key, property.Value.Type, value?.GetType()); } return null; @@ -161,7 +162,7 @@ private static string ValidateUnknownType(KeyValuePair pro var owner = portModel.Connectors[0].Start.Owner as Pack; if (owner == null || !property.Value.Type.Equals(owner.TypeDefinition.Name, StringComparison.InvariantCultureIgnoreCase)) { - return $"Input {property.Key} expected type {property.Value.Type} but received {owner?.TypeDefinition.Name ?? value?.GetType().ToString()}."; + return string.Format(Resource.PortValidatorWrongTypeMessage, property.Key, property.Value.Type, owner?.TypeDefinition.Name ?? value?.GetType().ToString()); } return null; diff --git a/src/Libraries/PackingNodeModels/Properties/Resource.Designer.cs b/src/Libraries/PackingNodeModels/Properties/Resource.Designer.cs index 3d291ecca8b..73d44878d12 100644 --- a/src/Libraries/PackingNodeModels/Properties/Resource.Designer.cs +++ b/src/Libraries/PackingNodeModels/Properties/Resource.Designer.cs @@ -78,6 +78,60 @@ internal static string PackNodeOutputPortName { } } + /// + /// Looks up a localized string similar to Input {0} expected a single value of type {1} but received a list of values.. + /// + internal static string PortValidatorArrayInsteadOfSingleValueMessage { + get { + return ResourceManager.GetString("PortValidatorArrayInsteadOfSingleValueMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Input {0} expected an array of type {1} but received a mixed combination of single values and arrays.. + /// + internal static string PortValidatorMixedCombinationMessage { + get { + return ResourceManager.GetString("PortValidatorMixedCombinationMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Input {0} expected an array of type {1} but received a nested array.. + /// + internal static string PortValidatorNestedArrayMessage { + get { + return ResourceManager.GetString("PortValidatorNestedArrayMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Input {0} expected type {1} but received null.. + /// + internal static string PortValidatorNullValueMessage { + get { + return ResourceManager.GetString("PortValidatorNullValueMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Input {0} expected an array of type {1} but received a single value.. + /// + internal static string PortValidatorSingleValueInsteadOfArrayMessage { + get { + return ResourceManager.GetString("PortValidatorSingleValueInsteadOfArrayMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Input {0} expected type {1} but received {2}.. + /// + internal static string PortValidatorWrongTypeMessage { + get { + return ResourceManager.GetString("PortValidatorWrongTypeMessage", resourceCulture); + } + } + /// /// Looks up a localized string similar to Type. /// diff --git a/src/Libraries/PackingNodeModels/Properties/Resource.en-US.resx b/src/Libraries/PackingNodeModels/Properties/Resource.en-US.resx index f35a933ea6e..7f25bbb9843 100644 --- a/src/Libraries/PackingNodeModels/Properties/Resource.en-US.resx +++ b/src/Libraries/PackingNodeModels/Properties/Resource.en-US.resx @@ -123,6 +123,24 @@ Out + + Input {0} expected a single value of type {1} but received a list of values. + + + Input {0} expected an array of type {1} but received a mixed combination of single values and arrays. + + + Input {0} expected an array of type {1} but received a nested array. + + + Input {0} expected type {1} but received null. + + + Input {0} expected an array of type {1} but received a single value. + + + Input {0} expected type {1} but received {2}. + Type diff --git a/src/Libraries/PackingNodeModels/Properties/Resource.resx b/src/Libraries/PackingNodeModels/Properties/Resource.resx index f35a933ea6e..7f25bbb9843 100644 --- a/src/Libraries/PackingNodeModels/Properties/Resource.resx +++ b/src/Libraries/PackingNodeModels/Properties/Resource.resx @@ -123,6 +123,24 @@ Out + + Input {0} expected a single value of type {1} but received a list of values. + + + Input {0} expected an array of type {1} but received a mixed combination of single values and arrays. + + + Input {0} expected an array of type {1} but received a nested array. + + + Input {0} expected type {1} but received null. + + + Input {0} expected an array of type {1} but received a single value. + + + Input {0} expected type {1} but received {2}. + Type From 1c67d28d0c02265cf1452159c785b20c1142b236 Mon Sep 17 00:00:00 2001 From: Mathieu Bilodeau-Roy Date: Fri, 20 Jul 2018 15:20:03 -0400 Subject: [PATCH 4/7] -Fixed an issue where passing in a faulty type definition would not remove the previous one. --- src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs | 4 ++-- src/Libraries/PackingNodeModels/PackingNode.cs | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs b/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs index 54fa221d12b..1a847fc937c 100644 --- a/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs +++ b/src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs @@ -45,7 +45,7 @@ // to distinguish one build from another. AssemblyFileVersion is specified // in AssemblyVersionInfo.cs so that it can be easily incremented by the // automated build process. -[assembly: AssemblyVersion("2.1.0.5655")] +[assembly: AssemblyVersion("2.1.0.5656")] // By default, the "Product version" shown in the file properties window is @@ -64,4 +64,4 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("2.1.0.5655")] +[assembly: AssemblyFileVersion("2.1.0.5656")] diff --git a/src/Libraries/PackingNodeModels/PackingNode.cs b/src/Libraries/PackingNodeModels/PackingNode.cs index 7a9f4aab48f..a972a0f4c9a 100644 --- a/src/Libraries/PackingNodeModels/PackingNode.cs +++ b/src/Libraries/PackingNodeModels/PackingNode.cs @@ -113,7 +113,11 @@ private void OnEvaluationComplete(object obj) if (obj is ArrayList inputValues) { CheckTypeDefinition(inputValues); - ValidateInputs(inputValues.Cast().ToList()); + + if (TypeDefinition != null) + { + ValidateInputs(inputValues.Cast().ToList()); + } } } @@ -136,6 +140,7 @@ private void CheckTypeDefinition(ArrayList inputValues) } catch (Sprache.ParseException e) { + TypeDefinition = null; Warning(e.Message, true); } } From 2a98045abda56630cfb1f695e132c5e8c0bda377 Mon Sep 17 00:00:00 2001 From: Mathieu Bilodeau-Roy Date: Mon, 23 Jul 2018 11:26:41 -0400 Subject: [PATCH 5/7] -Added null check to prevent esceptions while building the outputast -Added Bool and Int32 to known types. -Exposed "typeid" in the pack node output --- src/Libraries/PackingNodeModels/Pack/Pack.cs | 4 +++- .../PackingNodeModels/Pack/Validation/PortValidator.cs | 2 ++ src/Libraries/PackingNodeModels/UnPack/UnPack.cs | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Libraries/PackingNodeModels/Pack/Pack.cs b/src/Libraries/PackingNodeModels/Pack/Pack.cs index 756995f1593..799f6e49718 100644 --- a/src/Libraries/PackingNodeModels/Pack/Pack.cs +++ b/src/Libraries/PackingNodeModels/Pack/Pack.cs @@ -86,14 +86,16 @@ public override IEnumerable BuildOutputAst(List AstFactory.BuildStringNode(input.Key) as AssociativeNode).ToList(); + keys.Add(AstFactory.BuildStringNode("typeid")); //FIXME Only way I found to pass in the context to the output function call so it knows how replications should work. //Maybe a tuple of keys,context would be better? var isCollectionInputs = TypeDefinition.Properties.Select(input => AstFactory.BuildBooleanNode(input.Value.IsCollection) as AssociativeNode).ToList(); diff --git a/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs b/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs index d7d530a4f20..2252e1441f5 100644 --- a/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs +++ b/src/Libraries/PackingNodeModels/Pack/Validation/PortValidator.cs @@ -20,6 +20,8 @@ internal class PortValidator /// private static Dictionary> CompatibleTypes = new Dictionary> { + { "Bool", new List() { typeof(bool), typeof(Boolean) } }, + { "Int32", new List() { typeof(int), typeof(Int32) } }, { "String", new List() { typeof(string) } }, { "Float64", new List() { typeof(float), typeof(double), typeof(Int32), typeof(Int64) } }, { "Arc", new List() { typeof(Autodesk.DesignScript.Geometry.Arc) } }, diff --git a/src/Libraries/PackingNodeModels/UnPack/UnPack.cs b/src/Libraries/PackingNodeModels/UnPack/UnPack.cs index b8c041b7314..73f3c53482e 100644 --- a/src/Libraries/PackingNodeModels/UnPack/UnPack.cs +++ b/src/Libraries/PackingNodeModels/UnPack/UnPack.cs @@ -55,7 +55,7 @@ public override IEnumerable BuildOutputAst(List Date: Wed, 25 Jul 2018 10:46:11 -0400 Subject: [PATCH 6/7] -Added an event on port connected and disconnected to the packing node to force execution of the graph in non-automatic mode. --- .../PackingNodeModels/PackingNode.cs | 52 ++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/Libraries/PackingNodeModels/PackingNode.cs b/src/Libraries/PackingNodeModels/PackingNode.cs index a972a0f4c9a..a564b3e6b36 100644 --- a/src/Libraries/PackingNodeModels/PackingNode.cs +++ b/src/Libraries/PackingNodeModels/PackingNode.cs @@ -1,5 +1,7 @@ using Dynamo.Controls; +using Dynamo.Graph.Connectors; using Dynamo.Graph.Nodes; +using Dynamo.Graph.Workspaces; using Dynamo.Models; using Dynamo.Scheduler; using Dynamo.ViewModels; @@ -30,6 +32,7 @@ public abstract class PackingNode : NodeModel /// Event made to communicate with the NodeViewCustomization and request a scheduled action. /// public event Action RequestScheduledTask; + public event Action RequestExecution; private string _cachedTypeDefinition; private TypeDefinition _typeDefinition; @@ -78,6 +81,7 @@ public PackingNode() InPorts.Add(new PortModel(PortType.Input, this, new PortData(TypeDefinitionPortName, Resource.TypePortTooltip))); RegisterAllPorts(); + RegisterPortEvents(); } /// @@ -86,7 +90,10 @@ public PackingNode() /// A collection of objects. /// A collection of objects. protected PackingNode(IEnumerable inPorts, IEnumerable outPorts) - : base(inPorts, outPorts) { } + : base(inPorts, outPorts) + { + RegisterPortEvents(); + } protected override void OnBuilt() { @@ -157,8 +164,38 @@ protected bool IsValidInputState(List inputAstNodes) return InPorts[0].Connectors.Any() && inputAstNodes.Count > 1 && IsInValidState && !inputAstNodes.Exists(node => node is NullNode); } + private void RegisterPortEvents() + { + PortConnected += OnPortConnected; + + PortDisconnected += OnPortDisconnected; + } + + private void OnPortConnected(PortModel port, ConnectorModel connector) + { + if (IsTypePort(port)) + { + RequestExecution?.Invoke(); + } + } + + private void OnPortDisconnected(PortModel port) + { + if (IsTypePort(port)) + { + RequestExecution?.Invoke(); + } + } + + private bool IsTypePort(PortModel port) + { + return port.Name == TypeDefinitionPortName && port.Index == 0; + } + public override void Dispose() { + PortConnected -= OnPortConnected; + PortDisconnected -= OnPortDisconnected; base.Dispose(); DataBridge.Instance.UnregisterCallback(GUID.ToString()); } @@ -173,6 +210,7 @@ public class PackingNodeView : INodeViewCustomization private DynamoViewModel dynamoViewModel; private DispatcherSynchronizationContext syncContext; private PackingNode node; + private NodeView view; public void CustomizeView(PackingNode nodeModel, NodeView nodeView) { @@ -181,6 +219,9 @@ public void CustomizeView(PackingNode nodeModel, NodeView nodeView) syncContext = new DispatcherSynchronizationContext(nodeView.Dispatcher); node = nodeModel; node.RequestScheduledTask += OnRequestScheduledTask; + view = nodeView; + + node.RequestExecution += OnRequestExecution; } private void OnRequestScheduledTask(Action action) @@ -199,6 +240,15 @@ private void OnRequestScheduledTask(Action action) s.ScheduleForExecution(t); } + private void OnRequestExecution() + { + if (view.ViewModel.WorkspaceViewModel.Model is HomeWorkspaceModel homeWorkspaceModel && + homeWorkspaceModel.RunSettings.RunType != RunType.Automatic) + { + dynamoModel.ForceRun(); + } + } + public void Dispose() { } From 20ec008cd2ca9858936c84f950f01165d237a7b6 Mon Sep 17 00:00:00 2001 From: Mathieu Bilodeau-Roy Date: Wed, 25 Jul 2018 11:12:11 -0400 Subject: [PATCH 7/7] Revert "-Added an event on port connected and disconnected to the packing node to force execution of the graph in non-automatic mode." This reverts commit 2d2a18626f7e8ae3d8c0a9de35d06f381d8675b5. --- .../PackingNodeModels/PackingNode.cs | 52 +------------------ 1 file changed, 1 insertion(+), 51 deletions(-) diff --git a/src/Libraries/PackingNodeModels/PackingNode.cs b/src/Libraries/PackingNodeModels/PackingNode.cs index a564b3e6b36..a972a0f4c9a 100644 --- a/src/Libraries/PackingNodeModels/PackingNode.cs +++ b/src/Libraries/PackingNodeModels/PackingNode.cs @@ -1,7 +1,5 @@ using Dynamo.Controls; -using Dynamo.Graph.Connectors; using Dynamo.Graph.Nodes; -using Dynamo.Graph.Workspaces; using Dynamo.Models; using Dynamo.Scheduler; using Dynamo.ViewModels; @@ -32,7 +30,6 @@ public abstract class PackingNode : NodeModel /// Event made to communicate with the NodeViewCustomization and request a scheduled action. /// public event Action RequestScheduledTask; - public event Action RequestExecution; private string _cachedTypeDefinition; private TypeDefinition _typeDefinition; @@ -81,7 +78,6 @@ public PackingNode() InPorts.Add(new PortModel(PortType.Input, this, new PortData(TypeDefinitionPortName, Resource.TypePortTooltip))); RegisterAllPorts(); - RegisterPortEvents(); } /// @@ -90,10 +86,7 @@ public PackingNode() /// A collection of objects. /// A collection of objects. protected PackingNode(IEnumerable inPorts, IEnumerable outPorts) - : base(inPorts, outPorts) - { - RegisterPortEvents(); - } + : base(inPorts, outPorts) { } protected override void OnBuilt() { @@ -164,38 +157,8 @@ protected bool IsValidInputState(List inputAstNodes) return InPorts[0].Connectors.Any() && inputAstNodes.Count > 1 && IsInValidState && !inputAstNodes.Exists(node => node is NullNode); } - private void RegisterPortEvents() - { - PortConnected += OnPortConnected; - - PortDisconnected += OnPortDisconnected; - } - - private void OnPortConnected(PortModel port, ConnectorModel connector) - { - if (IsTypePort(port)) - { - RequestExecution?.Invoke(); - } - } - - private void OnPortDisconnected(PortModel port) - { - if (IsTypePort(port)) - { - RequestExecution?.Invoke(); - } - } - - private bool IsTypePort(PortModel port) - { - return port.Name == TypeDefinitionPortName && port.Index == 0; - } - public override void Dispose() { - PortConnected -= OnPortConnected; - PortDisconnected -= OnPortDisconnected; base.Dispose(); DataBridge.Instance.UnregisterCallback(GUID.ToString()); } @@ -210,7 +173,6 @@ public class PackingNodeView : INodeViewCustomization private DynamoViewModel dynamoViewModel; private DispatcherSynchronizationContext syncContext; private PackingNode node; - private NodeView view; public void CustomizeView(PackingNode nodeModel, NodeView nodeView) { @@ -219,9 +181,6 @@ public void CustomizeView(PackingNode nodeModel, NodeView nodeView) syncContext = new DispatcherSynchronizationContext(nodeView.Dispatcher); node = nodeModel; node.RequestScheduledTask += OnRequestScheduledTask; - view = nodeView; - - node.RequestExecution += OnRequestExecution; } private void OnRequestScheduledTask(Action action) @@ -240,15 +199,6 @@ private void OnRequestScheduledTask(Action action) s.ScheduleForExecution(t); } - private void OnRequestExecution() - { - if (view.ViewModel.WorkspaceViewModel.Model is HomeWorkspaceModel homeWorkspaceModel && - homeWorkspaceModel.RunSettings.RunType != RunType.Automatic) - { - dynamoModel.ForceRun(); - } - } - public void Dispose() { }