Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/zeusongit/Dynamo
Browse files Browse the repository at this point in the history
  • Loading branch information
zeusongit committed May 23, 2024
2 parents b73b834 + 8e108f2 commit 6536fd4
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 65 deletions.
53 changes: 25 additions & 28 deletions src/Libraries/CoreNodeModels/DefineData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ public class DefineData : DSDropDownBase
private List<DynamoDropDownItem> serializedItems;
private bool isAutoMode = true; // default start with auto-detect 'on'
private bool isList;
private string playerValue = "";
private string displayValue = Properties.Resources.DefineDataDisplayValueMessage;

/// <summary>
Expand Down Expand Up @@ -99,16 +98,23 @@ public override bool IsInputNode
get { return true; }
}

[JsonIgnore]
public string PlayerValue

private string value = "";

[JsonProperty("InputValue")]
public virtual string Value
{
get { return playerValue; }
get
{
return value;
}
set
{
if (Equals(this.playerValue, null) || !this.playerValue.Equals(value))
if (Equals(this.value, null) || !this.value.Equals(value))
{
playerValue = value ?? "";
this.value = value ?? "";
MarkNodeAsModified();
RaisePropertyChanged(nameof(Value));
}
}
}
Expand All @@ -120,8 +126,6 @@ public DefineData() : base(">")
{
PropertyChanged += OnPropertyChanged;

//Items.Add(new DynamoDropDownItem("Select a type", null));

foreach (var dataType in Data.DataNodeDynamoTypeList)
{
var displayName = dataType.Name;
Expand Down Expand Up @@ -158,40 +162,38 @@ public override void Dispose()
DataBridge.Instance.UnregisterCallback(GUID.ToString());
}

private static readonly string BuiltinDictionaryTypeName = typeof(DesignScript.Builtin.Dictionary).FullName;
private static readonly string BuiltinDictionaryGet = nameof(DesignScript.Builtin.Dictionary.ValueAtKey);

public override IEnumerable<AssociativeNode> BuildOutputAst(List<AssociativeNode> inputAstNodes)
{
{
var resultAst = new List<AssociativeNode>();

// function call inputs - reference to the function, and the function arguments coming from the inputs
// the object to be (type) evaluated
// the expected datatype
// if the input is an ArrayList or not
var function = new Func<object, string, bool, bool, string, Dictionary<string, object>>(DSCore.Data.IsSupportedDataNodeType);
var funtionInputs = new List<AssociativeNode> {
var functionInputs = new List<AssociativeNode> {
inputAstNodes[0],
AstFactory.BuildStringNode((Items[SelectedIndex].Item as Data.DataNodeDynamoType).Type.ToString()),
AstFactory.BuildBooleanNode(IsList),
AstFactory.BuildBooleanNode(IsAutoMode),
AstFactory.BuildStringNode(PlayerValue)
AstFactory.BuildStringNode(Value)
};

var functionCall = AstFactory.BuildFunctionCall(function, funtionInputs);
var functionCall = AstFactory.BuildFunctionCall(function, functionInputs);
var functionCallIdentifier = AstFactory.BuildIdentifier(GUID + "_func");

resultAst.Add(AstFactory.BuildAssignment(functionCallIdentifier, functionCall));

//Next add the first key value pair to the output port
var getFirstKey = AstFactory.BuildFunctionCall(BuiltinDictionaryTypeName, BuiltinDictionaryGet,
new List<AssociativeNode> { functionCallIdentifier, AstFactory.BuildStringNode(">") });
// Next add the first key value pair to the output port
var safeExtractDictionaryValue = new Func<Dictionary<string, object>, string, object>(DSCore.Data.SafeExtractDictionaryValue);
var getFirstKey = AstFactory.BuildFunctionCall(safeExtractDictionaryValue,
[functionCallIdentifier, AstFactory.BuildStringNode(">")]);

resultAst.Add(AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), getFirstKey));

//Second get the key value pair to pass to the databridge callback
var getSecondKey = AstFactory.BuildFunctionCall(BuiltinDictionaryTypeName, BuiltinDictionaryGet,
new List<AssociativeNode> { functionCallIdentifier, AstFactory.BuildStringNode("Validation") });
// Second get the key value pair to pass to the databridge callback
var getSecondKey = AstFactory.BuildFunctionCall(safeExtractDictionaryValue,
[functionCallIdentifier, AstFactory.BuildStringNode("Validation")]);

resultAst.Add(AstFactory.BuildAssignment(
AstFactory.BuildIdentifier(GUID + "_db"),
Expand All @@ -201,25 +203,20 @@ public override IEnumerable<AssociativeNode> BuildOutputAst(List<AssociativeNode
}


/// <summary>
/// Not sure at the moment how relevant is the databridge for this node type
/// </summary>
/// <param name="data"></param>
private void DataBridgeCallback(object data)
{
//Todo If the playerValue is not empty string then we can chanage the UI to reflect the value is coming from the player
//Todo if the function call throws we don't get back to DatabridgeCallback. Not sure if we need to handle this case

//Now we reset this value to empty string so that the next time a value is set from upstream nodes we can know that it is not coming from the player
playerValue = "";
value = "";

// If data is null
if (data == null)
{
if(IsAutoMode)
{
DisplayValue = string.Empty; // show blank if we are in locked mode (as we cannot interact with the node)
//DisplayValue = Properties.Resources.DefineDataDisplayValueMessage;
}
else
{
Expand Down Expand Up @@ -290,7 +287,7 @@ protected override bool UpdateValueCore(UpdateValueParams updateValueParams)
switch (name)
{
case "Value":
PlayerValue = value;
Value = value;
return true; // UpdateValueCore handled.
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ public void CustomizeView(DefineData model, NodeView nodeView)
};
selectedItemDisplay.SetBinding(TextBox.WidthProperty, widthBinding);


// Move the ComboBox to the placeholder
var placeholderText = formControl.FindName("TextPlaceholder") as TextBox;
if (placeholderText != null)
Expand Down Expand Up @@ -189,6 +188,11 @@ public void CustomizeView(DefineData model, NodeView nodeView)
dropdown.DropDownClosed -= dropDown_DropDownClosed;
}

if (selectedItemDisplay != null)
{
selectedItemDisplay.IsEnabledChanged -= selectedItemDisplay_IsEnabledChanged;
}

if (listToggleButton != null)
{
listToggleButton.Click -= listToggle_IsClicked;
Expand Down Expand Up @@ -217,6 +221,14 @@ private void dropDown_DropDownClosed(object sender, EventArgs e)
}
}

private void selectedItemDisplay_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (sender is TextBox textBox)
{
textBox.MinWidth = textBox.IsEnabled ? 200 : 220;
}
}

private void listToggle_IsClicked(object sender, RoutedEventArgs e)
{
if (modeToggleButton != null && modeToggleButton.IsChecked == true)
Expand Down
114 changes: 81 additions & 33 deletions src/Libraries/CoreNodes/Data.cs
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,23 @@ static Data()
DataNodeDynamoTypeList = new ReadOnlyCollection<DataNodeDynamoType>(typeList);
}

/// <summary>
/// A helper function to safely extract a dictionary value
/// </summary>
/// <param name="dict">The dictionary to extract the value from</param>
/// <param name="key">The key of the key/value pair</param>
/// <returns></returns>
[IsVisibleInDynamoLibrary(false)]
public static object SafeExtractDictionaryValue(Dictionary<string, object> dict, string key)
{
if (dict?.TryGetValue(key, out var value) == true)
{
return value;
}

return null;
}

/// <summary>
/// This is the function used by AST
/// Handles some of the the node logic while performing the validation
Expand All @@ -656,11 +673,13 @@ static Data()
/// <param name="typeString">The Type as string (it would be better to pass an object of type 'Type' for direct type comparison)</param>
/// <param name="isList">If the input is of type `ArrayList`</param>
/// <param name="isAutoMode">If the node is in Auto mode</param>
/// <param name="playerValue">The value coming from Dynamo Player</param>
/// <returns></returns>
[IsVisibleInDynamoLibrary(false)]
public static Dictionary<string, object> IsSupportedDataNodeType([ArbitraryDimensionArrayImport] object inputValue,
string typeString, bool isList, bool isAutoMode, string playerValue)
{

if (inputValue == null)
{
// Don't raise a warning if the node is unused
Expand All @@ -673,7 +692,8 @@ public static Dictionary<string, object> IsSupportedDataNodeType([ArbitraryDimen

if (!IsSingleValueOrSingleLevelArrayList(inputValue))
{
throw new NotSupportedException(Properties.Resources.DefineDataSupportedInputValueExceptionMessage);
var warning = Properties.Resources.DefineDataSupportedInputValueExceptionMessage;
return DefineDataResult(inputValue, false, false, DataNodeDynamoTypeList.First(), warning);
}

// If the playerValue is not empty, then we assume it was set by the player.
Expand All @@ -687,19 +707,18 @@ public static Dictionary<string, object> IsSupportedDataNodeType([ArbitraryDimen
catch (Exception ex)
{
dynamoLogger?.Log("A Player value failed to deserialize with this exception: " + ex.Message);
throw new NotSupportedException(Properties.Resources.Exception_Deserialize_Unsupported_Cache);

var warning = Properties.Resources.Exception_Deserialize_Unsupported_Cache;
return DefineDataResult(inputValue, false, false, DataNodeDynamoTypeList.First(), warning);
}
}

object result; // Tuple<IsValid: bool, UpdateList: bool, InputType: DataNodeDynamoType>

// Currently working around passing the type as a string from the node - can be developed further to pass directly the type value
var type = DataNodeDynamoTypeList.First(x => x.Type.ToString().Equals(typeString));

if (isAutoMode)
{
// If running in AutoMode, then we would propagate the actual Type and List value and validate against them
// List logic
bool updateList = false;

var assertList = inputValue is ArrayList;
Expand All @@ -716,46 +735,78 @@ public static Dictionary<string, object> IsSupportedDataNodeType([ArbitraryDimen
{
// We couldn't find a common ancestor - list containing unsupported or incompatible data types
var incompatibleDataTypes = ConcatenateUniqueDataTypes(inputValue);
throw new ArgumentException(string.Format(Properties.Resources.DefineDataUnsupportedCombinationOfDataTypesExceptionMessage,
incompatibleDataTypes));
var warning = string.Format(Properties.Resources.DefineDataUnsupportedCombinationOfDataTypesExceptionMessage, incompatibleDataTypes);
return DefineDataResult(inputValue, false, updateList, DataNodeDynamoTypeList.First(), warning);
}
var inputType = DataNodeDynamoTypeList.FirstOrDefault(x => x.Type == valueType, null);
if(inputType == null)
if (inputType == null)
{
// We couldn't find a Dynamo data type that fits, so we throw
throw new ArgumentException(string.Format(Properties.Resources.DefineDataUnsupportedDataTypeExceptionMessage,
valueType.Name));
var warning = string.Format(Properties.Resources.DefineDataUnsupportedDataTypeExceptionMessage, valueType.Name);
return DefineDataResult(inputValue, false, updateList, inputType, warning);

}
result = (IsValid: false, UpdateList: updateList, InputType: inputType);
return DefineDataResult(inputValue, false, updateList, inputType, string.Empty);
}
else
{
result = (IsValid: true, UpdateList: updateList, InputType: type);
return DefineDataResult(inputValue, true, updateList, type, string.Empty);
}

return new Dictionary<string, object>
{
{ ">", inputValue },
{ "Validation", result }
};
}
else
{
// If we are in 'Manual mode' then we just validate and throw as needed
var isSupportedType = IsSupportedDataNodeDynamoType(inputValue, type.Type, isList);
if (!isSupportedType)
{
throw new ArgumentException(string.Format(Properties.Resources.DefineDataUnexpectedInputExceptionMessage, type.Type.FullName,
inputValue.GetType().FullName));
var expectedType = GetStringTypeFromInput(type, isList);
var currentType = GetStringTypeFromInput(inputValue);
var warning = string.Format(Properties.Resources.DefineDataUnexpectedInputExceptionMessage, expectedType, currentType);
return DefineDataResult(inputValue, false, false, DataNodeDynamoTypeList.First(), warning);
}
result = (IsValid: isSupportedType, UpdateList: false, InputType: type);

return new Dictionary<string, object>
return DefineDataResult(inputValue, isSupportedType, false, type, string.Empty);

}
}

private static string GetStringTypeFromInput(object inputValue)
{
if (inputValue == null) return string.Empty;
try
{
if (inputValue is ArrayList || inputValue is IEnumerable)
{
{ ">", inputValue },
{ "Validation", result }
};
var values = ConcatenateUniqueDataTypes(inputValue);
return $"List of {values}";
}

return inputValue.GetType().FullName;
}
catch (Exception) { return string.Empty; }
}

private static string GetStringTypeFromInput(DataNodeDynamoType type, bool isList)
{
if (type == null) return string.Empty;

var typeFullName = type.Type.FullName;
return isList ? $"List of {typeFullName}" : typeFullName;
}

private static Dictionary<string, object> DefineDataResult(object inputValue, bool isSupportedType, bool updateList, DataNodeDynamoType type, string warning)
{
if(warning != string.Empty)
{
throw new Exception(warning);
}

var result = (IsValid: isSupportedType, UpdateList: updateList, InputType: type);
return new Dictionary<string, object>
{
{ ">", inputValue },
{ "Validation", result }
};
}

private static string ConcatenateUniqueDataTypes(object inputValue)
Expand Down Expand Up @@ -837,15 +888,12 @@ private static DataNodeDynamoType FindClosestCommonAncestor(List<DataNodeDynamoT
// Try to predict the likely ancestor as the single lowest-level node type
var likelyAncestor = LikelyAncestor(nodes);

var uniqueLevelNodes = nodes
.GroupBy(x => x.Level)
.Select(g => g.First())
.ToList();

foreach (var node in uniqueLevelNodes)
foreach (var node in nodes)
{
if (node.Type == likelyAncestor.Type) continue;
likelyAncestor = FindCommonAncestorBetweenTwoNodes(node, likelyAncestor);
if (node.Type == likelyAncestor.Type) continue; // skip self

// if at least one node type is not related to the likely ancestor, bail
likelyAncestor = FindCommonAncestorBetweenTwoNodes(node, likelyAncestor);

if (likelyAncestor == null) break;
}
Expand Down
Loading

0 comments on commit 6536fd4

Please sign in to comment.