Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/bringing pack unpack to dynamo #9005

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/AssemblySharedInfoGenerator/AssemblySharedInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.5656")]


// By default, the "Product version" shown in the file properties window is
Expand All @@ -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.5656")]
14 changes: 14 additions & 0 deletions src/Dynamo.All.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
1 change: 1 addition & 0 deletions src/DynamoCore/Graph/Nodes/NodeCategories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
1 change: 1 addition & 0 deletions src/Libraries/CoreNodes/CoreNodes.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
<Compile Include="List.cs" />
<Compile Include="Math.cs" />
<Compile Include="Object.cs" />
<Compile Include="Packing.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
Expand Down
111 changes: 111 additions & 0 deletions src/Libraries/CoreNodes/Packing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
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
{
/// <summary>
/// Packs data to a dictionary while manually handling lacing in a "Longest" strategy.
///
/// </summary>
/// <param name="keys">Ordered list of keys to use in the output dictionary</param>
/// <param name="isCollection">Ordered list of whether a value is a collection or not, which helps differenciating Lacing from a normal array value.</param>
/// <param name="data">Ordered ArrayList or List of ArrayList defining the data to be packed.</param>
/// <returns>List of Dictionary matching the ordered keys and data lists.</returns>
public static object PackOutputAsDictionary(List<string> keys, List<bool> isCollection, [ArbitraryDimensionArrayImport] object data)
{
if (keys == null || keys.Count == 0 || isCollection == null || isCollection.Count == 0 || data == null)
{
return null;
}

var result = new List<Dictionary<string, object>>();

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<string, object> CreateOuputDictionary(List<string> keys, List<bool> isCollection, ArrayList inputs, int outputIndex)
{
var dictionary = new Dictionary<string, object>();

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;
}

/// <summary>
/// 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.
/// </summary>
/// <param name="isCollection"></param>
/// <param name="inputs"></param>
/// <returns></returns>
private static int GetOuputCountFrom(List<bool> isCollection, ArrayList inputs)
{
var subArrays = inputs.Cast<object>()
.Where(i => i is ArrayList)
.Cast<ArrayList>();

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
{
/// <summary>
/// Returns the value of dictionary at a given key.
/// </summary>
/// <param name="dictionary">Dictionary exposing all available values</param>
/// <param name="key">The key of the value to retrieve</param>
/// <returns>The value retrieved from the dictionary at the given key</returns>
public static object UnPackOutputByKey(DesignScript.Builtin.Dictionary dictionary, string key)
{
if (dictionary == null) return null;

return dictionary.ValueAtKey(key);
}
}
}
112 changes: 112 additions & 0 deletions src/Libraries/PackingNodeModels/Pack/Pack.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
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;

namespace PackingNodeModels.Pack
{
/// <summary>
/// Creates a node that takes in a TypeDefinition and input values defined by the given TypeDefinition and outputs it as dictionary.
/// </summary>
[NodeName("Pack")]
[NodeCategory(BuiltinNodeCategories.CORE_PACKING)]
[NodeDescription("PackNodeDescription", typeof(Resource))]
[OutPortNames("Out")]
[OutPortTypes("Dictionary")]
[OutPortDescriptions("Dictionary")]
[IsDesignScriptCompatible]
public class Pack : PackingNode
{
private List<object> cachedValues;
private IValidationManager validationManager;

public Pack()
{
validationManager = new ValidationManager(this);
}

/// <summary>
/// Private constructor used for serialization.
/// </summary>
/// <param name="inPorts">A collection of <see cref="PortModel"/> objects.</param>
/// <param name="outPorts">A collection of <see cref="PortModel"/> objects.</param>
[JsonConstructor]
protected Pack(IEnumerable<PortModel> inPorts, IEnumerable<PortModel> 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<object> values)
{
var wasInWarningState = validationManager.Warnings.Any();
if (values.Count > 1)
{
var valuesByIndex = new Dictionary<int, object>();
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<AssociativeNode> BuildOutputAst(List<AssociativeNode> inputAstNodes)
{
var baseOutput = base.BuildOutputAst(inputAstNodes).ToList();

if (inputAstNodes == null || !IsValidInputState(inputAstNodes) || TypeDefinition == null)
{
baseOutput.Add(AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), AstFactory.BuildNullNode()));
return baseOutput;
}

inputAstNodes = inputAstNodes.Skip(1).ToList();
inputAstNodes.Add(AstFactory.BuildStringNode(TypeDefinition.Name));
var keys = TypeDefinition.Properties.Select(input => 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();

var functionCall = AstFactory.BuildFunctionCall(
new Func<List<string>, List<bool>, object, object>(DSCore.PackFunctions.PackOutputAsDictionary),
new List<AssociativeNode> { AstFactory.BuildExprList(keys), AstFactory.BuildExprList(isCollectionInputs), AstFactory.BuildExprList(inputAstNodes) });

baseOutput.Add(AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), functionCall));

return baseOutput;
}
}
}
Loading